Merge remote-tracking branch 'origin/master' into jetty-8

Conflicts:
	jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
This commit is contained in:
Jan Bartel 2011-10-06 18:31:57 +11:00
commit d862348b4c
19 changed files with 504 additions and 164 deletions

View File

@ -360,26 +360,29 @@ public class HttpConnection extends AbstractConnection implements Dumpable
// we wont be called again so let the parser see the close
complete=true;
_parser.parseAvailable();
// TODO should not need this
if (!(_parser.isComplete()||_parser.isIdle()))
{
LOG.warn("Incomplete {} {}",_parser,_endp);
if (_exchange!=null)
_exchange.cancel();
if (_exchange!=null && !_exchange.isDone())
{
_exchange.setStatus(HttpExchange.STATUS_EXCEPTED);
_exchange.getEventListener().onException(new EOFException("Incomplete"));
}
}
}
}
/* TODO - is this needed ?
if (_generator.isComplete() && !_parser.isComplete())
// TODO should not need this
if (_endp.isInputShutdown() && !_parser.isComplete())
{
if (!_endp.isOpen() || _endp.isInputShutdown())
if (_exchange!=null && !_exchange.isDone())
{
complete=true;
close=true;
close();
_exchange.setStatus(HttpExchange.STATUS_EXCEPTED);
_exchange.getEventListener().onException(new EOFException("Incomplete"));
}
_endp.close();
}
*/
if (complete || failed)
{

View File

@ -14,9 +14,9 @@
package org.eclipse.jetty.client;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
@ -25,6 +25,9 @@ import java.util.concurrent.TimeUnit;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @version $Revision$ $Date$
*/
@ -37,7 +40,7 @@ public abstract class AbstractConnectionTest
// httpClient.setConnectorType(HttpClient.CONNECTOR_SOCKET);
return httpClient;
}
@Test
public void testServerClosedConnection() throws Exception
{
@ -57,6 +60,19 @@ public abstract class AbstractConnectionTest
httpClient.send(exchange);
Socket remote = serverSocket.accept();
// HttpClient.send() above is async, so if we write the response immediately
// there is a chance that it arrives before the request is being sent, so we
// read the request before sending the response to avoid the race
InputStream input = remote.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
String line;
while ((line = reader.readLine()) != null)
{
if (line.length() == 0)
break;
}
OutputStream output = remote.getOutputStream();
output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8"));
output.write("Content-Length: 0\r\n".getBytes("UTF-8"));
@ -80,6 +96,15 @@ public abstract class AbstractConnectionTest
httpClient.send(exchange);
remote = serverSocket.accept();
input = remote.getInputStream();
reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
while ((line = reader.readLine()) != null)
{
if (line.length() == 0)
break;
}
output = remote.getOutputStream();
output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8"));
output.write("Content-Length: 0\r\n".getBytes("UTF-8"));
@ -94,6 +119,105 @@ public abstract class AbstractConnectionTest
}
}
@Test
public void testServerClosedIncomplete() throws Exception
{
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(null);
int port=serverSocket.getLocalPort();
HttpClient httpClient = newHttpClient();
httpClient.setMaxConnectionsPerAddress(1);
httpClient.start();
try
{
CountDownLatch latch = new CountDownLatch(1);
HttpExchange exchange = new ConnectionExchange(latch);
exchange.setAddress(new Address("localhost", port));
exchange.setRequestURI("/");
httpClient.send(exchange);
Socket remote = serverSocket.accept();
// HttpClient.send() above is async, so if we write the response immediately
// there is a chance that it arrives before the request is being sent, so we
// read the request before sending the response to avoid the race
InputStream input = remote.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
String line;
while ((line = reader.readLine()) != null)
{
if (line.length() == 0)
break;
}
OutputStream output = remote.getOutputStream();
output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8"));
output.write("Content-Length: 10\r\n".getBytes("UTF-8"));
output.write("\r\n".getBytes("UTF-8"));
output.flush();
remote.close();
assertEquals(HttpExchange.STATUS_EXCEPTED, exchange.waitForDone());
}
finally
{
httpClient.stop();
}
}
@Test
public void testServerHalfClosedIncomplete() throws Exception
{
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(null);
int port=serverSocket.getLocalPort();
HttpClient httpClient = newHttpClient();
httpClient.setIdleTimeout(10000);
httpClient.setMaxConnectionsPerAddress(1);
httpClient.start();
try
{
CountDownLatch latch = new CountDownLatch(1);
HttpExchange exchange = new ConnectionExchange(latch);
exchange.setAddress(new Address("localhost", port));
exchange.setRequestURI("/");
httpClient.send(exchange);
Socket remote = serverSocket.accept();
// HttpClient.send() above is async, so if we write the response immediately
// there is a chance that it arrives before the request is being sent, so we
// read the request before sending the response to avoid the race
InputStream input = remote.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
String line;
while ((line = reader.readLine()) != null)
{
if (line.length() == 0)
break;
}
OutputStream output = remote.getOutputStream();
output.write("HTTP/1.1 200 OK\r\n".getBytes("UTF-8"));
output.write("Content-Length: 10\r\n".getBytes("UTF-8"));
output.write("\r\n".getBytes("UTF-8"));
output.flush();
remote.shutdownOutput();
assertEquals(HttpExchange.STATUS_EXCEPTED, exchange.waitForDone());
}
finally
{
httpClient.stop();
}
}
@Test
public void testConnectionFailed() throws Exception
{

View File

@ -22,4 +22,12 @@ public class AsyncSelectConnectionTest extends AbstractConnectionTest
httpClient.setConnectBlocking(false);
return httpClient;
}
@Override
public void testServerHalfClosedIncomplete() throws Exception
{
super.testServerHalfClosedIncomplete();
}
}

View File

@ -32,12 +32,4 @@ public class AsyncSslHttpExchangeTest extends SslHttpExchangeTest
_port = _server.getConnectors()[0].getLocalPort();
}
@Test
public void testPerf1() throws Exception
{
sender(10,true);
}
}

View File

@ -483,8 +483,13 @@ public abstract class AbstractGenerator implements Generator
{
if (close)
_persistent=false;
if (!isCommitted())
if (isCommitted())
{
LOG.debug("sendError on committed: {} {}",code,reason);
}
else
{
LOG.debug("sendError: {} {}",code,reason);
setResponse(code, reason);
if (content != null)
{

View File

@ -4,15 +4,16 @@
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.http;
import java.io.EOFException;
import java.io.IOException;
import org.eclipse.jetty.io.Buffer;
@ -67,7 +68,7 @@ public class HttpParser implements Parser
private String _multiLineValue;
private int _responseStatus; // If >0 then we are parsing a response
private boolean _forceContentBuffer;
/* ------------------------------------------------------------------------------- */
protected final View _contentView=new View(); // View of the content in the buffer for {@link Input}
protected int _state=STATE_START;
@ -78,7 +79,7 @@ public class HttpParser implements Parser
protected int _chunkLength;
protected int _chunkPosition;
private boolean _headResponse;
/* ------------------------------------------------------------------------------- */
/**
* Constructor.
@ -103,7 +104,7 @@ public class HttpParser implements Parser
/* ------------------------------------------------------------------------------- */
/**
* Constructor.
* @param buffers the buffers to use
* @param buffers the buffers to use
* @param endp the endpoint
* @param handler the even handler
*/
@ -134,7 +135,7 @@ public class HttpParser implements Parser
{
_headResponse=head;
}
/* ------------------------------------------------------------------------------- */
public int getState()
{
@ -170,7 +171,7 @@ public class HttpParser implements Parser
{
return isState(STATE_END);
}
/* ------------------------------------------------------------ */
public boolean isMoreInBuffer()
throws IOException
@ -203,11 +204,11 @@ public class HttpParser implements Parser
if (parseNext()<0)
return;
}
/* ------------------------------------------------------------------------------- */
/**
* Parse until END state.
* This method will parse any remaining content in the current buffer. It does not care about the
* This method will parse any remaining content in the current buffer. It does not care about the
* {@link #getState current state} of the parser.
* @see #parse
* @see #parseNext
@ -216,7 +217,7 @@ public class HttpParser implements Parser
{
int progress = parseNext();
int total=progress>0?1:0;
// continue parsing
while (!isComplete() && _buffer!=null && _buffer.length()>0)
{
@ -237,9 +238,9 @@ public class HttpParser implements Parser
{
int progress=0;
if (_state == STATE_END)
if (_state == STATE_END)
return 0;
if (_buffer==null)
{
if (_header == null)
@ -252,24 +253,24 @@ public class HttpParser implements Parser
_tok0.setPutIndex(_tok0.getIndex());
_tok1.setPutIndex(_tok1.getIndex());
}
if (_state == STATE_CONTENT && _contentPosition == _contentLength)
{
_state=STATE_END;
_handler.messageComplete(_contentPosition);
return 1;
}
int length=_buffer.length();
// Fill buffer if we can
if (length == 0)
{
long filled=-1;
IOException ex=null;
try
{
try
{
filled=fill();
}
catch(IOException e)
@ -277,8 +278,8 @@ public class HttpParser implements Parser
LOG.debug(this.toString(),e);
ex=e;
}
if (filled < 0 || _endp.isInputShutdown())
if (filled < 0 || _endp.isInputShutdown())
{
if (_headResponse && _state>STATE_END)
{
@ -294,21 +295,25 @@ public class HttpParser implements Parser
Buffer chunk=_buffer.get(_buffer.length());
_contentPosition += chunk.length();
_contentView.update(chunk);
_handler.content(chunk); // May recurse here
_handler.content(chunk); // May recurse here
}
_state=STATE_END;
_handler.messageComplete(_contentPosition);
return 1;
}
if (ex!=null)
throw ex;
if (!isComplete() && !isIdle())
throw new EOFException();
return -1;
}
length=_buffer.length();
}
// EventHandler header
byte ch;
byte[] array=_buffer.array();
@ -320,16 +325,16 @@ public class HttpParser implements Parser
progress++;
last=_state;
}
ch=_buffer.get();
if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED)
{
_eol=HttpTokens.LINE_FEED;
continue;
}
_eol=0;
switch (_state)
{
case STATE_START:
@ -476,22 +481,22 @@ public class HttpParser implements Parser
_state=STATE_HEADER_VALUE;
break;
}
default:
{
// handler last header if any
if (_cached!=null || _tok0.length() > 0 || _tok1.length() > 0 || _multiLineValue != null)
{
Buffer header=_cached!=null?_cached:HttpHeaders.CACHE.lookup(_tok0);
_cached=null;
Buffer value=_multiLineValue == null ? _tok1 : new ByteArrayBuffer(_multiLineValue);
int ho=HttpHeaders.CACHE.getOrdinal(header);
if (ho >= 0)
{
int vo;
int vo;
switch (ho)
{
case HttpHeaders.CONTENT_LENGTH_ORDINAL:
@ -510,7 +515,7 @@ public class HttpParser implements Parser
_contentLength=HttpTokens.NO_CONTENT;
}
break;
case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
value=HttpHeaderValues.CACHE.lookup(value);
vo=HttpHeaderValues.CACHE.getOrdinal(value);
@ -521,22 +526,22 @@ public class HttpParser implements Parser
String c=value.toString(StringUtil.__ISO_8859_1);
if (c.endsWith(HttpHeaderValues.CHUNKED))
_contentLength=HttpTokens.CHUNKED_CONTENT;
else if (c.indexOf(HttpHeaderValues.CHUNKED) >= 0)
throw new HttpException(400,null);
}
break;
}
}
_handler.parsedHeader(header, value);
_tok0.setPutIndex(_tok0.getIndex());
_tok1.setPutIndex(_tok1.getIndex());
_multiLineValue=null;
}
_buffer.setMarkIndex(-1);
// now handle ch
if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
{
@ -556,7 +561,7 @@ public class HttpParser implements Parser
_eol=ch;
if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED)
_eol=_buffer.get();
// We convert _contentLength to an int for this switch statement because
// we don't care about the amount of data available just whether there is some.
switch (_contentLength > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) _contentLength)
@ -565,19 +570,19 @@ public class HttpParser implements Parser
_state=STATE_EOF_CONTENT;
_handler.headerComplete(); // May recurse here !
break;
case HttpTokens.CHUNKED_CONTENT:
_state=STATE_CHUNKED_CONTENT;
_handler.headerComplete(); // May recurse here !
break;
case HttpTokens.NO_CONTENT:
_state=STATE_END;
returnBuffers();
_handler.headerComplete();
_handler.headerComplete();
_handler.messageComplete(_contentPosition);
break;
default:
_state=STATE_CONTENT;
_handler.headerComplete(); // May recurse here !
@ -591,7 +596,7 @@ public class HttpParser implements Parser
_length=1;
_buffer.mark();
_state=STATE_HEADER_NAME;
// try cached name!
if (array!=null)
{
@ -604,10 +609,10 @@ public class HttpParser implements Parser
length=_buffer.length();
}
}
}
}
}
}
break;
case STATE_HEADER_NAME:
@ -629,16 +634,16 @@ public class HttpParser implements Parser
case HttpTokens.SPACE:
case HttpTokens.TAB:
break;
default:
default:
{
_cached=null;
if (_length == -1)
if (_length == -1)
_buffer.mark();
_length=_buffer.getIndex() - _buffer.markIndex();
_state=STATE_HEADER_IN_NAME;
_state=STATE_HEADER_IN_NAME;
}
}
break;
case STATE_HEADER_IN_NAME:
@ -694,11 +699,11 @@ public class HttpParser implements Parser
break;
default:
{
if (_length == -1)
if (_length == -1)
_buffer.mark();
_length=_buffer.getIndex() - _buffer.markIndex();
_state=STATE_HEADER_IN_VALUE;
}
}
}
break;
@ -732,9 +737,9 @@ public class HttpParser implements Parser
break;
}
} // end of HEADER states loop
// ==========================
// Handle HEAD response
if (_responseStatus>0 && _headResponse)
{
@ -743,10 +748,10 @@ public class HttpParser implements Parser
}
// ==========================
// Handle _content
length=_buffer.length();
Buffer chunk;
Buffer chunk;
last=_state;
while (_state > STATE_END && length > 0)
{
@ -755,7 +760,7 @@ public class HttpParser implements Parser
progress++;
last=_state;
}
if (_eol == HttpTokens.CARRIAGE_RETURN && _buffer.peek() == HttpTokens.LINE_FEED)
{
_eol=_buffer.get();
@ -769,11 +774,11 @@ public class HttpParser implements Parser
chunk=_buffer.get(_buffer.length());
_contentPosition += chunk.length();
_contentView.update(chunk);
_handler.content(chunk); // May recurse here
_handler.content(chunk); // May recurse here
// TODO adjust the _buffer to keep unconsumed content
return 1;
case STATE_CONTENT:
case STATE_CONTENT:
{
long remaining=_contentLength - _contentPosition;
if (remaining == 0)
@ -782,24 +787,24 @@ public class HttpParser implements Parser
_handler.messageComplete(_contentPosition);
return 1;
}
if (length > remaining)
if (length > remaining)
{
// We can cast reamining to an int as we know that it is smaller than
// or equal to length which is already an int.
// or equal to length which is already an int.
length=(int)remaining;
}
chunk=_buffer.get(length);
_contentPosition += chunk.length();
_contentView.update(chunk);
_handler.content(chunk); // May recurse here
_handler.content(chunk); // May recurse here
if(_contentPosition == _contentLength)
{
_state=STATE_END;
_handler.messageComplete(_contentPosition);
}
}
// TODO adjust the _buffer to keep unconsumed content
return 1;
}
@ -826,7 +831,7 @@ public class HttpParser implements Parser
if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
{
_eol=ch;
if (_chunkLength == 0)
{
if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED)
@ -870,8 +875,8 @@ public class HttpParser implements Parser
}
break;
}
case STATE_CHUNK:
case STATE_CHUNK:
{
int remaining=_chunkLength - _chunkPosition;
if (remaining == 0)
@ -879,13 +884,13 @@ public class HttpParser implements Parser
_state=STATE_CHUNKED_CONTENT;
break;
}
else if (length > remaining)
else if (length > remaining)
length=remaining;
chunk=_buffer.get(length);
_contentPosition += chunk.length();
_chunkPosition += chunk.length();
_contentView.update(chunk);
_handler.content(chunk); // May recurse here
_handler.content(chunk); // May recurse here
// TODO adjust the _buffer to keep unconsumed content
return 1;
}
@ -893,13 +898,13 @@ public class HttpParser implements Parser
length=_buffer.length();
}
return progress;
}
/* ------------------------------------------------------------------------------- */
/** fill the buffers from the endpoint
*
*
*/
public long fill() throws IOException
{
@ -910,14 +915,14 @@ public class HttpParser implements Parser
_tok0=new View.CaseInsensitive(_buffer);
_tok1=new View.CaseInsensitive(_buffer);
}
// Is there unconsumed content in body buffer
if (_state>STATE_END && _buffer==_header && _header!=null && !_header.hasContent() && _body!=null && _body.hasContent())
{
_buffer=_body;
return _buffer.length();
}
// Shall we switch to a body buffer?
if (_buffer==_header && _state>STATE_END && _header.length()==0 && (_forceContentBuffer || (_contentLength-_contentPosition)>_header.capacity()) && (_body!=null||_buffers!=null))
{
@ -925,20 +930,20 @@ public class HttpParser implements Parser
_body=_buffers.getBuffer();
_buffer=_body;
}
// Do we have somewhere to fill from?
if (_endp != null )
{
// Shall we compact the body?
if (_buffer==_body || _state>STATE_END)
if (_buffer==_body || _state>STATE_END)
{
_buffer.compact();
}
// Are we full?
if (_buffer.space() == 0)
throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413, "FULL "+(_buffer==_body?"body":"head"));
if (_buffer.space() == 0)
throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413, "FULL "+(_buffer==_body?"body":"head"));
try
{
return _endp.fill(_buffer);
@ -955,7 +960,7 @@ public class HttpParser implements Parser
/* ------------------------------------------------------------------------------- */
/** Skip any CRLFs in buffers
*
*
*/
public void skipCRLF()
{
@ -983,12 +988,12 @@ public class HttpParser implements Parser
else
break;
}
}
/* ------------------------------------------------------------------------------- */
public void reset()
{
{
// reset state
_contentView.setGetIndex(_contentView.putIndex());
_state=STATE_START;
@ -1038,12 +1043,12 @@ public class HttpParser implements Parser
public void returnBuffers()
{
if (_body!=null && !_body.hasContent() && _body.markIndex()==-1 && _buffers!=null)
{
{
if (_buffer==_body)
_buffer=_header;
if (_buffers!=null)
_buffers.returnBuffer(_body);
_body=null;
_body=null;
}
if (_header!=null && !_header.hasContent() && _header.markIndex()==-1 && _buffers!=null)
@ -1054,7 +1059,7 @@ public class HttpParser implements Parser
_header=null;
}
}
/* ------------------------------------------------------------------------------- */
public void setState(int state)
{
@ -1067,13 +1072,13 @@ public class HttpParser implements Parser
{
return "state=" + _state + " length=" + _length + " buf=" + buf.hashCode();
}
/* ------------------------------------------------------------------------------- */
@Override
public String toString()
{
return "state=" + _state + " length=" + _length + " len=" + _contentLength;
}
}
/* ------------------------------------------------------------ */
public Buffer getHeaderBuffer()
@ -1084,7 +1089,7 @@ public class HttpParser implements Parser
}
return _header;
}
/* ------------------------------------------------------------ */
public Buffer getBodyBuffer()
{
@ -1098,20 +1103,20 @@ public class HttpParser implements Parser
public void setForceContentBuffer(boolean force)
{
_forceContentBuffer=force;
}
}
/* ------------------------------------------------------------ */
public Buffer blockForContent(long maxIdleTime) throws IOException
{
if (_contentView.length()>0)
return _contentView;
if (getState() <= HttpParser.STATE_END)
if (getState() <= HttpParser.STATE_END)
return null;
try
{
parseNext();
// parse until some progress is made (or IOException thrown for timeout)
while(_contentView.length() == 0 && !isState(HttpParser.STATE_END) && _endp!=null && _endp.isOpen())
{
@ -1135,9 +1140,9 @@ public class HttpParser implements Parser
_endp.close();
throw e;
}
return _contentView.length()>0?_contentView:null;
}
return _contentView.length()>0?_contentView:null;
}
/* ------------------------------------------------------------ */
/* (non-Javadoc)
@ -1155,11 +1160,11 @@ public class HttpParser implements Parser
return 0;
}
parseNext();
return _contentView==null?0:_contentView.length();
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
@ -1187,7 +1192,7 @@ public class HttpParser implements Parser
*/
public abstract void startRequest(Buffer method, Buffer url, Buffer version)
throws IOException;
/**
* This is the method called by parser when the HTTP request line is parsed
*/
@ -1197,5 +1202,5 @@ public class HttpParser implements Parser
}

View File

@ -47,6 +47,7 @@ public interface EndPoint
* The buffer may chose to do a compact before filling.
* @return an <code>int</code> value indicating the number of bytes
* filled or -1 if EOF is reached.
* @throws EofException If input is shutdown or the endpoint is closed.
*/
int fill(Buffer buffer) throws IOException;
@ -59,6 +60,7 @@ public interface EndPoint
*
* @param buffer The buffer to flush. This buffers getIndex is updated.
* @return the number of bytes written
* @throws EofException If the endpoint is closed or output is shutdown.
*/
int flush(Buffer buffer) throws IOException;
@ -157,7 +159,7 @@ public interface EndPoint
/* ------------------------------------------------------------ */
/** Flush any buffered output.
* May fail to write all data if endpoint is non-blocking
* @throws IOException
* @throws EofException If the endpoint is closed or output is shutdown.
*/
public void flush() throws IOException;

View File

@ -38,21 +38,42 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo
private final SelectorManager.SelectSet _selectSet;
private final SelectorManager _manager;
private SelectionKey _key;
private final Runnable _handler = new Runnable()
{
public void run() { handle(); }
};
/** The desired value for {@link SelectionKey#interestOps()} */
private int _interestOps;
/**
* The connection instance is the handler for any IO activity on the endpoint.
* There is a different type of connection for HTTP, AJP, WebSocket and
* ProxyConnect. The connection may change for an SCEP as it is upgraded
* from HTTP to proxy connect or websocket.
*/
private volatile Connection _connection;
/** true if a thread has been dispatched to handle this endpoint */
private boolean _dispatched = false;
/** true if a non IO dispatch (eg async resume) is outstanding */
private boolean _redispatched = false;
/** true if the last write operation succeed and wrote all offered bytes */
private volatile boolean _writable = true;
private SelectionKey _key;
private int _interestOps;
/** True if a thread has is blocked in {@link #blockReadable(long)} */
private boolean _readBlocked;
/** True if a thread has is blocked in {@link #blockWritable(long)} */
private boolean _writeBlocked;
/** true if {@link SelectSet#destroyEndPoint(SelectChannelEndPoint)} has not been called */
private boolean _open;
private volatile long _idleTimestamp;
/* ------------------------------------------------------------ */

View File

@ -14,9 +14,15 @@
package org.eclipse.jetty.io;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Reader;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import org.eclipse.jetty.util.IO;
import org.junit.Test;
@ -42,4 +48,88 @@ public class IOTest
out.toString(),
"The quick brown fox jumped over the lazy dog");
}
@Test
public void testHalfCloses() throws Exception
{
ServerSocket connector = new ServerSocket(0);
Socket client = new Socket("localhost",connector.getLocalPort());
System.err.println(client);
Socket server = connector.accept();
System.err.println(server);
// we can write both ways
client.getOutputStream().write(1);
assertEquals(1,server.getInputStream().read());
server.getOutputStream().write(1);
assertEquals(1,client.getInputStream().read());
// shutdown output results in read -1
client.shutdownOutput();
assertEquals(-1,server.getInputStream().read());
// Even though EOF has been read, the server input is not seen as shutdown
assertFalse(server.isInputShutdown());
// and we can read -1 again
assertEquals(-1,server.getInputStream().read());
// but cannot write
try { client.getOutputStream().write(1); assertTrue(false); } catch (SocketException e) {}
// but can still write in opposite direction.
server.getOutputStream().write(1);
assertEquals(1,client.getInputStream().read());
// server can shutdown input to match the shutdown out of client
server.shutdownInput();
// now we EOF instead of reading -1
try { server.getInputStream().read(); assertTrue(false); } catch (SocketException e) {}
// but can still write in opposite direction.
server.getOutputStream().write(1);
assertEquals(1,client.getInputStream().read());
// client can shutdown input
client.shutdownInput();
// now we EOF instead of reading -1
try { client.getInputStream().read(); assertTrue(false); } catch (SocketException e) {}
// But we can still write at the server (data which will never be read)
server.getOutputStream().write(1);
// and the server output is not shutdown
assertFalse( server.isOutputShutdown() );
// until we explictly shut it down
server.shutdownOutput();
// and now we can't write
try { server.getOutputStream().write(1); assertTrue(false); } catch (SocketException e) {}
// but the sockets are still open
assertFalse(client.isClosed());
assertFalse(server.isClosed());
// but if we close one end
client.close();
// it is seen as closed.
assertTrue(client.isClosed());
// but not the other end
assertFalse(server.isClosed());
// which has to be closed explictly
server.close();
assertTrue(server.isClosed());
}
}

View File

@ -27,7 +27,6 @@ public class AsyncHttpConnection extends HttpConnection
public Connection handle() throws IOException
{
LOG.debug("handle {}",this);
Connection connection = this;
boolean some_progress=false;
boolean progress=true;
@ -60,6 +59,11 @@ public class AsyncHttpConnection extends HttpConnection
// Flush output from buffering endpoint
if (_endp.isBufferingOutput())
_endp.flush();
// Special case close handling.
// If we were dispatched and have made no progress, but io is shutdown, then close
if (!progress && !some_progress && (_endp.isInputShutdown()||_endp.isOutputShutdown()))
_endp.close();
}
catch (HttpException e)
{
@ -123,13 +127,12 @@ public class AsyncHttpConnection extends HttpConnection
{
setCurrentConnection(null);
_parser.returnBuffers();
_generator.returnBuffers();
// Are we write blocked
if (_generator.isCommitted() && !_generator.isComplete() && _endp.isOpen())
((AsyncEndPoint)_endp).scheduleWrite();
else
_generator.returnBuffers();
// Check if we are write blocked
if (_generator.isCommitted() && !_generator.isComplete() && _endp.isOpen() && !_endp.isOutputShutdown())
((AsyncEndPoint)_endp).scheduleWrite(); // TODO. This should not be required
if (!some_progress)
{
_total_no_progress++;
@ -137,11 +140,6 @@ public class AsyncHttpConnection extends HttpConnection
if (NO_PROGRESS_INFO>0 && _total_no_progress%NO_PROGRESS_INFO==0 && (NO_PROGRESS_CLOSE<=0 || _total_no_progress< NO_PROGRESS_CLOSE))
{
LOG.info("EndPoint making no progress: "+_total_no_progress+" "+_endp);
LOG.setDebugEnabled(true);
Log.getLogger("org.eclipse.jetty.io.nio").getLogger("ssl").setDebugEnabled(true);
Log.getLogger(ChannelEndPoint.class).setDebugEnabled(true);
}
if (NO_PROGRESS_CLOSE>0 && _total_no_progress>NO_PROGRESS_CLOSE)
@ -154,7 +152,6 @@ public class AsyncHttpConnection extends HttpConnection
}
}
}
LOG.debug("unhandle {}",this);
}
return connection;
}

View File

@ -455,22 +455,22 @@ public abstract class HttpConnection extends AbstractConnection
{
async_exception=e;
LOG.debug(e);
_request.setHandled(true);
error=true;
_request.setHandled(true);
}
catch (UncheckedIOException e)
{
async_exception=e;
LOG.debug(e);
_request.setHandled(true);
error=true;
_request.setHandled(true);
}
catch (HttpException e)
{
LOG.debug(e);
error=true;
_request.setHandled(true);
_response.sendError(e.getStatus(), e.getReason());
error=true;
}
catch (Throwable e)
{
@ -478,9 +478,8 @@ public abstract class HttpConnection extends AbstractConnection
throw (ThreadDeath)e;
async_exception=e;
error=true;
LOG.warn(String.valueOf(_uri),e);
error=true;
_request.setHandled(true);
_generator.sendError(info==null?400:500, null, null, true);
}
@ -515,7 +514,12 @@ public abstract class HttpConnection extends AbstractConnection
if(_endp.isOpen())
{
if (error)
{
_endp.shutdownOutput();
_generator.setPersistent(false);
if (!_generator.isComplete())
_response.complete();
}
else
{
if (!_response.isCommitted() && !_request.isHandled())

View File

@ -45,7 +45,6 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
@Test
public void testMaxIdleWithRequest10() throws Exception
{
System.err.println("testMaxIdleWithRequest10");
configureServer(new HelloWorldHandler());
Socket client=newSocket(HOST,_connector.getLocalPort());
client.setSoTimeout(10000);
@ -77,7 +76,6 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
@Test
public void testMaxIdleWithRequest11() throws Exception
{
System.err.println("testMaxIdleWithRequest11");
configureServer(new EchoHandler());
Socket client=newSocket(HOST,_connector.getLocalPort());
client.setSoTimeout(10000);
@ -112,7 +110,6 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
@Test
public void testMaxIdleNoRequest() throws Exception
{
System.err.println("testMaxIdleNoRequest");
configureServer(new EchoHandler());
Socket client=newSocket(HOST,_connector.getLocalPort());
client.setSoTimeout(10000);
@ -141,7 +138,6 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
@Test
public void testMaxIdleWithSlowRequest() throws Exception
{
System.err.println("testMaxIdleWithSlowRequest");
configureServer(new EchoHandler());
Socket client=newSocket(HOST,_connector.getLocalPort());
client.setSoTimeout(10000);
@ -182,7 +178,6 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
@Test
public void testMaxIdleWithSlowResponse() throws Exception
{
System.err.println("testMaxIdleWithSlowResponse");
configureServer(new SlowResponseHandler());
Socket client=newSocket(HOST,_connector.getLocalPort());
client.setSoTimeout(10000);
@ -212,7 +207,6 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
@Test
public void testMaxIdleWithWait() throws Exception
{
System.err.println("testMaxIdleWithWait");
configureServer(new WaitHandler());
Socket client=newSocket(HOST,_connector.getLocalPort());
client.setSoTimeout(10000);

View File

@ -37,8 +37,11 @@ import javax.servlet.http.HttpServletResponse;
import junit.framework.Assert;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StdErrLog;
import org.junit.Test;
/**
@ -106,7 +109,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
* Feed the server the entire request at once.
*/
@Test
public void testRequest1_jetty() throws Exception
public void testRequest1() throws Exception
{
configureServer(new HelloWorldHandler());
@ -164,7 +167,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
* Feed the server fragmentary headers and see how it copes with it.
*/
@Test
public void testRequest1Fragments_jetty() throws Exception, InterruptedException
public void testRequest1Fragments() throws Exception, InterruptedException
{
configureServer(new HelloWorldHandler());
@ -197,7 +200,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
}
@Test
public void testRequest2_jetty() throws Exception
public void testRequest2() throws Exception
{
configureServer(new EchoHandler());
@ -226,7 +229,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
}
@Test
public void testRequest2Fragments_jetty() throws Exception
public void testRequest2Fragments() throws Exception
{
configureServer(new EchoHandler());
@ -270,7 +273,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
}
@Test
public void testRequest2Iterate_jetty() throws Exception
public void testRequest2Iterate() throws Exception
{
configureServer(new EchoHandler());
@ -309,7 +312,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
* After several iterations, I generated some known bad fragment points.
*/
@Test
public void testRequest2KnownBad_jetty() throws Exception
public void testRequest2KnownBad() throws Exception
{
configureServer(new EchoHandler());
@ -914,6 +917,68 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
}
}
@Test
public void testCommittedError() throws Exception
{
CommittedErrorHandler handler =new CommittedErrorHandler();
configureServer(handler);
Socket client=newSocket(HOST,_connector.getLocalPort());
try
{
((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(true);
OutputStream os=client.getOutputStream();
InputStream is=client.getInputStream();
// Send a request
os.write((
"GET / HTTP/1.1\r\n"+
"Host: "+HOST+":"+_connector.getLocalPort()+"\r\n" +
"\r\n"
).getBytes());
os.flush();
client.setSoTimeout(2000);
String in = IO.toString(is);
assertEquals(-1,is.read()); // Closed by error!
assertTrue(in.indexOf("HTTP/1.1 200 OK")>=0);
assertTrue(in.indexOf("Transfer-Encoding: chunked")>0);
assertTrue(in.indexOf("Now is the time for all good men to come to the aid of the party")>0);
assertTrue(in.indexOf("\r\n0\r\n")==-1); // chunking is interrupted by error close
client.close();
Thread.sleep(100);
assertTrue(!handler._endp.isOpen());
}
finally
{
((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(false);
if (!client.isClosed())
client.close();
}
}
protected static class CommittedErrorHandler extends AbstractHandler
{
public EndPoint _endp;
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
_endp=baseRequest.getConnection().getEndPoint();
response.setHeader("test","value");
response.setStatus(200);
response.setContentType("text/plain");
response.getWriter().println("Now is the time for all good men to come to the aid of the party");
response.getWriter().flush();
response.flushBuffer();
throw new ServletException(new Exception("exception after commit"));
}
}
protected static class AvailableHandler extends AbstractHandler
{
public Exchanger<Object> _ex = new Exchanger<Object>();

View File

@ -25,10 +25,12 @@ public class SelectChannelServerTest extends HttpServerTestBase
{
startServer(new SelectChannelConnector());
}
@Override
public void testBigBlocks() throws Exception
public void testCommittedError() throws Exception
{
super.testBigBlocks();
super.testCommittedError();
}
}

View File

@ -277,11 +277,11 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
}
}
// TODO is this too soon?
/* Set the webapp's classpath for Jasper */
context.setAttribute("org.apache.catalina.jsp_classpath", context.getClassPath());
/* Set the system classpath for Jasper */
holder.setInitParameter("com.sun.appserv.jsp.classpath", getSystemClassPath(context));
holder.setInitParameter("com.sun.appserv.jsp.classpath", getSystemClassPath(context));
}
//Set the servlet-class
@ -327,6 +327,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
{
holder.setForcedPath(jsp_file);
holder.setClassName(jspServletClass);
//set the system classpath explicitly for the holder that will represent the JspServlet instance
holder.setInitParameter("com.sun.appserv.jsp.classpath", getSystemClassPath(context));
}
// handle load-on-startup

View File

@ -95,12 +95,11 @@
</execution>
</executions>
</plugin>
<!--
<!--
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>7.1.1-SNAPSHOT</version>
<version>7.5.2-SNAPSHOT</version>
<configuration>
<stopPort>8087</stopPort>
<stopKey>foo</stopKey>

View File

@ -259,6 +259,17 @@
<url-pattern>/javadoc-proxy/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>foo.jsp</servlet-name>
<jsp-file>/jsp/foo/foo.jsp</jsp-file>
</servlet>
<servlet-mapping>
<servlet-name>foo.jsp</servlet-name>
<url-pattern>/jsp/foo/</url-pattern>
</servlet-mapping>
<error-page>
<error-code>404</error-code>
<location>/error404.html</location>

View File

@ -0,0 +1,15 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
</head>
<body>
<h1>FOO Example</h1>
<hr>
<p>A trivial FOO example
<hr>
<c:forEach var="i" begin="1" end="10" step="1">
<c:out value="${i}" />
<br />
</c:forEach>
</body>
</html>

View File

@ -11,6 +11,7 @@
<li><a href="tagfile.jsp">JSP 2.0 Tag File demo</a><br/>
<li><a href="expr.jsp?A=1">JSP 2.0 Tag Expression</a><br/>
<li><a href="jstl.jsp">JSTL Expression</a><br/>
<li><a href="foo/">Mapping to &lt;jsp-file&gt;</a><br/>
</ul>
<a href="/">Main Menu</a>