340265 Improve handling of io shutdown in SSL

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@2906 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Greg Wilkins 2011-03-23 03:36:06 +00:00
parent 903dd14656
commit a6483bfa6f
21 changed files with 435 additions and 218 deletions

View File

@ -6,6 +6,7 @@ jetty-7.3.2-SNAPSHOT
+ 339150 Validate client certificate when it is used for authentication
+ 339187 In the OSGi manifest of the jetty-all-server aggregate, mark javax.annotation as optional
+ 339543 Add configuration options for Certificate Revocation checking
+ 340265 Improve handling of io shutdown in SSL
+ Ensure generated fragment names are unique
+ JETTY-1245 Pooled Buffers implementation

View File

@ -145,10 +145,10 @@ public class Ajp13Parser implements Parser
}
/* ------------------------------------------------------------------------------- */
public long parseAvailable() throws IOException
public int parseAvailable() throws IOException
{
long len = parseNext();
long total = len > 0 ? len : 0;
int len = parseNext();
int total = len > 0 ? len : 0;
// continue parsing
while (!isComplete() && _buffer != null && _buffer.length() > 0)
@ -228,9 +228,9 @@ public class Ajp13Parser implements Parser
volatile int _seq=0;
/* ------------------------------------------------------------------------------- */
public long parseNext() throws IOException
public int parseNext() throws IOException
{
long total_filled = -1;
int total_filled = 0;
if (_buffer == null)
{
@ -250,7 +250,7 @@ public class Ajp13Parser implements Parser
{
_state = STATE_END;
_handler.messageComplete(_contentPosition);
return total_filled;
return 1;
}
if (_state < 0)

View File

@ -87,10 +87,11 @@ public class HttpExchangeTest extends TestCase
public void testPerf() throws Exception
{
sender(1,false);
sender(1,true);
if (Stress.isEnabled())
{
sender(1,false);
sender(1,true);
sender(100,false);
sender(100,true);
sender(10000,false);
@ -98,8 +99,6 @@ public class HttpExchangeTest extends TestCase
}
else
{
sender(1,false);
sender(1,true);
sender(10,false);
sender(10,true);
}
@ -168,7 +167,9 @@ public class HttpExchangeTest extends TestCase
if (len==2009)
latch.countDown();
else
{
System.err.println(n+" ONLY "+len);
}
complete.countDown();
}
@ -223,7 +224,7 @@ public class HttpExchangeTest extends TestCase
if(elapsed>0)
System.err.println(nb+"/"+_count+" c="+close+" rate="+(nb*1000/elapsed));
*/
assertEquals("nb="+nb+" close="+close,0,latch.getCount());
}

View File

@ -31,8 +31,6 @@ import org.eclipse.jetty.util.log.Log;
* two optional writer to byte conversions. buffer.writers=true will probably be
* faster, but will consume more memory. This option is just for testing and tuning.
*
*
*
*/
public abstract class AbstractGenerator implements Generator
{

View File

@ -198,7 +198,8 @@ public class HttpParser implements Parser
// continue parsing
while (_state != STATE_END)
parseNext();
if (parseNext()<0)
return;
}
/* ------------------------------------------------------------------------------- */
@ -209,34 +210,33 @@ public class HttpParser implements Parser
* @see #parse
* @see #parseNext
*/
public long parseAvailable() throws IOException
public int parseAvailable() throws IOException
{
long len = parseNext();
long total=len>0?len:0;
int progress = parseNext();
int total=progress>0?1:0;
// continue parsing
while (!isComplete() && _buffer!=null && _buffer.length()>0)
{
len = parseNext();
if (len>0)
total+=len;
progress = parseNext();
if (progress>0)
total++;
}
return total;
}
/* ------------------------------------------------------------------------------- */
/**
* Parse until next Event.
* @return number of bytes filled from endpoint or -1 if fill never called.
* @return an indication of progress <0 EOF, 0 no progress, >0 progress.
*/
public long parseNext() throws IOException
public int parseNext() throws IOException
{
long total_filled=-1;
int progress=0;
if (_state == STATE_END)
return -1;
return 0;
if (_buffer==null)
{
@ -256,7 +256,7 @@ public class HttpParser implements Parser
{
_state=STATE_END;
_handler.messageComplete(_contentPosition);
return total_filled;
return 1;
}
int length=_buffer.length();
@ -287,11 +287,9 @@ public class HttpParser implements Parser
throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413, "FULL "+(_buffer==_body?"body":"head"));
try
{
if (total_filled<0)
total_filled=0;
filled=_endp.fill(_buffer);
if (filled>0)
total_filled+=filled;
progress++;
}
catch(IOException e)
{
@ -303,6 +301,12 @@ public class HttpParser implements Parser
if (filled < 0)
{
if (_headResponse && _state>STATE_END)
{
_state=STATE_END;
_handler.messageComplete(_contentPosition);
return 1;
}
if ( _state == STATE_EOF_CONTENT)
{
if (_buffer.length()>0)
@ -315,10 +319,10 @@ public class HttpParser implements Parser
}
_state=STATE_END;
_handler.messageComplete(_contentPosition);
return total_filled;
return 1;
}
reset(true);
throw new EofException(ioex);
return -1;
}
length=_buffer.length();
}
@ -327,9 +331,15 @@ public class HttpParser implements Parser
// EventHandler header
byte ch;
byte[] array=_buffer.array();
int last=_state;
while (_state<STATE_END && length-->0)
{
if (last!=_state)
{
progress++;
last=_state;
}
ch=_buffer.get();
if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED)
@ -425,7 +435,7 @@ public class HttpParser implements Parser
_state=STATE_END;
_handler.headerComplete();
_handler.messageComplete(_contentPosition);
return total_filled;
return 1;
}
break;
@ -453,7 +463,7 @@ public class HttpParser implements Parser
_state=STATE_END;
_handler.headerComplete();
_handler.messageComplete(_contentPosition);
return total_filled;
return 1;
}
}
break;
@ -601,7 +611,7 @@ public class HttpParser implements Parser
_handler.headerComplete(); // May recurse here !
break;
}
return total_filled;
return 1;
}
else
{
@ -765,8 +775,15 @@ public class HttpParser implements Parser
// Handle _content
length=_buffer.length();
Buffer chunk;
last=_state;
while (_state > STATE_END && length > 0)
{
if (last!=_state)
{
progress++;
last=_state;
}
if (_eol == HttpTokens.CARRIAGE_RETURN && _buffer.peek() == HttpTokens.LINE_FEED)
{
_eol=_buffer.get();
@ -782,7 +799,7 @@ public class HttpParser implements Parser
_contentView.update(chunk);
_handler.content(chunk); // May recurse here
// TODO adjust the _buffer to keep unconsumed content
return total_filled;
return 1;
case STATE_CONTENT:
{
@ -791,7 +808,7 @@ public class HttpParser implements Parser
{
_state=STATE_END;
_handler.messageComplete(_contentPosition);
return total_filled;
return 1;
}
if (length > remaining)
@ -812,7 +829,7 @@ public class HttpParser implements Parser
_handler.messageComplete(_contentPosition);
}
// TODO adjust the _buffer to keep unconsumed content
return total_filled;
return 1;
}
case STATE_CHUNKED_CONTENT:
@ -844,7 +861,7 @@ public class HttpParser implements Parser
_eol=_buffer.get();
_state=STATE_END;
_handler.messageComplete(_contentPosition);
return total_filled;
return 1;
}
else
_state=STATE_CHUNK;
@ -874,7 +891,7 @@ public class HttpParser implements Parser
_eol=_buffer.get();
_state=STATE_END;
_handler.messageComplete(_contentPosition);
return total_filled;
return 1;
}
else
_state=STATE_CHUNK;
@ -898,13 +915,14 @@ public class HttpParser implements Parser
_contentView.update(chunk);
_handler.content(chunk); // May recurse here
// TODO adjust the _buffer to keep unconsumed content
return total_filled;
return 1;
}
}
length=_buffer.length();
}
return total_filled;
return progress;
}
/* ------------------------------------------------------------------------------- */

View File

@ -17,17 +17,18 @@ import java.io.IOException;
/**
* Abstract interface for a connection Parser for use by Jetty.
*
*
*/
public interface Parser
{
void reset(boolean returnBuffers);
boolean isComplete();
long parseAvailable() throws IOException;
/**
* @return An indication of progress, typically the number of bytes filled plus the events parsed: -1 means EOF read, 0 no progress, >0 progress
* @throws IOException
*/
int parseAvailable() throws IOException;
boolean isMoreInBuffer() throws IOException;

View File

@ -69,6 +69,8 @@ public class HttpGeneratorTest
Handler handler = new Handler();
HttpParser parser=null;
// For HTTP version
for (int v=9;v<=11;v++)
{
@ -81,8 +83,7 @@ public class HttpGeneratorTest
// For none, keep-alive, close
for (int c=0;c<(v==11?connect.length:(connect.length-1));c++)
{
String t="v="+v+",r="+r+",chunks="+chunks+",connect="+connect[c]+",tr="+tr[r];
String t="v="+v+",r="+r+",chunks="+chunks+",connect="+c+",tr="+tr[r];
// System.err.println(t);
hb.reset(true);
@ -91,39 +92,45 @@ public class HttpGeneratorTest
tr[r].build(v,hb,"OK\r\nTest",connect[c],null,chunks, fields);
String response=endp.getOut().toString();
// System.out.println("RESPONSE: "+t+"\n"+response+(hb.isPersistent()?"...\n":"---\n"));
//System.out.println("RESPONSE: "+t+"\n"+response+(hb.isPersistent()?"...\n":"---\n"));
if (v==9)
{
assertFalse(t,hb.isPersistent());
if (tr[r].body!=null)
assertEquals(t,tr[r].body, response);
if (tr[r]._body!=null)
assertEquals(t,tr[r]._body, response);
continue;
}
parser=new HttpParser(new ByteArrayBuffer(response.getBytes()), handler);
parser.setHeadResponse(tr[r]._head);
try
{
parser.parse();
}
catch(IOException e)
{
if (tr[r].body!=null)
if (tr[r]._body!=null)
throw new Exception(t,e);
continue;
}
if (tr[r].body!=null)
assertEquals(t,tr[r].body, this.content);
if (tr[r]._body!=null)
assertEquals(t,tr[r]._body, this.content);
if (v==10)
assertTrue(t,hb.isPersistent() || tr[r].values[1]==null || c==2 || c==0);
assertTrue(t,hb.isPersistent() || tr[r]._contentLength==null || c==2 || c==0);
else
assertTrue(t,hb.isPersistent() || c==2 || c==3);
if (v>9)
assertEquals("OK Test",f2);
assertTrue(t,tr[r].values[1]==null || content.length()==Integer.parseInt(tr[r].values[1]));
if (content==null)
assertTrue(t,tr[r]._body==null);
else
assertTrue(t,tr[r]._contentLength==null || content.length()==Integer.parseInt(tr[r]._contentLength));
}
}
}
@ -133,37 +140,48 @@ public class HttpGeneratorTest
private static final String[] headers= { "Content-Type","Content-Length","Connection","Transfer-Encoding","Other"};
private class TR
{
private int code;
private String[] values=new String[headers.length];
private String body;
private int _code;
private String _body;
private boolean _head;
String _contentType;
String _contentLength;
String _connection;
String _te;
String _other;
private TR(int code,String ct, String cl ,String content)
private TR(int code,String contentType, String contentLength ,String content,boolean head)
{
this.code=code;
values[0]=ct;
values[1]=cl;
values[4]="value";
this.body=content;
_code=code;
_contentType=contentType;
_contentLength=contentLength;
_other="value";
_body=content;
_head=head;
}
private void build(int version,HttpGenerator hb,String reason, String connection, String te, int chunks, HttpFields fields) throws Exception
{
values[2]=connection;
values[3]=te;
_connection=connection;
_te=te;
hb.setVersion(version);
hb.setResponse(code,reason);
for (int i=0;i<headers.length;i++)
hb.setResponse(_code,reason);
hb.setHead(_head);
if (_contentType!=null)
fields.put(new ByteArrayBuffer("Content-Type"),new ByteArrayBuffer(_contentType));
if (_contentLength!=null)
fields.put(new ByteArrayBuffer("Content-Length"),new ByteArrayBuffer(_contentLength));
if (_connection!=null)
fields.put(new ByteArrayBuffer("Connection"),new ByteArrayBuffer(_connection));
if (_te!=null)
fields.put(new ByteArrayBuffer("Transfer-Encoding"),new ByteArrayBuffer(_te));
if (_other!=null)
fields.put(new ByteArrayBuffer("Other"),new ByteArrayBuffer(_other));
if (_body!=null)
{
if (values[i]==null)
continue;
fields.put(new ByteArrayBuffer(headers[i]),new ByteArrayBuffer(values[i]));
}
if (body!=null)
{
int inc=1+body.length()/chunks;
Buffer buf=new ByteArrayBuffer(body);
int inc=1+_body.length()/chunks;
Buffer buf=new ByteArrayBuffer(_body);
View view = new View(buf);
for (int i=1;i<chunks;i++)
{
@ -198,20 +216,20 @@ public class HttpGeneratorTest
@Override
public String toString()
{
return "["+code+","+values[0]+","+values[1]+","+(body==null?"none":"_content")+"]";
return "["+_code+","+_contentType+","+_contentLength+","+(_body==null?"null":"content")+"]";
}
}
private final TR[] tr =
{
/* 0 */ new TR(200,null,null,null),
/* 1 */ new TR(200,null,null,CONTENT),
/* 2 */ new TR(200,null,""+CONTENT.length(),null),
/* 3 */ new TR(200,null,""+CONTENT.length(),CONTENT),
/* 4 */ new TR(200,"text/html",null,null),
/* 5 */ new TR(200,"text/html",null,CONTENT),
/* 6 */ new TR(200,"text/html",""+CONTENT.length(),null),
/* 7 */ new TR(200,"text/html",""+CONTENT.length(),CONTENT),
/* 0 */ new TR(200,null,null,null,false),
/* 1 */ new TR(200,null,null,CONTENT,false),
/* 2 */ new TR(200,null,""+CONTENT.length(),null,true),
/* 3 */ new TR(200,null,""+CONTENT.length(),CONTENT,false),
/* 4 */ new TR(200,"text/html",null,null,true),
/* 5 */ new TR(200,"text/html",null,CONTENT,false),
/* 6 */ new TR(200,"text/html",""+CONTENT.length(),null,true),
/* 7 */ new TR(200,"text/html",""+CONTENT.length(),CONTENT,false),
};
private String content;

View File

@ -121,6 +121,7 @@ public class ByteArrayEndPoint implements ConnectedEndPoint
{
_out = out;
}
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.io.EndPoint#isOpen()
@ -130,6 +131,24 @@ public class ByteArrayEndPoint implements ConnectedEndPoint
return !_closed;
}
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.jetty.io.EndPoint#isInputShutdown()
*/
public boolean isInputShutdown()
{
return _closed;
}
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.jetty.io.EndPoint#isOutputShutdown()
*/
public boolean isOutputShutdown()
{
return _closed;
}
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.io.EndPoint#isBlocking()
@ -157,6 +176,16 @@ public class ByteArrayEndPoint implements ConnectedEndPoint
*/
public void shutdownOutput() throws IOException
{
close();
}
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.io.EndPoint#shutdownInput()
*/
public void shutdownInput() throws IOException
{
close();
}
/* ------------------------------------------------------------ */
@ -176,13 +205,19 @@ public class ByteArrayEndPoint implements ConnectedEndPoint
{
if (_closed)
throw new IOException("CLOSED");
if (_in==null)
return -1;
if (_in.length()<=0)
return _nonBlocking?0:-1;
int len = buffer.put(_in);
_in.skip(len);
return len;
if (_in!=null && _in.length()>0)
{
int len = buffer.put(_in);
_in.skip(len);
return len;
}
if (_in!=null && _in.length()==0 && _nonBlocking)
return 0;
close();
return -1;
}
/* ------------------------------------------------------------ */

View File

@ -26,6 +26,15 @@ public interface EndPoint
* Shutdown any backing output stream associated with the endpoint
*/
void shutdownOutput() throws IOException;
boolean isOutputShutdown();
/**
* Shutdown any backing input stream associated with the endpoint
*/
void shutdownInput() throws IOException;
boolean isInputShutdown();
/**
* Close any backing stream associated with the endpoint

View File

@ -19,6 +19,7 @@ import java.net.InetSocketAddress;
import java.net.Socket;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
/**
*
@ -67,7 +68,21 @@ public class SocketEndPoint extends StreamEndPoint
@Override
public boolean isOpen()
{
return super.isOpen() && _socket!=null && !_socket.isClosed() && !_socket.isInputShutdown() && !_socket.isOutputShutdown();
return super.isOpen() && _socket!=null && !_socket.isClosed();
}
/* ------------------------------------------------------------ */
@Override
public boolean isInputShutdown()
{
return !super.isOpen() || _socket!=null && _socket.isInputShutdown();
}
/* ------------------------------------------------------------ */
@Override
public boolean isOutputShutdown()
{
return !super.isOpen() || _socket!=null && _socket.isOutputShutdown();
}
/* ------------------------------------------------------------ */
@ -80,6 +95,18 @@ public class SocketEndPoint extends StreamEndPoint
if (!_socket.isClosed() && !_socket.isOutputShutdown())
_socket.shutdownOutput();
}
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.jetty.io.bio.StreamEndPoint#shutdownOutput()
*/
@Override
public void shutdownInput() throws IOException
{
if (!_socket.isClosed() && !_socket.isInputShutdown())
_socket.shutdownInput();
}
/* ------------------------------------------------------------ */
/* (non-Javadoc)
@ -190,6 +217,22 @@ public class SocketEndPoint extends StreamEndPoint
_socket.setSoTimeout(timeMs>0?timeMs:0);
super.setMaxIdleTime(timeMs);
}
/* ------------------------------------------------------------ */
@Override
protected void idleExpired() throws IOException
{
try
{
if (!isInputShutdown())
shutdownInput();
}
catch(IOException e)
{
Log.ignore(e);
_socket.close();
}
}
}

View File

@ -17,6 +17,7 @@ package org.eclipse.jetty.io.bio;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketTimeoutException;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.EndPoint;
@ -77,6 +78,20 @@ public class StreamEndPoint implements EndPoint
{
}
public boolean isInputShutdown()
{
return !isOpen();
}
public void shutdownInput() throws IOException
{
}
public boolean isOutputShutdown()
{
return !isOpen();
}
/*
* @see org.eclipse.io.BufferIO#close()
*/
@ -90,6 +105,11 @@ public class StreamEndPoint implements EndPoint
_out=null;
}
protected void idleExpired() throws IOException
{
_in.close();
}
/* (non-Javadoc)
* @see org.eclipse.io.BufferIO#fill(org.eclipse.io.Buffer)
*/
@ -107,7 +127,15 @@ public class StreamEndPoint implements EndPoint
throw new IOException("FULL");
}
return buffer.readFrom(_in,space);
try
{
return buffer.readFrom(_in,space);
}
catch(SocketTimeoutException e)
{
idleExpired();
return -1;
}
}
/* (non-Javadoc)

View File

@ -98,6 +98,19 @@ public class ChannelEndPoint implements EndPoint
return _channel.isOpen();
}
/* (non-Javadoc)
* @see org.eclipse.io.EndPoint#close()
*/
public void shutdownInput() throws IOException
{
if (_channel.isOpen() && _channel instanceof SocketChannel)
{
Socket socket= ((SocketChannel)_channel).socket();
if (!socket.isClosed()&&!socket.isInputShutdown())
socket.shutdownInput();
}
}
/* (non-Javadoc)
* @see org.eclipse.io.EndPoint#close()
*/
@ -110,6 +123,16 @@ public class ChannelEndPoint implements EndPoint
socket.shutdownOutput();
}
}
public boolean isOutputShutdown()
{
return _channel.isOpen() && _socket!=null && _socket.isOutputShutdown();
}
public boolean isInputShutdown()
{
return _channel.isOpen() && _socket!=null && _socket.isInputShutdown();
}
/* (non-Javadoc)
* @see org.eclipse.io.EndPoint#close()
@ -148,8 +171,25 @@ public class ChannelEndPoint implements EndPoint
{
bbuf.position(buffer.putIndex());
len=_channel.read(bbuf);
if (len<0)
_channel.close(); // Don't call this.close() as that may recurse in SSL land and call fill again
if (len<0 && isOpen() && !isInputShutdown())
{
try
{
shutdownInput();
}
catch(IOException e)
{
Log.ignore(e);
try
{
close();
}
catch(IOException e2)
{
Log.ignore(e2);
}
}
}
}
finally
{
@ -318,6 +358,7 @@ public class ChannelEndPoint implements EndPoint
return length;
}
/* ------------------------------------------------------------ */
/**
* @return Returns the channel.
*/

View File

@ -250,12 +250,14 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo
protected void idleExpired()
{
if (_connection instanceof Idleable)
{
((Idleable)_connection).idleExpired();
}
else
{
try
{
close();
shutdownOutput();
}
catch(IOException e)
{
@ -415,8 +417,8 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo
if (getChannel().isOpen())
{
_interestOps =
((!_dispatched || _readBlocked) ? SelectionKey.OP_READ : 0)
| ((!_writable || _writeBlocked) ? SelectionKey.OP_WRITE : 0);
((!_socket.isInputShutdown() && (!_dispatched || _readBlocked)) ? SelectionKey.OP_READ : 0)
| ((!_socket.isOutputShutdown()&& (!_writable || _writeBlocked)) ? SelectionKey.OP_WRITE : 0);
try
{
ops = ((_key!=null && _key.isValid())?_key.interestOps():-1);

View File

@ -172,12 +172,39 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint
{
Log.info(""+_result);
}
/* ------------------------------------------------------------ */
@Override
public boolean isOutputShutdown()
{
return _engine!=null && _engine.isOutboundDone();
}
/* ------------------------------------------------------------ */
@Override
public boolean isInputShutdown()
{
return _engine!=null && _engine.isInboundDone();
}
/* ------------------------------------------------------------ */
@Override
public void shutdownInput() throws IOException
{
// Nothing to do here. If the socket is open, let the SSL close handshake work, else the socket is closed anyway.
}
/* ------------------------------------------------------------ */
@Override
public void shutdownOutput() throws IOException
{
if (_closing)
return;
_closing=true;
// TODO - this really should not be done in a loop here - but with async callbacks.
long end=System.currentTimeMillis()+((SocketChannel)_channel).socket().getSoTimeout();
long end=System.currentTimeMillis()+getMaxIdleTime();
try
{
while (isOpen() && isBufferingOutput()&& System.currentTimeMillis()<end)
@ -185,7 +212,7 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint
flush();
if (isBufferingOutput())
{
Thread.sleep(100); // TODO non blocking
Thread.sleep(50); // TODO non blocking
flush();
}
}
@ -198,7 +225,7 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint
{
flush();
if (isBufferingOutput())
Thread.sleep(100);
Thread.sleep(50);
}
if (_debug) __log.debug(_session+" closing "+_engine.getHandshakeStatus());
@ -273,27 +300,12 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint
}
}
/* ------------------------------------------------------------ */
@Override
public void close() throws IOException
{
try
{
_closing=true;
shutdownOutput();
}
catch(IOException e)
{
Log.ignore(e);
}
finally
{
super.close();
}
super.close();
}
/* ------------------------------------------------------------ */
/** Fill the buffer with unencrypted bytes.
@ -617,6 +629,10 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint
flushed=super.flush(_outNIOBuffer);
if (_debug) __log.debug(_session+" flushed "+flushed+"/"+len);
}
else if (_closing && !_engine.isOutboundDone())
{
_engine.closeOutbound();
}
}
}
@ -626,7 +642,7 @@ public class SslSelectChannelEndPoint extends SelectChannelEndPoint
if (_handshook && !_allowRenegotiate && _channel!=null && _channel.isOpen())
{
Log.warn("SSL renegotiate denied: "+_channel);
close();
super.close();
}
}

View File

@ -395,8 +395,9 @@ public class HttpConnection /* TODO extends AbstractConnection*/ implements Conn
handleRequest();
else if (!_parser.isComplete())
{
long parsed=_parser.parseAvailable();
progress|=parsed>0;
int parsed=_parser.parseAvailable();
if (parsed>0)
progress=true;
}
if (_generator.isCommitted() && !_generator.isComplete())
@ -408,7 +409,11 @@ public class HttpConnection /* TODO extends AbstractConnection*/ implements Conn
{
// If we are not ended then parse available
if (!_parser.isComplete())
progress|=_parser.parseAvailable()>0;
{
int parsed=_parser.parseAvailable();
if (parsed>0)
progress=true;
}
// Do we have more generating to do?
// Loop here because some writes may take multiple steps and
@ -453,7 +458,7 @@ public class HttpConnection /* TODO extends AbstractConnection*/ implements Conn
finally
{
more_in_buffer = _parser.isMoreInBuffer() || _endp.isBufferingInput();
// Is this request/response round complete?
if (_parser.isComplete() && _generator.isComplete() && !_endp.isBufferingOutput())
{
@ -471,7 +476,7 @@ public class HttpConnection /* TODO extends AbstractConnection*/ implements Conn
else
{
// No switch, so cleanup and reset
if (!_generator.isPersistent())
if (!_generator.isPersistent() || _endp.isInputShutdown())
{
_parser.reset(true);
more_in_buffer=false;
@ -488,6 +493,11 @@ public class HttpConnection /* TODO extends AbstractConnection*/ implements Conn
progress=true;
}
}
else if (_parser.isIdle() && _endp.isInputShutdown())
{
more_in_buffer=false;
_endp.close();
}
if (_request.isAsyncStarted())
{

View File

@ -622,6 +622,12 @@ public class SslSocketConnector extends SocketConnector implements SslConnector
close();
}
@Override
public void shutdownInput() throws IOException
{
close();
}
@Override
public void run()
{

View File

@ -125,7 +125,7 @@ public class AbstractConnectorTest
doInit(connections);
sendRequest(1, 1);
doClose(connections);
assertEquals(connections, _connector.getConnections());
@ -216,12 +216,6 @@ public class AbstractConnectorTest
{
for (int idx=0; idx < count; idx++)
{
if (_out[idx] != null)
_out[idx].close();
if (_in[idx] != null)
_in[idx].close();
if (_socket[idx] != null)
_socket[idx].close();
}

View File

@ -301,10 +301,9 @@ public class RFC2616Test
offset=0;
response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"\n"+
"GET /R2 HTTP/1.1\n"+"Host: localhost\n"+"Connection: close\n"+"\n"+
"GET /R3 HTTP/1.1\n"+"Host: localhost\n"+"Connection: close\n"+"\n");
offset=checkContains(response,offset,"HTTP/1.1 200 OK\015\012","8.1.2 default")+1;
offset=checkContains(response,offset,"/R1","8.1.2 default")+1;
@ -322,82 +321,84 @@ public class RFC2616Test
}
@Test
public void test8_2()
public void test8_2() throws Exception
{
try
{
String response;
int offset=0;
// No Expect 100
offset=0;
response=connector.getResponses("GET /R1 HTTP/1.1\n"+
"Host: localhost\n"+
"Content-Type: text/plain\n"+
"Content-Length: 8\n"+
"\n",true);
String response;
int offset=0;
// No Expect 100
offset=0;
response=connector.getResponses("GET /R1 HTTP/1.1\n"+
"Host: localhost\n"+
"Content-Type: text/plain\n"+
"Content-Length: 8\n"+
"\n",true);
assertTrue("8.2.3 no expect 100",response==null || response.length()==0);
response=connector.getResponses("GET /R1 HTTP/1.1\n"+
"Host: localhost\n"+
"Content-Type: text/plain\n"+
"Content-Length: 8\n"+
"\n"+
"AbCdEf\015\012",true);
offset=checkContains(response,offset,"HTTP/1.1 200","8.2.3 no expect 100")+1;
offset=checkContains(response,offset,"AbCdEf","8.2.3 no expect 100")+1;
assertTrue("8.2.3 no expect 100",response==null || response.length()==0);
response=connector.getResponses("GET /R1 HTTP/1.1\n"+
"Host: localhost\n"+
"Content-Type: text/plain\n"+
"Content-Length: 8\n"+
"\n"+
"AbCdEf\015\012",true);
offset=checkContains(response,offset,"HTTP/1.1 200","8.2.3 no expect 100")+1;
offset=checkContains(response,offset,"AbCdEf","8.2.3 no expect 100")+1;
// Expect Failure
offset=0;
response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"Expect: unknown\n"+"Content-Type: text/plain\n"+"Content-Length: 8\n"
+"\n");
offset=checkContains(response,offset,"HTTP/1.1 417","8.2.3 expect failure")+1;
// Expect Failure
offset=0;
response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"Expect: unknown\n"+"Content-Type: text/plain\n"+"Content-Length: 8\n"
+"\n");
offset=checkContains(response,offset,"HTTP/1.1 417","8.2.3 expect failure")+1;
// Expect with body
offset=0;
response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"Expect: 100-continue\n"+"Content-Type: text/plain\n"
+"Content-Length: 8\n"+"Connection: close\n"+"\n"+"123456\015\012");
checkNotContained(response,offset,"HTTP/1.1 100 ","8.2.3 expect 100");
offset=checkContains(response,offset,"HTTP/1.1 200 OK","8.2.3 expect with body")+1;
// Expect with body
offset=0;
response=connector.getResponses("GET /R1 HTTP/1.1\n"+"Host: localhost\n"+"Expect: 100-continue\n"+"Content-Type: text/plain\n"
+"Content-Length: 8\n"+"Connection: close\n"+"\n"+"123456\015\012");
checkNotContained(response,offset,"HTTP/1.1 100 ","8.2.3 expect 100");
offset=checkContains(response,offset,"HTTP/1.1 200 OK","8.2.3 expect with body")+1;
}
// Expect 100
((StdErrLog)Log.getLog()).setHideStacks(true);
offset=0;
response=connector.getResponses("GET /R1 HTTP/1.1\n"+
"Host: localhost\n"+
"Connection: close\n"+
"Expect: 100-continue\n"+
"Content-Type: text/plain\n"+
"Content-Length: 8\n"+
"\n",true);
offset=checkContains(response,offset,"HTTP/1.1 100 ","8.2.3 expect 100")+1;
checkNotContained(response,offset,"HTTP/1.1 200","8.2.3 expect 100");
/* can't test this with localconnector.
@Test
public void test8_2_3() throws Exception
{
String response;
int offset=0;
// Expect 100
response=connector.getResponses("GET /R1 HTTP/1.1\n"+
"Host: localhost\n"+
"Connection: close\n"+
"Expect: 100-continue\n"+
"Content-Type: text/plain\n"+
"Content-Length: 8\n"+
"\n",true);
offset=checkContains(response,offset,"HTTP/1.1 100 ","8.2.3 expect 100")+1;
checkNotContained(response,offset,"HTTP/1.1 200","8.2.3 expect 100");
/* can't test this with localconnector.
response=connector.getResponses("654321\015\012");
offset=checkContains(response,offset,"HTTP/1.1 200","8.2.3 expect 100")+1;
offset=checkContains(response,offset,"654321","8.2.3 expect 100")+1;
*/
*/
}
@Test
public void test8_2_4() throws Exception
{
String response;
int offset=0;
// Expect 100 not sent
((StdErrLog)Log.getLog()).setHideStacks(true);
offset=0;
// Expect 100 not sent
((StdErrLog)Log.getLog()).setHideStacks(true);
offset=0;
response=connector.getResponses("GET /R1?error=401 HTTP/1.1\n"+
"Host: localhost\n"+
"Expect: 100-continue\n"+
"Content-Type: text/plain\n"+
"Content-Length: 8\n"+
"\n",true);
checkNotContained(response,offset,"HTTP/1.1 100","8.2.3 expect 100");
offset=checkContains(response,offset,"HTTP/1.1 401 ","8.2.3 expect 100")+1;
offset=checkContains(response,offset,"Connection: close","8.2.3 expect 100")+1;
response=connector.getResponses("GET /R1?error=401 HTTP/1.1\n"+
"Host: localhost\n"+
"Expect: 100-continue\n"+
"Content-Type: text/plain\n"+
"Content-Length: 8\n"+
"\n",true);
checkNotContained(response,offset,"HTTP/1.1 100","8.2.3 expect 100");
offset=checkContains(response,offset,"HTTP/1.1 401 ","8.2.3 expect 100")+1;
offset=checkContains(response,offset,"Connection: close","8.2.3 expect 100")+1;
((StdErrLog)Log.getLog()).setHideStacks(false);
}
catch (Exception e)
{
e.printStackTrace();
assertTrue(false);
}
((StdErrLog)Log.getLog()).setHideStacks(false);
}
@Test

View File

@ -247,21 +247,13 @@ public class WebSocketGeneratorD06 implements WebSocketGenerator
private synchronized int flushBuffer() throws IOException
{
try
{
if (!_endp.isOpen())
throw new EofException();
if (!_endp.isOpen())
throw new EofException();
if (_buffer!=null)
return _endp.flush(_buffer);
if (_buffer!=null)
return _endp.flush(_buffer);
return 0;
}
catch(IOException e)
{
System.err.println("FAILED to flush: "+_buffer.toDetailString());
throw e;
}
return 0;
}
private synchronized int expelBuffer(long blockFor) throws IOException

View File

@ -120,7 +120,7 @@ public class WebSocketParserD01 implements WebSocketParser
{
int filled=_endp.isOpen()?_endp.fill(_buffer):-1;
if (filled<=0)
return total_filled;
return total_filled>0?total_filled:-1;
total_filled+=filled;
available=_buffer.length();
}

View File

@ -80,6 +80,7 @@ public class WebSocketParserD06Test
{
WebSocketBuffers buffers = new WebSocketBuffers(1024);
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
endPoint.setNonBlocking(true);
_handler = new Handler();
_parser=new WebSocketParserD06(buffers, endPoint,_handler,true);
_in = new MaskedByteArrayBuffer();
@ -243,6 +244,8 @@ public class WebSocketParserD06Test
@Test
public void testFrameTooLarge() throws Exception
{
// Buffers are only 1024, so this frame is too large
_in.sendMask();
_in.put((byte)0x84);
_in.put((byte)0x7E);