From 3d12ef707558839ad7319706e3752dc6bde99dcc Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 7 Mar 2012 17:26:33 +1100 Subject: [PATCH] jetty-9 work in progress --- .../org/eclipse/jetty/http/HttpFields.java | 9 +- .../org/eclipse/jetty/http/HttpGenerator.java | 1012 +++++++++-------- .../org/eclipse/jetty/http/HttpVersion.java | 12 + .../jetty/http/HttpGeneratorClientTest.java | 685 ++++++----- .../eclipse/jetty/http/HttpGeneratorTest.java | 649 +++++++++-- .../org/eclipse/jetty/util/BufferUtil.java | 30 +- 6 files changed, 1518 insertions(+), 879 deletions(-) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java index 9850370a4eb..4adc35b5b73 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java @@ -1073,13 +1073,12 @@ public class HttpFields implements Iterable } /* ------------------------------------------------------------ */ - public void putTo(ByteBuffer buffer) throws IOException + public void putTo(ByteBuffer buffer) { HttpHeader header = HttpHeader.CACHE.get(_name); if (header!=null) { - buffer.put(header.toBuffer()); - buffer.put(__colon_space); + buffer.put(header.toBytesColonSpace()); if (HttpHeaderValue.hasKnownValues(header)) { @@ -1089,6 +1088,8 @@ public class HttpFields implements Iterable else buffer.put(toSanitisedBytes(_value)); } + else + buffer.put(toSanitisedBytes(_value)); } else { @@ -1101,7 +1102,7 @@ public class HttpFields implements Iterable } /* ------------------------------------------------------------ */ - public void putValueTo(ByteBuffer buffer) throws IOException + public void putValueTo(ByteBuffer buffer) { buffer.put(toSanitisedBytes(_value)); } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java index bd3623bafd0..64c1b77c08b 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java @@ -82,7 +82,7 @@ public class HttpGenerator // states - enum State { START, CHUNKING, STREAMING, COMPLETING, END }; + enum State { START, COMPLETING_UNCOMMITTED, COMMITTING, COMMITTING_COMPLETING, COMMITTED, COMPLETING, END }; enum Result { NEED_COMMIT,NEED_CHUNK,NEED_BUFFER,FLUSH,FLUSH_CONTENT,NEED_COMPLETE,OK,SHUTDOWN_OUT}; public static final byte[] NO_BYTES = {}; @@ -112,8 +112,7 @@ public class HttpGenerator // common _content - private static final byte[] LAST_CHUNK = - { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'}; + private static final byte[] LAST_CHUNK = { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'}; private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012"); private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012"); private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012"); @@ -124,7 +123,7 @@ public class HttpGenerator private static byte[] SERVER = StringUtil.getBytes("Server: Jetty(7.0.x)\015\012"); // other statics - private static final int CHUNK_SPACE = 12; + public static final int CHUNK_SIZE = 12; public static void setServerVersion(String version) { @@ -133,9 +132,6 @@ public class HttpGenerator // data private boolean _needCRLF = false; - private boolean _sentEOC = false; - - /* ------------------------------------------------------------------------------- */ public void reset() @@ -154,7 +150,6 @@ public class HttpGenerator _method=null; _needCRLF = false; - _sentEOC = false; _uri=null; _noContent=false; } @@ -201,6 +196,12 @@ public class HttpGenerator return _state != State.START; } + /* ------------------------------------------------------------ */ + public boolean isChunking() + { + return _contentLength==HttpTokens.CHUNKED_CONTENT; + } + /* ------------------------------------------------------------ */ public int getLargeContent() { @@ -365,446 +366,478 @@ public class HttpGenerator { return _method==null; } - /* ------------------------------------------------------------ */ - public boolean needsHeader() + public Result commit(HttpFields fields,ByteBuffer header,ByteBuffer buffer, ByteBuffer content, boolean last) throws IOException { - return _state==State.START || _contentLength==HttpTokens.CHUNKED_CONTENT; - } - - - /* ------------------------------------------------------------ */ - public Result commit(HttpFields fields,ByteBuffer header,ByteBuffer buffer, ByteBuffer content, boolean last) - throws IOException - { - - if (header==null) - throw new IllegalArgumentException("!header"); if (isResponse() && _status==0) throw new EofException(); // TODO ??? - - if (_state == State.START) + int pos=header.position(); + try { - if (isRequest() && _version==HttpVersion.HTTP_0_9) - _noContent=true; + BufferUtil.flipToFill(header); - boolean has_server = false; - int pos=header.position(); - try + switch(_state) { - BufferUtil.flipToFill(header); - if (isRequest()) - { - _persistent=true; - - if (_version == HttpVersion.HTTP_0_9) + case START: + case COMPLETING_UNCOMMITTED: + + if (isRequest()) { - _contentLength = HttpTokens.NO_CONTENT; - header.put(_method); - header.put((byte)' '); - header.put(_uri); - header.put(HttpTokens.CRLF); - _state = State.END; - _noContent=true; + if(_version==HttpVersion.HTTP_0_9) + { + _noContent=true; + generateRequestLine(header); + _state = State.END; + return Result.OK; + } + _persistent=true; + generateRequestLine(header); + } + else + { + // Responses + if (_version == HttpVersion.HTTP_0_9) + { + _persistent = false; + _contentLength = HttpTokens.EOF_CONTENT; + _state = State.COMMITTED; + return prepareContent(null,buffer,content); + } + + // Are we persistent by default? + if (_persistent==null) + _persistent=(_version.ordinal() > HttpVersion.HTTP_1_0.ordinal()); + + generateResponseLine(header); + + // Handle 1xx + if (_status>=100 && _status<200 ) + { + _noContent=true; + + if (_status!=101 ) + { + header.put(HttpTokens.CRLF); + _state = State.START; + return Result.OK; + } + } + else if (_status==204 || _status==304) + { + _noContent=true; + } + } + + generateHeaders(fields,header,content,last); + + _state = _state==State.COMPLETING_UNCOMMITTED?State.COMMITTING_COMPLETING:State.COMMITTING; + + // fall through to COMMITTING states + + case COMMITTING: + case COMMITTING_COMPLETING: + + // Handle any content + if (BufferUtil.hasContent(content)) + { + // Do we have too much content? + if (_contentLength>0 && content.remaining()>(_contentLength-_contentPrepared)) + { + LOG.warn("Content truncated at {}",new Throwable()); + content.limit(content.position()+(int)(_contentLength-_contentPrepared)); + } + + // Can we do a direct flush + if (BufferUtil.isEmpty(buffer) && content.remaining()>_largeContent) + { + _contentPrepared+=content.remaining(); + if (isChunking()) + prepareChunk(header,content.remaining()); + return Result.FLUSH_CONTENT; + } + + // we copy content to buffer + // if we don't have one, we need one + if (buffer==null) + return Result.NEED_BUFFER; + + _contentPrepared+=BufferUtil.put(content,buffer); + + if (isChunking()) + prepareChunk(header,buffer.remaining()); + return Result.FLUSH; } + _state = _state==State.COMMITTING?State.COMMITTED:State.COMPLETING; + + break; + + default: + throw new IllegalStateException(this.toString()); + + } - header.put(_method); - header.put((byte)' '); - header.put(_uri); - header.put((byte)' '); - header.put((_version==HttpVersion.HTTP_1_0?HttpVersion.HTTP_1_0:HttpVersion.HTTP_1_1).toBytes()); - header.put(HttpTokens.CRLF); + } + catch(BufferOverflowException e) + { + throw new RuntimeException("Header>"+header.capacity(),e); + } + finally + { + BufferUtil.flipToFlush(header,pos); + } + + return _state==State.COMPLETING?Result.NEED_COMPLETE:Result.OK; + } + + /* ------------------------------------------------------------ */ + private void generateRequestLine(ByteBuffer header) + { + header.put(_method); + header.put((byte)' '); + header.put(_uri); + switch(_version) + { + case HTTP_1_0: + case HTTP_1_1: + header.put((byte)' '); + header.put(_version.toBytes()); + } + header.put(HttpTokens.CRLF); + } + + /* ------------------------------------------------------------ */ + private void generateResponseLine(ByteBuffer header) + { + // Look for prepared response line + Status status = _status<__status.length?__status[_status]:null; + if (status!=null) + { + if (_reason==null) + header.put(status._responseLine); + else + { + header.put(status._schemeCode); + header.put(_reason); + header.put(HttpTokens.CRLF); + } + } + else // generate response line + { + header.put(HTTP_1_1_SPACE); + header.put((byte) ('0' + _status / 100)); + header.put((byte) ('0' + (_status % 100) / 10)); + header.put((byte) ('0' + (_status % 10))); + header.put((byte) ' '); + if (_reason==null) + { + header.put((byte) ('0' + _status / 100)); + header.put((byte) ('0' + (_status % 100) / 10)); + header.put((byte) ('0' + (_status % 10))); + } + else + header.put(_reason); + header.put(HttpTokens.CRLF); + } + } + + /* ------------------------------------------------------------ */ + private void generateHeaders(HttpFields fields,ByteBuffer header,ByteBuffer content,boolean last) + { + + // Add Date header + if (_status>=200 && _date!=null) + { + header.put(HttpHeader.DATE.toBytesColonSpace()); + header.put(_date); + header.put(CRLF); + } + + // default field values + boolean has_server = false; + HttpFields.Field transfer_encoding=null; + boolean keep_alive=false; + boolean close=false; + boolean content_type=false; + boolean content_length=false; + StringBuilder connection = null; + + // Generate fields + if (fields != null) + { + for (HttpFields.Field field : fields) + { + HttpHeader name = HttpHeader.CACHE.get(field.getName()); + + switch (name==null?HttpHeader.UNKNOWN:name) + { + case CONTENT_LENGTH: + { + long length = field.getLongValue(); + if (length>=0) + { + if (length < _contentPrepared || last && length != _contentPrepared) + LOG.warn("Incorrect ContentLength ignored ",new Throwable()); + else + { + // write the field to the header + header.put(HttpHeader.CONTENT_LENGTH.toBytesColonSpace()); + BufferUtil.putDecLong(header,length); + BufferUtil.putCRLF(header); + _contentLength=length; + content_length=true; + } + } + break; + } + + case CONTENT_TYPE: + { + if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString())) + _contentLength = HttpTokens.SELF_DEFINING_CONTENT; + + // write the field to the header + content_type=true; + field.putTo(header); + break; + } + + case TRANSFER_ENCODING: + { + if (_version == HttpVersion.HTTP_1_1) + transfer_encoding = field; + // Do NOT add yet! + break; + } + + case CONNECTION: + { + if (isRequest()) + field.putTo(header); + + // Lookup and/or split connection value field + HttpHeaderValue[] values = new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())}; + String[] split = null; + + if (values[0]==null) + { + split = field.getValue().split("\\s*,\\s*"); + if (split.length>0) + { + values=new HttpHeaderValue[split.length]; + for (int i=0;i0 || content_type ) && !_noContent) + { + // known length but not actually set. + header.put(HttpHeader.CONTENT_LENGTH.toBytesColonSpace()); + BufferUtil.putDecLong(header, _contentLength); + header.put(HttpTokens.CRLF); + } } else { - // Responses - if (_version == HttpVersion.HTTP_0_9) - { - _persistent = false; - _contentLength = HttpTokens.EOF_CONTENT; - _state = State.STREAMING; - return prepareContent(null,buffer,content); - } - - // Are we persistent by default? - if (_persistent==null) - _persistent=(_version.ordinal() > HttpVersion.HTTP_1_0.ordinal()); - - // Look for prepared response line - Status status = _status<__status.length?__status[_status]:null; - if (status!=null) - { - if (_reason==null) - header.put(status._responseLine); - else - { - header.put(status._schemeCode); - header.put(_reason); - header.put(HttpTokens.CRLF); - } - } - else // generate response line - { - header.put(HTTP_1_1_SPACE); - header.put((byte) ('0' + _status / 100)); - header.put((byte) ('0' + (_status % 100) / 10)); - header.put((byte) ('0' + (_status % 10))); - header.put((byte) ' '); - if (_reason==null) - { - header.put((byte) ('0' + _status / 100)); - header.put((byte) ('0' + (_status % 100) / 10)); - header.put((byte) ('0' + (_status % 10))); - } - else - header.put(_reason); - header.put(HttpTokens.CRLF); - } - - // Handle 1xx - if (_status>=100 && _status<200 ) - { - _noContent=true; - - if (_status!=101 ) - { - header.put(HttpTokens.CRLF); - _state = State.START; - return Result.FLUSH; - } - } - else if (_status==204 || _status==304) + // No idea, so we must assume that a body is coming + _contentLength = (!_persistent || _version.ordinal() < HttpVersion.HTTP_1_1.ordinal() ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT; + if (isRequest() && _contentLength==HttpTokens.EOF_CONTENT) { + _contentLength=HttpTokens.NO_CONTENT; _noContent=true; } } + break; - // Add Date header - if (_status>=200 && _date!=null) + case HttpTokens.NO_CONTENT: + if (!content_length && isResponse() && _status >= 200 && _status != 204 && _status != 304) + header.put(CONTENT_LENGTH_0); + break; + + case HttpTokens.EOF_CONTENT: + _persistent = isRequest(); + break; + + case HttpTokens.CHUNKED_CONTENT: + break; + + default: + // TODO - maybe allow forced chunking by setting te ??? + break; + } + + // Add transfer_encoding if needed + if (isChunking()) + { + // try to use user supplied encoding as it may have other values. + if (transfer_encoding != null && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue())) + { + String c = transfer_encoding.getValue(); + if (c.endsWith(HttpHeaderValue.CHUNKED.toString())) + transfer_encoding.putTo(header); + else + throw new IllegalArgumentException("BAD TE"); + } + else + header.put(TRANSFER_ENCODING_CHUNKED); + } + + // Handle connection if need be + if (_contentLength==HttpTokens.EOF_CONTENT) + { + keep_alive=false; + _persistent=false; + } + + // If this is a response, work out persistence + if (isResponse()) + { + if (!_persistent && (close || _version.ordinal() > HttpVersion.HTTP_1_0.ordinal())) + { + if (connection==null) + header.put(CONNECTION_CLOSE); + else { - header.put(HttpHeader.DATE.toBytesColonSpace()); - header.put(_date); + header.put(CONNECTION_CLOSE,0,CONNECTION_CLOSE.length-2); + header.put((byte)','); + header.put(StringUtil.getBytes(connection.toString())); header.put(CRLF); } - - // default field values - HttpFields.Field transfer_encoding=null; - boolean keep_alive=false; - boolean close=false; - boolean content_type=false; - boolean content_length=false; - StringBuilder connection = null; - - // Generate fields - if (fields != null) - { - for (HttpFields.Field field : fields) - { - HttpHeader name = HttpHeader.CACHE.get(field.getName()); - - switch (name==null?HttpHeader.UNKNOWN:name) - { - case CONTENT_LENGTH: - { - long length = field.getLongValue(); - if (length>=0) - { - if (length < _contentPrepared || last && length != _contentPrepared) - LOG.warn("Incorrect ContentLength ignored ",new Throwable()); - else - { - // write the field to the header - field.putTo(header); - _contentLength=length; - content_length=true; - } - } - break; - } - - case CONTENT_TYPE: - { - if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString())) - _contentLength = HttpTokens.SELF_DEFINING_CONTENT; - - // write the field to the header - content_type=true; - field.putTo(header); - break; - } - - case TRANSFER_ENCODING: - { - if (_version == HttpVersion.HTTP_1_1) - transfer_encoding = field; - // Do NOT add yet! - break; - } - - case CONNECTION: - { - if (isRequest()) - field.putTo(header); - - // Lookup and/or split connection value field - HttpHeaderValue[] values = new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())}; - String[] split = null; - - if (values[0]==null) - { - split = field.getValue().split("\\s*,\\s*"); - if (split.length>0) - { - values=new HttpHeaderValue[split.length]; - for (int i=0;i0 || content_type ) && !_noContent) - { - // known length but not actually set. - header.put(HttpHeader.CONTENT_LENGTH.toBytesColonSpace()); - BufferUtil.putDecLong(header, _contentLength); - header.put(HttpTokens.CRLF); - } - } - else - { - // No idea, so we must assume that a body is coming - _contentLength = (!_persistent || _version.ordinal() < HttpVersion.HTTP_1_1.ordinal() ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT; - if (isRequest() && _contentLength==HttpTokens.EOF_CONTENT) - { - _contentLength=HttpTokens.NO_CONTENT; - _noContent=true; - } - } - break; - - case HttpTokens.NO_CONTENT: - if (!content_length && isResponse() && _status >= 200 && _status != 204 && _status != 304) - header.put(CONTENT_LENGTH_0); - break; - - case HttpTokens.EOF_CONTENT: - _persistent = isRequest(); - break; - - case HttpTokens.CHUNKED_CONTENT: - break; - - default: - // TODO - maybe allow forced chunking by setting te ??? - break; - } - - // Add transfer_encoding if needed - if (_contentLength == HttpTokens.CHUNKED_CONTENT) - { - // try to use user supplied encoding as it may have other values. - if (transfer_encoding != null && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue())) - { - String c = transfer_encoding.getValue(); - if (c.endsWith(HttpHeaderValue.CHUNKED.toString())) - transfer_encoding.putTo(header); - else - throw new IllegalArgumentException("BAD TE"); - } - else - header.put(TRANSFER_ENCODING_CHUNKED); - } - - // Handle connection if need be - if (_contentLength==HttpTokens.EOF_CONTENT) - { - keep_alive=false; - _persistent=false; - } - - if (isResponse()) - { - if (!_persistent && (close || _version.ordinal() > HttpVersion.HTTP_1_0.ordinal())) - { - if (connection==null) - header.put(CONNECTION_CLOSE); - else - { - header.put(CONNECTION_CLOSE,0,CONNECTION_CLOSE.length-2); - header.put((byte)','); - header.put(StringUtil.getBytes(connection.toString())); - header.put(CRLF); - } - } - else if (keep_alive) - { - if (connection==null) - header.put(CONNECTION_KEEP_ALIVE); - else - { - header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_CLOSE.length-2); - header.put((byte)','); - header.put(StringUtil.getBytes(connection.toString())); - header.put(CRLF); - } - } - else if (connection!=null) - { - header.put(CONNECTION_); - header.put(StringUtil.getBytes(connection.toString())); - header.put(CRLF); - } - } - - if (!has_server && _status>199 && getSendServerVersion()) - header.put(SERVER); - - // end the header. - header.put(HttpTokens.CRLF); - _state = _contentLength==HttpTokens.CHUNKED_CONTENT?State.CHUNKING:State.STREAMING; - } - catch(BufferOverflowException e) + else if (keep_alive) { - throw new RuntimeException("Header>"+header.capacity(),e); + if (connection==null) + header.put(CONNECTION_KEEP_ALIVE); + else + { + header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_CLOSE.length-2); + header.put((byte)','); + header.put(StringUtil.getBytes(connection.toString())); + header.put(CRLF); + } } - finally + else if (connection!=null) { - BufferUtil.flipToFlush(header,pos); + header.put(CONNECTION_); + header.put(StringUtil.getBytes(connection.toString())); + header.put(CRLF); } } - // Handle the content - if (BufferUtil.hasContent(content)) - { - // Can we do a direct flush - if (BufferUtil.isEmpty(buffer) && content.remaining()>_largeContent) - { - _contentPrepared+=content.remaining(); - if (_state==State.CHUNKING) - prepareChunk(header,content.remaining()); - return Result.FLUSH_CONTENT; - } + if (!has_server && _status>199 && getSendServerVersion()) + header.put(SERVER); - // we copy content to buffer - // if we don't have one, we need one - if (buffer==null) - return Result.NEED_BUFFER; + // end the header. + header.put(HttpTokens.CRLF); - _contentPrepared+=BufferUtil.put(content,buffer); - - if (_state==State.CHUNKING) - prepareChunk(header,buffer.remaining()); - } - - if (BufferUtil.hasContent(buffer)) - { - if (last && BufferUtil.isEmpty(content) || _contentLength>0&&_contentLength==_contentPrepared) - return Result.NEED_COMPLETE; - - return Result.FLUSH; - } - return Result.OK; } /* ------------------------------------------------------------ */ public Result prepareContent(ByteBuffer chunk, ByteBuffer buffer, ByteBuffer content) { + // Do we have too much content? + if (_contentLength>0 && content.remaining()>(_contentLength-_contentPrepared)) + { + LOG.warn("Content truncated at {}",new Throwable()); + content.limit(content.position()+(int)(_contentLength-_contentPrepared)); + } + switch (_state) { case START: @@ -817,33 +850,30 @@ public class HttpGenerator if (buffer==null) return Result.NEED_BUFFER; - // are we limited by content length? - if (_contentLength>0) - { - _contentPrepared+=BufferUtil.put(content,buffer,_contentLength-_contentPrepared); - if (_contentPrepared==_contentLength) - return Result.NEED_COMMIT; - } - else - _contentPrepared+=BufferUtil.put(content,buffer); + // copy content to buffer + _contentPrepared+=BufferUtil.put(content,buffer); // are we full? if (BufferUtil.isAtCapacity(buffer)) return Result.NEED_COMMIT; return Result.OK; + + case COMPLETING: + return Result.NEED_COMPLETE; + + case COMMITTED: - case STREAMING: // Can we do a direct flush if (BufferUtil.isEmpty(buffer) && content.remaining()>_largeContent) { - if (_contentLength>0) + if (isChunking()) { - long total=_contentPrepared+content.remaining(); - if (total>_contentLength) - throw new IllegalStateException(); - if (total==_contentLength) - return Result.NEED_COMPLETE; + if (chunk==null) + return Result.NEED_CHUNK; + BufferUtil.clearToFill(chunk); + prepareChunk(chunk,content.remaining()); + BufferUtil.flipToFlush(chunk,0); } _contentPrepared+=content.remaining(); return Result.FLUSH_CONTENT; @@ -854,53 +884,24 @@ public class HttpGenerator if (buffer==null) return Result.NEED_BUFFER; - // are we limited by content length? - if (_contentLength>0) - { - _contentPrepared+=BufferUtil.put(content,buffer,_contentLength-_contentPrepared); - if (_contentPrepared==_contentLength) - return Result.NEED_COMPLETE; - } - else - _contentPrepared+=BufferUtil.put(content,buffer); - - // are we full? - if (BufferUtil.isAtCapacity(buffer)) - return Result.FLUSH; - - return Result.OK; - - - case CHUNKING: - { - if (chunk==null) - return Result.NEED_CHUNK; - - // Can we do a direct flush - if (BufferUtil.isEmpty(buffer) && content.remaining()>_largeContent) - { - _contentPrepared+=content.remaining(); - BufferUtil.clear(chunk); - prepareChunk(chunk,content.remaining()); - return Result.FLUSH_CONTENT; - } - // we copy content to buffer - // if we don't have one, we need one - if (buffer==null) - return Result.NEED_BUFFER; - + // Copy the content _contentPrepared+=BufferUtil.put(content,buffer); // are we full? if (BufferUtil.isAtCapacity(buffer)) { - BufferUtil.clear(chunk); - prepareChunk(chunk,buffer.remaining()); + if (isChunking()) + { + if (chunk==null) + return Result.NEED_CHUNK; + BufferUtil.clearToFill(chunk); + prepareChunk(chunk,buffer.remaining()); + BufferUtil.flipToFlush(chunk,0); + } return Result.FLUSH; } - return Result.OK; - } + default: throw new IllegalStateException(); } @@ -914,17 +915,21 @@ public class HttpGenerator BufferUtil.putCRLF(chunk); // Add the chunk size to the header - BufferUtil.putHexInt(chunk, remaining); - BufferUtil.putCRLF(chunk); - - // Need a CRLF after the content - _needCRLF=remaining>0; + if (remaining>0) + { + BufferUtil.putHexInt(chunk, remaining); + BufferUtil.putCRLF(chunk); + _needCRLF=true; + } + else + { + chunk.put(LAST_CHUNK); + _needCRLF=false; + } } /* ------------------------------------------------------------ */ /** - * Complete the message. - * * @throws IOException */ public Result flush(ByteBuffer chunk, ByteBuffer buffer) throws IOException @@ -933,14 +938,18 @@ public class HttpGenerator { case START: return Result.NEED_COMMIT; - case CHUNKING: - if (chunk==null) - return Result.NEED_CHUNK; - - if (BufferUtil.hasContent(buffer)) + case COMMITTED: + if (isChunking()) { - BufferUtil.clear(chunk); - prepareChunk(chunk,buffer.remaining()); + if (chunk==null) + return Result.NEED_CHUNK; + + if (BufferUtil.hasContent(buffer)) + { + BufferUtil.clearToFill(chunk); + prepareChunk(chunk,buffer.remaining()); + BufferUtil.flipToFlush(chunk,0); + } } } return Result.FLUSH; @@ -960,38 +969,35 @@ public class HttpGenerator switch(_state) { case START: + case COMPLETING_UNCOMMITTED: + _state=State.COMPLETING_UNCOMMITTED; return Result.NEED_COMMIT; - - case CHUNKING: - if (chunk==null) - return Result.NEED_CHUNK; - if (BufferUtil.hasContent(buffer)) - { - _state=State.COMPLETING; - BufferUtil.clear(chunk); - prepareChunk(chunk,buffer.remaining()); - return Result.FLUSH; - } - - if (!_sentEOC) + case COMPLETING: + case COMMITTED: + _state=State.COMPLETING; + if (isChunking()) { + if (BufferUtil.hasContent(buffer)) + { + if (chunk==null) + return Result.NEED_CHUNK; + BufferUtil.clearToFill(chunk); + prepareChunk(chunk,buffer.remaining()); + BufferUtil.flipToFlush(chunk,0); + return Result.FLUSH; + } _state=State.END; + BufferUtil.clearToFill(chunk); prepareChunk(chunk,0); - _sentEOC=true; + BufferUtil.flipToFlush(chunk,0); return Result.FLUSH; } - return Result.OK; - - case STREAMING: - if (BufferUtil.hasContent(buffer)) - { - _state=State.COMPLETING; + else if (BufferUtil.hasContent(buffer)) return Result.FLUSH; - } - _state=State.END; - return Result.OK; + } + _state=State.END; return Result.OK; } @@ -1004,11 +1010,11 @@ public class HttpGenerator return null; } - + /* ------------------------------------------------------------------------------- */ @Override public String toString() { - return String.format("%s{s=%d}", + return String.format("%s{s=%s}", getClass().getSimpleName(), _state); } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java index 4a809deea45..4481bc40db0 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpVersion.java @@ -72,4 +72,16 @@ public enum HttpVersion { return _string; } + + /* ------------------------------------------------------------ */ + public static HttpVersion fromVersion(int version) + { + switch(version) + { + case 9: return HttpVersion.HTTP_0_9; + case 10: return HttpVersion.HTTP_1_0; + case 11: return HttpVersion.HTTP_1_1; + default: throw new IllegalArgumentException(); + } + } } diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorClientTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorClientTest.java index 71f9a70d99b..9e90573f3e1 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorClientTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorClientTest.java @@ -13,8 +13,11 @@ package org.eclipse.jetty.http; +import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.junit.matchers.JUnitMatchers.containsString; import java.io.IOException; import java.nio.ByteBuffer; @@ -23,332 +26,444 @@ import javax.swing.text.View; import org.eclipse.jetty.io.ByteArrayEndPoint; import org.eclipse.jetty.io.SimpleBuffers; +import org.eclipse.jetty.util.BufferUtil; +import org.hamcrest.CoreMatchers; +import org.junit.Assert; import org.junit.Test; +import org.junit.matchers.JUnitMatchers; public class HttpGeneratorClientTest { public final static String CONTENT="The quick brown fox jumped over the lazy dog.\nNow is the time for all good men to come to the aid of the party\nThe moon is blue to a fish in love.\n"; public final static String[] connect={null,"keep-alive","close"}; + @Test - public void testContentLength() throws Exception + public void testRequestNoContent() throws Exception { - ByteBuffer bb=new ByteArrayBuffer(8096); - ByteBuffer sb=new ByteArrayBuffer(1500); - ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); - HttpGenerator generator = new HttpGenerator(new SimpleBuffers(sb,bb),endp); - - generator.setRequest("GET","/usr"); - + ByteBuffer header=BufferUtil.allocate(8096); HttpFields fields = new HttpFields(); - fields.add("Header","Value"); - fields.add("Content-Type","text/plain"); + HttpGenerator gen = new HttpGenerator(); - String content = "The quick brown fox jumped over the lazy dog"; - fields.addLongField("Content-Length",content.length()); + fields.add("Host","something"); + fields.add("User-Agent","test"); - generator.completeHeader(fields,false); + gen.setRequest(HttpMethod.GET,"/index.html",HttpVersion.HTTP_1_1); + + HttpGenerator.Result + result=gen.complete(null,null); + assertEquals(HttpGenerator.State.COMPLETING_UNCOMMITTED,gen.getState()); + assertEquals(HttpGenerator.Result.NEED_COMMIT,result); + + result=gen.commit(fields,header,null,null,true); + assertEquals(HttpGenerator.Result.NEED_COMPLETE,result); + String out = BufferUtil.toString(header); + BufferUtil.clear(header); + assertThat(out,containsString("GET /index.html HTTP/1.1")); + assertThat(out,not(containsString("Content-Length"))); + + result=gen.complete(null,null); + assertEquals(HttpGenerator.Result.OK,result); + + + assertEquals(HttpGenerator.State.END,gen.getState()); + assertEquals(0,gen.getContentWritten()); } + + @Test + public void testRequestWithSmallContent() throws Exception + { + ByteBuffer header=BufferUtil.allocate(4096); + ByteBuffer buffer=BufferUtil.allocate(8096); + ByteBuffer content=BufferUtil.toBuffer("Hello World"); + ByteBuffer content1=BufferUtil.toBuffer(". The quick brown fox jumped over the lazy dog."); + HttpFields fields = new HttpFields(); + HttpGenerator gen = new HttpGenerator(); - generator.addContent(new ByteArrayBuffer(content),true); - generator.flushBuffer(); - generator.complete(); - generator.flushBuffer(); + gen.setVersion(HttpVersion.HTTP_1_1); + gen.setRequest("POST","/index.html"); + fields.add("Host","something"); + fields.add("User-Agent","test"); - String result=endp.getOut().toString().replace("\r\n","|").replace('\r','|').replace('\n','|'); - assertEquals("GET /usr HTTP/1.1|Header: Value|Content-Type: text/plain|Content-Length: 44||"+content,result); + HttpGenerator.Result + + result=gen.prepareContent(null,null,content); + assertEquals(HttpGenerator.Result.NEED_BUFFER,result); + assertEquals(HttpGenerator.State.START,gen.getState()); + + result=gen.prepareContent(null,buffer,content); + assertEquals(HttpGenerator.Result.OK,result); + assertEquals(HttpGenerator.State.START,gen.getState()); + assertEquals("Hello World",BufferUtil.toString(buffer)); + assertTrue(BufferUtil.isEmpty(content)); + + result=gen.prepareContent(null,buffer,content1); + assertEquals(HttpGenerator.Result.OK,result); + assertEquals(HttpGenerator.State.START,gen.getState()); + assertEquals("Hello World. The quick brown fox jumped over the lazy dog.",BufferUtil.toString(buffer)); + assertTrue(BufferUtil.isEmpty(content)); + + result=gen.complete(null,buffer); + assertEquals(HttpGenerator.Result.NEED_COMMIT,result); + assertEquals(HttpGenerator.State.COMPLETING_UNCOMMITTED,gen.getState()); + + result=gen.commit(fields,header,buffer,content,true); + assertEquals(HttpGenerator.Result.FLUSH,result); + assertEquals(HttpGenerator.State.COMPLETING,gen.getState()); + String head = BufferUtil.toString(header); + BufferUtil.clear(header); + String body = BufferUtil.toString(buffer); + BufferUtil.clear(buffer); + + result=gen.complete(null,buffer); + assertEquals(HttpGenerator.Result.OK,result); + assertEquals(HttpGenerator.State.END,gen.getState()); + + assertThat(head,containsString("POST /index.html HTTP/1.1")); + assertThat(head,containsString("Host: something")); + assertThat(head,containsString("Content-Length: 58")); + assertTrue(head.endsWith("\r\n\r\n")); + + assertEquals("Hello World. The quick brown fox jumped over the lazy dog.",body); + + assertEquals(58,gen.getContentWritten()); } @Test - public void testAutoContentLength() throws Exception + public void testRequestWithChunkedContent() throws Exception { - ByteBuffer bb=new ByteArrayBuffer(8096); - ByteBuffer sb=new ByteArrayBuffer(1500); - ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); - HttpGenerator generator = new HttpGenerator(new SimpleBuffers(sb,bb),endp); - - generator.setRequest("GET","/usr"); - + ByteBuffer header=BufferUtil.allocate(4096); + ByteBuffer buffer=BufferUtil.allocate(16); + ByteBuffer content0=BufferUtil.toBuffer("Hello World! "); + ByteBuffer content1=BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. "); HttpFields fields = new HttpFields(); - fields.add("Header","Value"); - fields.add("Content-Type","text/plain"); + HttpGenerator gen = new HttpGenerator(); - String content = "The quick brown fox jumped over the lazy dog"; + gen.setVersion(HttpVersion.HTTP_1_1); + gen.setRequest("POST","/index.html"); + fields.add("Host","something"); + fields.add("User-Agent","test"); - generator.addContent(new ByteArrayBuffer(content),true); - generator.completeHeader(fields,true); + HttpGenerator.Result + + result=gen.prepareContent(null,null,content0); + assertEquals(HttpGenerator.Result.NEED_BUFFER,result); + assertEquals(HttpGenerator.State.START,gen.getState()); + + result=gen.prepareContent(null,buffer,content0); + assertEquals(HttpGenerator.Result.OK,result); + assertEquals(HttpGenerator.State.START,gen.getState()); + assertEquals("Hello World! ",BufferUtil.toString(buffer)); + assertEquals(0,content0.remaining()); + + result=gen.prepareContent(null,buffer,content1); + assertEquals(HttpGenerator.Result.NEED_COMMIT,result); + assertEquals(HttpGenerator.State.START,gen.getState()); + assertEquals("Hello World! The",BufferUtil.toString(buffer)); + assertEquals(43,content1.remaining()); - generator.flushBuffer(); - generator.complete(); - generator.flushBuffer(); + result=gen.commit(fields,header,buffer,content1,false); + assertEquals(HttpGenerator.Result.FLUSH,result); + assertEquals(HttpGenerator.State.COMMITTING,gen.getState()); + assertEquals("Hello World! The",BufferUtil.toString(buffer)); + assertEquals(43,content1.remaining()); + assertTrue(gen.isChunking()); - String result=endp.getOut().toString().replace("\r\n","|").replace('\r','|').replace('\n','|'); - assertEquals("GET /usr HTTP/1.1|Header: Value|Content-Type: text/plain|Content-Length: 44||"+content,result); + String head = BufferUtil.toString(header); + BufferUtil.clear(header); + String body = BufferUtil.toString(buffer); + BufferUtil.clear(buffer); + + result=gen.commit(fields,header,buffer,content1,false); + assertEquals(HttpGenerator.Result.OK,result); + assertEquals(HttpGenerator.State.COMMITTED,gen.getState()); + + result=gen.prepareContent(null,buffer,content1); + assertEquals(HttpGenerator.Result.NEED_CHUNK,result); + assertEquals(HttpGenerator.State.COMMITTED,gen.getState()); + + ByteBuffer chunk=BufferUtil.allocate(HttpGenerator.CHUNK_SIZE); + result=gen.prepareContent(chunk,buffer,content1); + assertEquals(HttpGenerator.Result.FLUSH,result); + assertEquals(HttpGenerator.State.COMMITTED,gen.getState()); + assertEquals("\r\n10\r\n",BufferUtil.toString(chunk)); + assertEquals(" quick brown fox",BufferUtil.toString(buffer)); + assertEquals(27,content1.remaining()); + body += BufferUtil.toString(chunk)+BufferUtil.toString(buffer); + BufferUtil.clear(chunk); + BufferUtil.clear(buffer); + + result=gen.prepareContent(chunk,buffer,content1); + assertEquals(HttpGenerator.Result.FLUSH,result); + assertEquals(HttpGenerator.State.COMMITTED,gen.getState()); + assertEquals("\r\n10\r\n",BufferUtil.toString(chunk)); + assertEquals(" jumped over the",BufferUtil.toString(buffer)); + assertEquals(11,content1.remaining()); + body += BufferUtil.toString(chunk)+BufferUtil.toString(buffer); + BufferUtil.clear(chunk); + BufferUtil.clear(buffer); + + result=gen.prepareContent(chunk,buffer,content1); + assertEquals(HttpGenerator.Result.OK,result); + assertEquals(HttpGenerator.State.COMMITTED,gen.getState()); + assertEquals("",BufferUtil.toString(chunk)); + assertEquals(" lazy dog. ",BufferUtil.toString(buffer)); + assertEquals(0,content1.remaining()); + + result=gen.complete(chunk,buffer); + assertEquals(HttpGenerator.Result.FLUSH,result); + assertEquals(HttpGenerator.State.COMPLETING,gen.getState()); + assertEquals("\r\nB\r\n",BufferUtil.toString(chunk)); + assertEquals(" lazy dog. ",BufferUtil.toString(buffer)); + body += BufferUtil.toString(chunk)+BufferUtil.toString(buffer); + BufferUtil.clear(chunk); + BufferUtil.clear(buffer); + + result=gen.complete(chunk,buffer); + assertEquals(HttpGenerator.Result.FLUSH,result); + assertEquals(HttpGenerator.State.END,gen.getState()); + assertEquals("\r\n0\r\n\r\n",BufferUtil.toString(chunk)); + assertEquals(0,buffer.remaining()); + body += BufferUtil.toString(chunk); + BufferUtil.clear(chunk); + BufferUtil.clear(buffer); + + result=gen.complete(chunk,buffer); + assertEquals(HttpGenerator.Result.OK,result); + assertEquals(HttpGenerator.State.END,gen.getState()); + + assertEquals(59,gen.getContentWritten()); + + // System.err.println(head+body); + + assertThat(head,containsString("POST /index.html HTTP/1.1")); + assertThat(head,containsString("Host: something")); + assertThat(head,not(containsString("Content-Length"))); + assertThat(head,containsString("Transfer-Encoding: chunked")); + assertTrue(head.endsWith("\r\n\r\n10\r\n")); } @Test - public void testChunked() throws Exception + public void testRequestWithLargeChunkedContent() throws Exception { - ByteBuffer bb=new ByteArrayBuffer(8096); - ByteBuffer sb=new ByteArrayBuffer(1500); - ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); - HttpGenerator generator = new HttpGenerator(new SimpleBuffers(sb,bb),endp); - - generator.setRequest("GET","/usr"); - + ByteBuffer header=BufferUtil.allocate(4096); + ByteBuffer content0=BufferUtil.toBuffer("Hello Cruel World! "); + ByteBuffer content1=BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. "); HttpFields fields = new HttpFields(); - fields.add("Header","Value"); - fields.add("Content-Type","text/plain"); + HttpGenerator gen = new HttpGenerator(); + gen.setLargeContent(8); - String content = "The quick brown fox jumped over the lazy dog"; + gen.setVersion(HttpVersion.HTTP_1_1); + gen.setRequest("POST","/index.html"); + fields.add("Host","something"); + fields.add("User-Agent","test"); - generator.completeHeader(fields,false); + HttpGenerator.Result + + result=gen.prepareContent(null,null,content0); + assertEquals(HttpGenerator.Result.NEED_COMMIT,result); + assertEquals(HttpGenerator.State.START,gen.getState()); + + result=gen.commit(fields,header,null,content0,false); + assertEquals(HttpGenerator.Result.FLUSH_CONTENT,result); + assertEquals(HttpGenerator.State.COMMITTED,gen.getState()); + assertTrue(gen.isChunking()); + + String head = BufferUtil.toString(header); + BufferUtil.clear(header); + String body = BufferUtil.toString(content0); + BufferUtil.clear(content0); - generator.addContent(new ByteArrayBuffer(content),false); - generator.flushBuffer(); - generator.complete(); - generator.flushBuffer(); + result=gen.commit(fields,header,null,content0,false); + assertEquals(HttpGenerator.Result.OK,result); + assertEquals(HttpGenerator.State.COMMITTED,gen.getState()); + + result=gen.prepareContent(null,null,content1); + assertEquals(HttpGenerator.Result.NEED_CHUNK,result); + assertEquals(HttpGenerator.State.COMMITTED,gen.getState()); - String result=endp.getOut().toString().replace("\r\n","|").replace('\r','|').replace('\n','|'); - assertEquals("GET /usr HTTP/1.1|Header: Value|Content-Type: text/plain|Transfer-Encoding: chunked||2C|"+content+"|0||",result); + ByteBuffer chunk=BufferUtil.allocate(HttpGenerator.CHUNK_SIZE); + result=gen.prepareContent(chunk,null,content1); + assertEquals(HttpGenerator.Result.FLUSH_CONTENT,result); + assertEquals(HttpGenerator.State.COMMITTED,gen.getState()); + assertEquals("\r\n2E\r\n",BufferUtil.toString(chunk)); + + body += BufferUtil.toString(chunk)+BufferUtil.toString(content1); + BufferUtil.clear(content1); + + result=gen.complete(chunk,null); + assertEquals(HttpGenerator.Result.FLUSH,result); + assertEquals(HttpGenerator.State.END,gen.getState()); + assertEquals("\r\n0\r\n\r\n",BufferUtil.toString(chunk)); + body += BufferUtil.toString(chunk); + + result=gen.complete(chunk,null); + assertEquals(HttpGenerator.Result.OK,result); + assertEquals(HttpGenerator.State.END,gen.getState()); + + assertEquals(65,gen.getContentWritten()); + + // System.err.println(head+body); + + assertThat(head,containsString("POST /index.html HTTP/1.1")); + assertThat(head,containsString("Host: something")); + assertThat(head,not(containsString("Content-Length"))); + assertThat(head,containsString("Transfer-Encoding: chunked")); + assertTrue(head.endsWith("\r\n\r\n13\r\n")); + } + + + @Test + public void testRequestWithKnownContent() throws Exception + { + ByteBuffer header=BufferUtil.allocate(4096); + ByteBuffer buffer=BufferUtil.allocate(16); + ByteBuffer content0=BufferUtil.toBuffer("Hello World! "); + ByteBuffer content1=BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. "); + HttpFields fields = new HttpFields(); + HttpGenerator gen = new HttpGenerator(); + + gen.setVersion(HttpVersion.HTTP_1_1); + gen.setRequest("POST","/index.html"); + fields.add("Host","something"); + fields.add("User-Agent","test"); + fields.add("Content-Length","59"); + gen.setContentLength(59); + + HttpGenerator.Result + + result=gen.prepareContent(null,null,content0); + assertEquals(HttpGenerator.Result.NEED_BUFFER,result); + assertEquals(HttpGenerator.State.START,gen.getState()); + + result=gen.prepareContent(null,buffer,content0); + assertEquals(HttpGenerator.Result.OK,result); + assertEquals(HttpGenerator.State.START,gen.getState()); + assertEquals("Hello World! ",BufferUtil.toString(buffer)); + assertEquals(0,content0.remaining()); + + result=gen.prepareContent(null,buffer,content1); + assertEquals(HttpGenerator.Result.NEED_COMMIT,result); + assertEquals(HttpGenerator.State.START,gen.getState()); + assertEquals("Hello World! The",BufferUtil.toString(buffer)); + assertEquals(43,content1.remaining()); + + result=gen.commit(fields,header,buffer,content1,false); + assertEquals(HttpGenerator.Result.FLUSH,result); + assertEquals(HttpGenerator.State.COMMITTED,gen.getState()); + assertEquals("Hello World! The",BufferUtil.toString(buffer)); + assertEquals(43,content1.remaining()); + assertTrue(!gen.isChunking()); + + String head = BufferUtil.toString(header); + BufferUtil.clear(header); + String body = BufferUtil.toString(buffer); + BufferUtil.clear(buffer); + + result=gen.prepareContent(null,buffer,content1); + assertEquals(HttpGenerator.Result.FLUSH,result); + assertEquals(HttpGenerator.State.COMMITTED,gen.getState()); + assertEquals(" quick brown fox",BufferUtil.toString(buffer)); + assertEquals(27,content1.remaining()); + body += BufferUtil.toString(buffer); + BufferUtil.clear(buffer); + + result=gen.prepareContent(null,buffer,content1); + assertEquals(HttpGenerator.Result.FLUSH,result); + assertEquals(HttpGenerator.State.COMMITTED,gen.getState()); + assertEquals(" jumped over the",BufferUtil.toString(buffer)); + assertEquals(11,content1.remaining()); + body += BufferUtil.toString(buffer); + BufferUtil.clear(buffer); + + result=gen.prepareContent(null,buffer,content1); + assertEquals(HttpGenerator.Result.OK,result); + assertEquals(HttpGenerator.State.COMMITTED,gen.getState()); + assertEquals(" lazy dog. ",BufferUtil.toString(buffer)); + assertEquals(0,content1.remaining()); + + result=gen.complete(null,buffer); + assertEquals(HttpGenerator.Result.FLUSH,result); + assertEquals(HttpGenerator.State.COMPLETING,gen.getState()); + assertEquals(" lazy dog. ",BufferUtil.toString(buffer)); + body += BufferUtil.toString(buffer); + BufferUtil.clear(buffer); + + result=gen.complete(null,buffer); + assertEquals(HttpGenerator.Result.OK,result); + assertEquals(HttpGenerator.State.END,gen.getState()); + assertEquals(0,buffer.remaining()); + + assertEquals(59,gen.getContentWritten()); + + // System.err.println(head+body); + + assertThat(head,containsString("POST /index.html HTTP/1.1")); + assertThat(head,containsString("Host: something")); + assertThat(head,containsString("Content-Length: 59")); + assertThat(head,not(containsString("chunked"))); + assertTrue(head.endsWith("\r\n\r\n")); } @Test - public void testHTTP() throws Exception + public void testRequestWithKnownLargeContent() throws Exception { - ByteBuffer bb=new ByteArrayBuffer(8096); - ByteBuffer sb=new ByteArrayBuffer(1500); + ByteBuffer header=BufferUtil.allocate(4096); + ByteBuffer content0=BufferUtil.toBuffer("Hello World! "); + ByteBuffer content1=BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. "); HttpFields fields = new HttpFields(); - ByteArrayEndPoint endp = new ByteArrayEndPoint(new byte[0],4096); - HttpGenerator hb = new HttpGenerator(new SimpleBuffers(sb,bb),endp); - Handler handler = new Handler(); - HttpParser parser=null; + HttpGenerator gen = new HttpGenerator(); + gen.setLargeContent(8); - // For HTTP version - for (int v=9;v<=11;v++) - { - // For each test result - for (int r=0;r2) - continue; - System.err.println(t); - throw e; - } - String request=endp.getOut().toString(); - // System.out.println(request+(hb.isPersistent()?"...\n":"---\n")); + result=gen.complete(null,null); + assertEquals(HttpGenerator.Result.OK,result); + assertEquals(HttpGenerator.State.END,gen.getState()); + + assertEquals(59,gen.getContentWritten()); + + // System.err.println(head+body); - assertTrue(t,hb.isPersistent()); - - if (v==9) - { - assertEquals(t,"GET /context/path/info\r\n", request); - continue; - } - - parser=new HttpParser(new ByteArrayBuffer(request.getBytes()), handler); - try - { - parser.parse(); - } - catch(IOException e) - { - if (tr[r].body!=null) - throw e; - continue; - } - - 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); - else - assertTrue(t,hb.isPersistent() || c==2); - - assertTrue(t,tr[r].values[1]==null || content.length()==Integer.parseInt(tr[r].values[1])); - } - } - } - } - } - - private static final String[] headers= { "Content-Type","Content-Length","Connection","Transfer-Encoding","Other"}; - private static class TR - { - private String[] values=new String[headers.length]; - private String body; - - private TR(String ct, String cl ,String content) - { - values[0]=ct; - values[1]=cl; - values[4]="value"; - this.body=content; - } - - private void build(int version,HttpGenerator hb, String connection, String te, int chunks, HttpFields fields) - throws Exception - { - values[2]=connection; - values[3]=te; - - hb.setRequest(HttpMethod.GET,"/context/path/info"); - hb.setVersion(version); - - for (int i=0;i1) + content.limit(content.position()+content.remaining()/2); + else + content.limit(content.capacity()); + } + + + switch(gen.getState()) + { + case START: + case COMPLETING_UNCOMMITTED: + case COMMITTED: + case COMPLETING: + case END: + + } + + } + + + switch(gen.prepareContent(chunk,buffer,content)) + { + case FLUSH: + if (BufferUtil.hasContent(chunk)) + { + response+=BufferUtil.toString(chunk); + chunk.position(chunk.limit()); + } + if (BufferUtil.hasContent(buffer)) + { + response+=BufferUtil.toString(buffer); + buffer.position(buffer.limit()); + } + break; + + case FLUSH_CONTENT: + if (BufferUtil.hasContent(chunk)) + { + response+=BufferUtil.toString(chunk); + chunk.position(chunk.limit()); + } + if (BufferUtil.hasContent(content)) + { + response+=BufferUtil.toString(content); + content.position(content.limit()); + } + break; + + case NEED_BUFFER: + buffer=BufferUtil.allocate(8192); + break; + + case NEED_CHUNK: + chunk=BufferUtil.allocate(HttpGenerator.CHUNK_SIZE); + break; + + case NEED_COMMIT: + { + commitLoop: while (true) + { + ByteBuffer header=BufferUtil.allocate(4096); + switch(gen.commit(fields,header,buffer,content,chunks==0)) + { + case FLUSH: + if (BufferUtil.hasContent(header)) + { + response+=BufferUtil.toString(header); + header.position(header.limit()); + } + if (BufferUtil.hasContent(buffer)) + { + response+=BufferUtil.toString(buffer); + buffer.position(buffer.limit()); + } + break; + + case FLUSH_CONTENT: + if (BufferUtil.hasContent(header)) + { + response+=BufferUtil.toString(header); + header.position(header.limit()); + } + if (BufferUtil.hasContent(content)) + { + response+=BufferUtil.toString(content); + content.position(content.limit()); + } + break; + + case NEED_BUFFER: + buffer=BufferUtil.allocate(8192); + break; + + case OK: + break commitLoop; + + default: + throw new IllegalStateException(gen.toString()); + } + } + } + break; + + + case NEED_COMPLETE: + { + completeLoop: while (true) + { + ByteBuffer header=BufferUtil.allocate(4096); + switch(gen.complete(chunk,buffer)) + { + case FLUSH: + if (BufferUtil.hasContent(chunk)) + { + response+=BufferUtil.toString(chunk); + chunk.position(chunk.limit()); + } + if (BufferUtil.hasContent(buffer)) + { + response+=BufferUtil.toString(buffer); + buffer.position(buffer.limit()); + } + break; + + case OK: + break completeLoop; + + default: + throw new IllegalStateException(gen.toString()); + } + } + } + break; + } + + continue; + } + + + while (true) + { + switch(gen.complete(chunk,buffer)) + { + case FLUSH: + if (BufferUtil.hasContent(chunk)) + { + response+=BufferUtil.toString(chunk); + chunk.position(chunk.limit()); + } + if (BufferUtil.hasContent(buffer)) + { + response+=BufferUtil.toString(buffer); + buffer.position(buffer.limit()); + } + break; + + case OK: + break mainLoop; + + default: + throw new IllegalStateException(gen.toString()); } } - view.setPutIndex(buf.putIndex()); - view.setGetIndex((chunks-1)*inc); - hb.addContent(view,Generator.LAST); - if(hb.isState(AbstractGenerator.STATE_HEADER)) - hb.completeHeader(fields, Generator.LAST); } - else - { - hb.completeHeader(fields, Generator.LAST); - } - hb.complete(); - while(!hb.isComplete()) - hb.flushBuffer(); + return response; } @Override diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java index 52a1941aeb7..8dd025edda4 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java @@ -33,13 +33,6 @@ public class BufferUtil { (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F' }; - - /* ------------------------------------------------------------ */ - public static void clear(ByteBuffer buffer) - { - buffer.position(0); - buffer.limit(0); - } /* ------------------------------------------------------------ */ /** Allocate ByteBuffer in output mode. @@ -70,10 +63,25 @@ public class BufferUtil } + + /* ------------------------------------------------------------ */ + public static void clear(ByteBuffer buffer) + { + buffer.position(0); + buffer.limit(0); + } + + /* ------------------------------------------------------------ */ + public static void clearToFill(ByteBuffer buffer) + { + buffer.position(0); + buffer.limit(buffer.capacity()); + } + /* ------------------------------------------------------------ */ public static void flipToFill(ByteBuffer buffer) { - buffer.position(buffer.limit()); + buffer.position(buffer.hasRemaining()?buffer.limit():0); buffer.limit(buffer.capacity()); } @@ -128,7 +136,7 @@ public class BufferUtil /** * Put data from one buffer into another, avoiding over/under flows * @param from Buffer to take bytes from - * @param to Buffer to put bytes to + * @param to Buffer to put bytes to. The buffer is flipped before and after the put. * @return number of bytes moved */ public static int put(ByteBuffer from, ByteBuffer to, long maxBytes) @@ -141,7 +149,7 @@ public class BufferUtil /** * Put data from one buffer into another, avoiding over/under flows * @param from Buffer to take bytes from - * @param to Buffer to put bytes to + * @param to Buffer to put bytes to. The buffer is flipped before and after the put. * @return number of bytes moved */ public static int put(ByteBuffer from, ByteBuffer to, int maxBytes) @@ -191,7 +199,7 @@ public class BufferUtil /** * Put data from one buffer into another, avoiding over/under flows * @param from Buffer to take bytes from - * @param to Buffer to put bytes to + * @param to Buffer to put bytes to. The buffer is flipped before and after the put. * @return number of bytes moved */ public static int put(ByteBuffer from, ByteBuffer to)