From 80386028c1dbcc9c4ec5a695829a34a4b1cbb834 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Thu, 2 Feb 2017 13:27:12 +1100 Subject: [PATCH] Trailers for generated requests --- .../org/eclipse/jetty/http/HttpGenerator.java | 205 ++++++------ .../java/org/eclipse/jetty/http/MetaData.java | 14 +- .../jetty/http/HttpGeneratorClientTest.java | 16 +- .../http/HttpGeneratorServerHTTPTest.java | 5 + .../jetty/http/HttpGeneratorServerTest.java | 302 ++++++++++++++---- .../org/eclipse/jetty/http/HttpTester.java | 4 + .../eclipse/jetty/server/HttpConnection.java | 7 + 7 files changed, 384 insertions(+), 169 deletions(-) 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 12c63dd85c3..d6ca0436c9e 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 @@ -24,6 +24,7 @@ import java.io.IOException; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.function.Supplier; import org.eclipse.jetty.http.HttpTokens.EndOfContent; import org.eclipse.jetty.util.ArrayTrie; @@ -55,7 +56,7 @@ public class HttpGenerator // states public enum State { START, COMMITTED, COMPLETING, COMPLETING_1XX, END } - public enum Result { NEED_CHUNK,NEED_INFO,NEED_HEADER,FLUSH,CONTINUE,SHUTDOWN_OUT,DONE} + public enum Result { NEED_CHUNK,NEED_INFO,NEED_HEADER,NEED_CHUNK_TRAILER, FLUSH,CONTINUE,SHUTDOWN_OUT,DONE} // other statics public static final int CHUNK_SIZE = 12; @@ -66,6 +67,7 @@ public class HttpGenerator private long _contentPrepared = 0; private boolean _noContentResponse = false; private Boolean _persistent = null; + private Supplier _trailers = null; private final int _send; private final static int SEND_SERVER = 0x01; @@ -111,6 +113,7 @@ public class HttpGenerator _persistent = null; _contentPrepared = 0; _needCRLF = false; + _trailers = null; } /* ------------------------------------------------------------ */ @@ -278,52 +281,12 @@ public class HttpGenerator case COMMITTED: { - int len = BufferUtil.length(content); - - if (len>0) - { - // Do we need a chunk buffer? - if (isChunking()) - { - // Do we need a chunk buffer? - if (chunk==null) - return Result.NEED_CHUNK; - BufferUtil.clearToFill(chunk); - prepareChunk(chunk,len); - BufferUtil.flipToFlush(chunk,0); - } - _contentPrepared+=len; - } - - if (last) - _state=State.COMPLETING; - - return len>0?Result.FLUSH:Result.CONTINUE; + return committed(chunk,content,last); } case COMPLETING: { - if (BufferUtil.hasContent(content)) - { - if (LOG.isDebugEnabled()) - LOG.debug("discarding content in COMPLETING"); - BufferUtil.clear(content); - } - - if (isChunking()) - { - // Do we need a chunk buffer? - if (chunk==null) - return Result.NEED_CHUNK; - BufferUtil.clearToFill(chunk); - prepareChunk(chunk,0); - BufferUtil.flipToFlush(chunk,0); - _endOfContent=EndOfContent.UNKNOWN_CONTENT; - return Result.FLUSH; - } - - _state=State.END; - return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT; + return completing(chunk,content); } case END: @@ -340,7 +303,82 @@ public class HttpGenerator } } + private Result committed( ByteBuffer chunk, ByteBuffer content, boolean last) + { + int len = BufferUtil.length(content); + + // handle the content. + if (len>0) + { + if (isChunking()) + { + if (chunk==null) + return Result.NEED_CHUNK; + BufferUtil.clearToFill(chunk); + prepareChunk(chunk,len); + BufferUtil.flipToFlush(chunk,0); + } + _contentPrepared+=len; + } + + if (last) + { + _state=State.COMPLETING; + return len>0?Result.FLUSH:Result.CONTINUE; + } + return len>0?Result.FLUSH:Result.DONE; + } + + private Result completing( ByteBuffer chunk, ByteBuffer content) + { + if (BufferUtil.hasContent(content)) + { + if (LOG.isDebugEnabled()) + LOG.debug("discarding content in COMPLETING"); + BufferUtil.clear(content); + } + + if (isChunking()) + { + if (_trailers!=null) + { + // Do we need a chunk buffer? + if (chunk==null || chunk.capacity()<=CHUNK_SIZE) + return Result.NEED_CHUNK_TRAILER; + + HttpFields trailers = _trailers.get(); + + if (trailers!=null) + { + // Write the last chunk + BufferUtil.clearToFill(chunk); + generateTrailers(chunk,trailers); + BufferUtil.flipToFlush(chunk,0); + _endOfContent=EndOfContent.UNKNOWN_CONTENT; + return Result.FLUSH; + } + } + + // Do we need a chunk buffer? + if (chunk==null) + return Result.NEED_CHUNK; + + // Write the last chunk + BufferUtil.clearToFill(chunk); + prepareChunk(chunk,0); + BufferUtil.flipToFlush(chunk,0); + _endOfContent=EndOfContent.UNKNOWN_CONTENT; + return Result.FLUSH; + } + + _state=State.END; + return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT; + + } + + /* ------------------------------------------------------------ */ + @Deprecated public Result generateResponse(MetaData.Response info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException { return generateResponse(info,false,header,chunk,content,last); @@ -386,7 +424,7 @@ public class HttpGenerator // prepare the header int pos=BufferUtil.flipToFill(header); try - { + { // generate ResponseLine generateResponseLine(info,header); @@ -442,29 +480,7 @@ public class HttpGenerator case COMMITTED: { - int len = BufferUtil.length(content); - - // handle the content. - if (len>0) - { - if (isChunking()) - { - if (chunk==null) - return Result.NEED_CHUNK; - BufferUtil.clearToFill(chunk); - prepareChunk(chunk,len); - BufferUtil.flipToFlush(chunk,0); - } - _contentPrepared+=len; - } - - if (last) - { - _state=State.COMPLETING; - return len>0?Result.FLUSH:Result.CONTINUE; - } - return len>0?Result.FLUSH:Result.DONE; - + return committed(chunk,content,last); } case COMPLETING_1XX: @@ -475,30 +491,7 @@ public class HttpGenerator case COMPLETING: { - if (BufferUtil.hasContent(content)) - { - if (LOG.isDebugEnabled()) - LOG.debug("discarding content in COMPLETING"); - BufferUtil.clear(content); - } - - if (isChunking()) - { - // Do we need a chunk buffer? - if (chunk==null) - return Result.NEED_CHUNK; - - // Write the last chunk - BufferUtil.clearToFill(chunk); - prepareChunk(chunk,0); - BufferUtil.flipToFlush(chunk,0); - _endOfContent=EndOfContent.UNKNOWN_CONTENT; - return Result.FLUSH; - } - - _state=State.END; - - return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT; + return completing(chunk,content); } case END: @@ -535,6 +528,30 @@ public class HttpGenerator _needCRLF=false; } } + + /* ------------------------------------------------------------ */ + private void generateTrailers(ByteBuffer buffer, HttpFields trailer) + { + // if we need CRLF add this to header + if (_needCRLF) + BufferUtil.putCRLF(buffer); + + // Add the chunk size to the header + buffer.put(ZERO_CHUNK); + + int n=trailer.size(); + for (int f=0;f { private HttpVersion _httpVersion; - private HttpFields _fields; + private final HttpFields _fields; private long _contentLength; + private Supplier _trailers; public MetaData(HttpVersion version, HttpFields fields) { @@ -90,6 +92,16 @@ public class MetaData implements Iterable return _fields; } + public Supplier getTrailerSupplier() + { + return _trailers; + } + + public void setTrailerSupplier(Supplier trailers) + { + _trailers = trailers; + } + /** * @return the content length if available, otherwise {@link Long#MIN_VALUE} */ 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 7dd1886da6d..11e93f6f9a5 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 @@ -69,7 +69,7 @@ public class HttpGeneratorClientTest String out = BufferUtil.toString(header); BufferUtil.clear(header); - result=gen.generateResponse(null,null,null,null, false); + result=gen.generateResponse(null,false,null,null, null, false); Assert.assertEquals(HttpGenerator.Result.DONE, result); Assert.assertEquals(HttpGenerator.State.END, gen.getState()); Assert.assertTrue(!gen.isChunking()); @@ -106,7 +106,7 @@ public class HttpGeneratorClientTest String out = BufferUtil.toString(header); BufferUtil.clear(header); - result=gen.generateResponse(null,null,null,null, false); + result=gen.generateResponse(null,false,null,null, null, false); Assert.assertEquals(HttpGenerator.Result.DONE, result); Assert.assertEquals(HttpGenerator.State.END, gen.getState()); Assert.assertTrue(!gen.isChunking()); @@ -146,7 +146,7 @@ public class HttpGeneratorClientTest out+=BufferUtil.toString(content0); BufferUtil.clear(content0); - result=gen.generateResponse(null,null,null,null, false); + result=gen.generateResponse(null,false,null,null, null, false); Assert.assertEquals(HttpGenerator.Result.DONE, result); Assert.assertEquals(HttpGenerator.State.END, gen.getState()); Assert.assertTrue(!gen.isChunking()); @@ -205,19 +205,19 @@ public class HttpGeneratorClientTest out+=BufferUtil.toString(content1); BufferUtil.clear(content1); - result=gen.generateResponse(null,null,chunk,null, true); + result=gen.generateResponse(null,false,null,chunk, null, true); Assert.assertEquals(HttpGenerator.Result.CONTINUE, result); Assert.assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); Assert.assertTrue(gen.isChunking()); - result=gen.generateResponse(null,null,chunk,null, true); + result=gen.generateResponse(null,false,null,chunk, null, true); Assert.assertEquals(HttpGenerator.Result.FLUSH, result); Assert.assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); out+=BufferUtil.toString(chunk); BufferUtil.clear(chunk); Assert.assertTrue(!gen.isChunking()); - result=gen.generateResponse(null,null,chunk,null, true); + result=gen.generateResponse(null,false,null,chunk, null, true); Assert.assertEquals(HttpGenerator.Result.DONE, result); Assert.assertEquals(HttpGenerator.State.END, gen.getState()); @@ -271,12 +271,12 @@ public class HttpGeneratorClientTest out+=BufferUtil.toString(content1); BufferUtil.clear(content1); - result=gen.generateResponse(null,null,null,null, true); + result=gen.generateResponse(null,false,null,null, null, true); Assert.assertEquals(HttpGenerator.Result.CONTINUE, result); Assert.assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); Assert.assertTrue(!gen.isChunking()); - result=gen.generateResponse(null,null,null,null, true); + result=gen.generateResponse(null,false,null,null, null, true); Assert.assertEquals(HttpGenerator.Result.DONE, result); Assert.assertEquals(HttpGenerator.State.END, gen.getState()); out+=BufferUtil.toString(chunk); diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerHTTPTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerHTTPTest.java index 4259d69566f..a85239cea56 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerHTTPTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerHTTPTest.java @@ -164,6 +164,11 @@ public class HttpGeneratorServerHTTPTest chunk = BufferUtil.allocate(HttpGenerator.CHUNK_SIZE); continue; + case NEED_CHUNK_TRAILER: + chunk = BufferUtil.allocate(2048); + continue; + + case FLUSH: if (BufferUtil.hasContent(header)) { diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java index 937539c6632..b363c29c579 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import java.nio.ByteBuffer; +import java.util.function.Supplier; import org.eclipse.jetty.util.BufferUtil; import org.hamcrest.Matchers; @@ -42,7 +43,7 @@ public class HttpGeneratorServerTest HttpGenerator gen = new HttpGenerator(); - HttpGenerator.Result result = gen.generateResponse(null, null, null, content, true); + HttpGenerator.Result result = gen.generateResponse(null, false, null, null, content, true); assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); @@ -50,7 +51,7 @@ public class HttpGeneratorServerTest info.getFields().add("Content-Type", "test/data"); info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); - result = gen.generateResponse(info, null, null, content, true); + result = gen.generateResponse(info, false, null, null, content, true); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); String response = BufferUtil.toString(header); @@ -58,7 +59,7 @@ public class HttpGeneratorServerTest response += BufferUtil.toString(content); BufferUtil.clear(content); - result = gen.generateResponse(null, null, null, content, false); + result = gen.generateResponse(null, false, null, null, content, false); assertEquals(HttpGenerator.Result.SHUTDOWN_OUT, result); assertEquals(HttpGenerator.State.END, gen.getState()); @@ -78,7 +79,7 @@ public class HttpGeneratorServerTest HttpGenerator gen = new HttpGenerator(); - HttpGenerator.Result result = gen.generateResponse(null, null, null, content, true); + HttpGenerator.Result result = gen.generateResponse(null, false, null, null, content, true); assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); @@ -86,10 +87,10 @@ public class HttpGeneratorServerTest info.getFields().add("Content-Type", "test/data"); info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); - result = gen.generateResponse(info, null, null, content, true); + result = gen.generateResponse(info, false, null, null, content, true); assertEquals(HttpGenerator.Result.NEED_HEADER, result); - result = gen.generateResponse(info, header, null, content, true); + result = gen.generateResponse(info, false, header, null, content, true); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); String response = BufferUtil.toString(header); @@ -97,7 +98,7 @@ public class HttpGeneratorServerTest response += BufferUtil.toString(content); BufferUtil.clear(content); - result = gen.generateResponse(null, null, null, content, false); + result = gen.generateResponse(null, false, null, null, content, false); assertEquals(HttpGenerator.Result.DONE, result); assertEquals(HttpGenerator.State.END, gen.getState()); @@ -121,7 +122,7 @@ public class HttpGeneratorServerTest info.getFields().add("Content-Type", "test/data"); info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); - HttpGenerator.Result result = gen.generateResponse(info, header, null, content, true); + HttpGenerator.Result result = gen.generateResponse(info, false, header, null, content, true); assertEquals(gen.isNoContent(), true); assertEquals(HttpGenerator.Result.FLUSH, result); @@ -129,7 +130,7 @@ public class HttpGeneratorServerTest String responseheaders = BufferUtil.toString(header); BufferUtil.clear(header); - result = gen.generateResponse(null, null, null, content, false); + result = gen.generateResponse(null, false, null, null, content, false); assertEquals(HttpGenerator.Result.DONE, result); assertEquals(HttpGenerator.State.END, gen.getState()); @@ -150,7 +151,7 @@ public class HttpGeneratorServerTest HttpGenerator gen = new HttpGenerator(); - HttpGenerator.Result result = gen.generateResponse(null, null, null, content, true); + HttpGenerator.Result result = gen.generateResponse(null, false, null, null, content, true); assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); @@ -158,10 +159,10 @@ public class HttpGeneratorServerTest info.getFields().add("Content-Type", "test/data;\r\nextra=value"); info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); - result = gen.generateResponse(info, null, null, content, true); + result = gen.generateResponse(info, false, null, null, content, true); assertEquals(HttpGenerator.Result.NEED_HEADER, result); - result = gen.generateResponse(info, header, null, content, true); + result = gen.generateResponse(info, false, header, null, content, true); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); String response = BufferUtil.toString(header); @@ -169,7 +170,7 @@ public class HttpGeneratorServerTest response += BufferUtil.toString(content); BufferUtil.clear(content); - result = gen.generateResponse(null, null, null, content, false); + result = gen.generateResponse(null, false, null, null, content, false); assertEquals(HttpGenerator.Result.DONE, result); assertEquals(HttpGenerator.State.END, gen.getState()); @@ -194,14 +195,14 @@ public class HttpGeneratorServerTest String head; HttpGenerator gen = new HttpGenerator(true, true); - gen.generateResponse(info, header, null, null, true); + gen.generateResponse(info, false, header, null, null, true); head = BufferUtil.toString(header); BufferUtil.clear(header); assertThat(head, containsString("HTTP/1.1 200 OK")); assertThat(head, containsString("Server: Jetty(9.x.x)")); assertThat(head, containsString("X-Powered-By: Jetty(9.x.x)")); gen.reset(); - gen.generateResponse(infoF, header, null, null, true); + gen.generateResponse(infoF, false, header, null, null, true); head = BufferUtil.toString(header); BufferUtil.clear(header); assertThat(head, containsString("HTTP/1.1 200 OK")); @@ -212,14 +213,14 @@ public class HttpGeneratorServerTest gen.reset(); gen = new HttpGenerator(false, false); - gen.generateResponse(info, header, null, null, true); + gen.generateResponse(info, false, header, null, null, true); head = BufferUtil.toString(header); BufferUtil.clear(header); assertThat(head, containsString("HTTP/1.1 200 OK")); assertThat(head, not(containsString("Server: Jetty(9.x.x)"))); assertThat(head, not(containsString("X-Powered-By: Jetty(9.x.x)"))); gen.reset(); - gen.generateResponse(infoF, header, null, null, true); + gen.generateResponse(infoF, false, header, null, null, true); head = BufferUtil.toString(header); BufferUtil.clear(header); assertThat(head, containsString("HTTP/1.1 200 OK")); @@ -237,7 +238,7 @@ public class HttpGeneratorServerTest HttpGenerator gen = new HttpGenerator(); - HttpGenerator.Result result = gen.generateResponse(null, null, null, null, true); + HttpGenerator.Result result = gen.generateResponse(null, false, null, null, null, true); assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); @@ -245,12 +246,12 @@ public class HttpGeneratorServerTest info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); info.getFields().add("Content-Length", "11"); - result = gen.generateResponse(info, null, null, null, true); + result = gen.generateResponse(info, false, null, null, null, true); assertEquals(HttpGenerator.Result.NEED_HEADER, result); try { - gen.generateResponse(info, header, null, null, true); + gen.generateResponse(info, false, header, null, null, true); Assert.fail(); } catch(BadMessageException e) @@ -266,23 +267,23 @@ public class HttpGeneratorServerTest HttpGenerator gen = new HttpGenerator(); - HttpGenerator.Result result = gen.generateResponse(null, null, null, null, true); + HttpGenerator.Result result = gen.generateResponse(null, false, null, null, null, true); assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), 0); info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); - result = gen.generateResponse(info, null, null, null, true); + result = gen.generateResponse(info, false, null, null, null, true); assertEquals(HttpGenerator.Result.NEED_HEADER, result); - result = gen.generateResponse(info, header, null, null, true); + result = gen.generateResponse(info, false, header, null, null, true); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); String head = BufferUtil.toString(header); BufferUtil.clear(header); - result = gen.generateResponse(null, null, null, null, false); + result = gen.generateResponse(null, false, null, null, null, false); assertEquals(HttpGenerator.Result.DONE, result); assertEquals(HttpGenerator.State.END, gen.getState()); @@ -299,7 +300,7 @@ public class HttpGeneratorServerTest HttpGenerator gen = new HttpGenerator(); - HttpGenerator.Result result = gen.generateResponse(null, null, null, null, true); + HttpGenerator.Result result = gen.generateResponse(null, false, null, null, null, true); assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); @@ -307,16 +308,16 @@ public class HttpGeneratorServerTest info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); info.getFields().add("Connection", "close"); - result = gen.generateResponse(info, null, null, null, true); + result = gen.generateResponse(info, false, null, null, null, true); assertEquals(HttpGenerator.Result.NEED_HEADER, result); - result = gen.generateResponse(info, header, null, null, true); + result = gen.generateResponse(info, false, header, null, null, true); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); String head = BufferUtil.toString(header); BufferUtil.clear(header); - result = gen.generateResponse(null, null, null, null, false); + result = gen.generateResponse(null, false, null, null, null, false); assertEquals(HttpGenerator.Result.SHUTDOWN_OUT, result); assertEquals(HttpGenerator.State.END, gen.getState()); @@ -333,7 +334,7 @@ public class HttpGeneratorServerTest HttpGenerator gen = new HttpGenerator(); - HttpGenerator.Result result = gen.generateResponse(null, null, null, null, true); + HttpGenerator.Result result = gen.generateResponse(null, false, null, null, null, true); assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); @@ -342,13 +343,13 @@ public class HttpGeneratorServerTest info.getFields().add("Connection", "Upgrade"); info.getFields().add("Sec-WebSocket-Accept", "123456789=="); - result = gen.generateResponse(info, header, null, null, true); + result = gen.generateResponse(info, false, header, null, null, true); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); String head = BufferUtil.toString(header); BufferUtil.clear(header); - result = gen.generateResponse(info, null, null, null, false); + result = gen.generateResponse(info, false, null, null, null, false); assertEquals(HttpGenerator.Result.DONE, result); assertEquals(HttpGenerator.State.END, gen.getState()); @@ -368,17 +369,17 @@ public class HttpGeneratorServerTest ByteBuffer content1 = BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. "); HttpGenerator gen = new HttpGenerator(); - HttpGenerator.Result result = gen.generateResponse(null, null, null, content0, false); + HttpGenerator.Result result = gen.generateResponse(null, false, null, null, content0, false); assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1); info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); - result = gen.generateResponse(info, null, null, content0, false); + result = gen.generateResponse(info, false, null, null, content0, false); assertEquals(HttpGenerator.Result.NEED_HEADER, result); assertEquals(HttpGenerator.State.START, gen.getState()); - result = gen.generateResponse(info, header, null, content0, false); + result = gen.generateResponse(info, false, header, null, content0, false); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); @@ -387,7 +388,7 @@ public class HttpGeneratorServerTest out += BufferUtil.toString(content0); BufferUtil.clear(content0); - result = gen.generateResponse(null, null, chunk, content1, false); + result = gen.generateResponse(null, false, null, chunk, content1, false); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); out += BufferUtil.toString(chunk); @@ -395,17 +396,17 @@ public class HttpGeneratorServerTest out += BufferUtil.toString(content1); BufferUtil.clear(content1); - result = gen.generateResponse(null, null, chunk, null, true); + result = gen.generateResponse(null, false, null, chunk, null, true); assertEquals(HttpGenerator.Result.CONTINUE, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - result = gen.generateResponse(null, null, chunk, null, true); + result = gen.generateResponse(null, false, null, chunk, null, true); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); out += BufferUtil.toString(chunk); BufferUtil.clear(chunk); - result = gen.generateResponse(null, null, chunk, null, true); + result = gen.generateResponse(null, false, null, chunk, null, true); assertEquals(HttpGenerator.Result.DONE, result); assertEquals(HttpGenerator.State.END, gen.getState()); @@ -433,18 +434,18 @@ public class HttpGeneratorServerTest HttpGenerator gen = new HttpGenerator(); gen.setPersistent(false); - HttpGenerator.Result result = gen.generateResponse(null, null, null, content0, false); + HttpGenerator.Result result = gen.generateResponse(null, false, null, null, content0, false); assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1); info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); info.getFields().add(HttpHeader.TRANSFER_ENCODING, HttpHeaderValue.CHUNKED); - result = gen.generateResponse(info, null, null, content0, false); + result = gen.generateResponse(info, false, null, null, content0, false); assertEquals(HttpGenerator.Result.NEED_HEADER, result); assertEquals(HttpGenerator.State.START, gen.getState()); - result = gen.generateResponse(info, header, null, content0, false); + result = gen.generateResponse(info, false, header, null, content0, false); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); @@ -453,7 +454,10 @@ public class HttpGeneratorServerTest out += BufferUtil.toString(content0); BufferUtil.clear(content0); - result = gen.generateResponse(null, null, chunk, content1, false); + result = gen.generateResponse(null, false, null, null, content1, false); + assertEquals(HttpGenerator.Result.NEED_CHUNK, result); + + result = gen.generateResponse(null, false, null, chunk, content1, false); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); out += BufferUtil.toString(chunk); @@ -461,17 +465,17 @@ public class HttpGeneratorServerTest out += BufferUtil.toString(content1); BufferUtil.clear(content1); - result = gen.generateResponse(null, null, chunk, null, true); + result = gen.generateResponse(null, false, null, chunk, null, true); assertEquals(HttpGenerator.Result.CONTINUE, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - result = gen.generateResponse(null, null, chunk, null, true); + result = gen.generateResponse(null, false, null, chunk, null, true); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); out += BufferUtil.toString(chunk); BufferUtil.clear(chunk); - result = gen.generateResponse(null, null, chunk, null, true); + result = gen.generateResponse(null, false, null, chunk, null, true); assertEquals(HttpGenerator.Result.SHUTDOWN_OUT, result); assertEquals(HttpGenerator.State.END, gen.getState()); @@ -489,6 +493,170 @@ public class HttpGeneratorServerTest "\r\n")); } + @Test + public void testResponseWithContentAndTrailer() throws Exception + { + ByteBuffer header = BufferUtil.allocate(4096); + ByteBuffer chunk = BufferUtil.allocate(HttpGenerator.CHUNK_SIZE); + ByteBuffer trailer = BufferUtil.allocate(4096); + ByteBuffer content0 = BufferUtil.toBuffer("Hello World! "); + ByteBuffer content1 = BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. "); + HttpGenerator gen = new HttpGenerator(); + gen.setPersistent(false); + + HttpGenerator.Result result = gen.generateResponse(null, false, null, null, content0, false); + assertEquals(HttpGenerator.Result.NEED_INFO, result); + assertEquals(HttpGenerator.State.START, gen.getState()); + + MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1); + info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); + info.getFields().add(HttpHeader.TRANSFER_ENCODING, HttpHeaderValue.CHUNKED); + info.setTrailerSupplier(new Supplier() + { + @Override + public HttpFields get() + { + HttpFields trailer = new HttpFields(); + trailer.add("T-Name0","T-ValueA"); + trailer.add("T-Name0","T-ValueB"); + trailer.add("T-Name1","T-ValueC"); + return trailer; + } + }); + + result = gen.generateResponse(info, false, null, null, content0, false); + assertEquals(HttpGenerator.Result.NEED_HEADER, result); + assertEquals(HttpGenerator.State.START, gen.getState()); + + result = gen.generateResponse(info, false, header, null, content0, false); + assertEquals(HttpGenerator.Result.FLUSH, result); + assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); + + String out = BufferUtil.toString(header); + BufferUtil.clear(header); + out += BufferUtil.toString(content0); + BufferUtil.clear(content0); + + result = gen.generateResponse(null, false, null, null, content1, false); + assertEquals(HttpGenerator.Result.NEED_CHUNK, result); + + result = gen.generateResponse(null, false, null, chunk, content1, false); + assertEquals(HttpGenerator.Result.FLUSH, result); + assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); + out += BufferUtil.toString(chunk); + BufferUtil.clear(chunk); + out += BufferUtil.toString(content1); + BufferUtil.clear(content1); + + result = gen.generateResponse(null, false, null, chunk, null, true); + assertEquals(HttpGenerator.Result.CONTINUE, result); + assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); + + result = gen.generateResponse(null, false, null, chunk, null, true); + + assertEquals(HttpGenerator.Result.NEED_CHUNK_TRAILER, result); + assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); + + result = gen.generateResponse(null, false, null, trailer, null, true); + + assertEquals(HttpGenerator.Result.FLUSH, result); + assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); + out += BufferUtil.toString(trailer); + BufferUtil.clear(trailer); + + result = gen.generateResponse(null, false, null, trailer, null, true); + assertEquals(HttpGenerator.Result.SHUTDOWN_OUT, result); + assertEquals(HttpGenerator.State.END, gen.getState()); + + assertThat(out, containsString("HTTP/1.1 200 OK")); + assertThat(out, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); + assertThat(out, not(containsString("Content-Length"))); + assertThat(out, containsString("Transfer-Encoding: chunked")); + + assertThat(out, endsWith( + "\r\n\r\nD\r\n"+ + "Hello World! \r\n"+ + "2E\r\n"+ + "The quick brown fox jumped over the lazy dog. \r\n"+ + "0\r\n"+ + "T-Name0: T-ValueA\r\n"+ + "T-Name0: T-ValueB\r\n"+ + "T-Name1: T-ValueC\r\n"+ + "\r\n")); + } + + @Test + public void testResponseWithTrailer() throws Exception + { + ByteBuffer header = BufferUtil.allocate(4096); + ByteBuffer chunk = BufferUtil.allocate(HttpGenerator.CHUNK_SIZE); + ByteBuffer trailer = BufferUtil.allocate(4096); + HttpGenerator gen = new HttpGenerator(); + gen.setPersistent(false); + + HttpGenerator.Result result = gen.generateResponse(null, false, null, null, null, true); + assertEquals(HttpGenerator.Result.NEED_INFO, result); + assertEquals(HttpGenerator.State.START, gen.getState()); + + MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1); + info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); + info.getFields().add(HttpHeader.TRANSFER_ENCODING, HttpHeaderValue.CHUNKED); + info.setTrailerSupplier(new Supplier() + { + @Override + public HttpFields get() + { + HttpFields trailer = new HttpFields(); + trailer.add("T-Name0","T-ValueA"); + trailer.add("T-Name0","T-ValueB"); + trailer.add("T-Name1","T-ValueC"); + return trailer; + } + }); + + result = gen.generateResponse(info, false, null, null, null, true); + assertEquals(HttpGenerator.Result.NEED_HEADER, result); + assertEquals(HttpGenerator.State.START, gen.getState()); + + result = gen.generateResponse(info, false, header, null, null, true); + assertEquals(HttpGenerator.Result.FLUSH, result); + assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); + + String out = BufferUtil.toString(header); + BufferUtil.clear(header); + + result = gen.generateResponse(null, false, null, null, null, true); + assertEquals(HttpGenerator.Result.NEED_CHUNK_TRAILER, result); + assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); + + result = gen.generateResponse(null, false, null, chunk, null, true); + assertEquals(HttpGenerator.Result.NEED_CHUNK_TRAILER, result); + assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); + + result = gen.generateResponse(null, false, null, trailer, null, true); + + assertEquals(HttpGenerator.Result.FLUSH, result); + assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); + out += BufferUtil.toString(trailer); + BufferUtil.clear(trailer); + + result = gen.generateResponse(null, false, null, trailer, null, true); + assertEquals(HttpGenerator.Result.SHUTDOWN_OUT, result); + assertEquals(HttpGenerator.State.END, gen.getState()); + + assertThat(out, containsString("HTTP/1.1 200 OK")); + assertThat(out, containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")); + assertThat(out, not(containsString("Content-Length"))); + assertThat(out, containsString("Transfer-Encoding: chunked")); + + assertThat(out, endsWith( + "\r\n\r\n"+ + "0\r\n"+ + "T-Name0: T-ValueA\r\n"+ + "T-Name0: T-ValueB\r\n"+ + "T-Name1: T-ValueC\r\n"+ + "\r\n")); + } @Test public void testResponseWithKnownContentLengthFromMetaData() throws Exception @@ -498,17 +666,17 @@ public class HttpGeneratorServerTest ByteBuffer content1 = BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. "); HttpGenerator gen = new HttpGenerator(); - HttpGenerator.Result result = gen.generateResponse(null, null, null, content0, false); + HttpGenerator.Result result = gen.generateResponse(null, false, null, null, content0, false); assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), 59); info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); - result = gen.generateResponse(info, null, null, content0, false); + result = gen.generateResponse(info, false, null, null, content0, false); assertEquals(HttpGenerator.Result.NEED_HEADER, result); assertEquals(HttpGenerator.State.START, gen.getState()); - result = gen.generateResponse(info, header, null, content0, false); + result = gen.generateResponse(info, false, header, null, content0, false); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); @@ -517,17 +685,17 @@ public class HttpGeneratorServerTest out += BufferUtil.toString(content0); BufferUtil.clear(content0); - result = gen.generateResponse(null, null, null, content1, false); + result = gen.generateResponse(null, false, null, null, content1, false); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); out += BufferUtil.toString(content1); BufferUtil.clear(content1); - result = gen.generateResponse(null, null, null, null, true); + result = gen.generateResponse(null, false, null, null, null, true); assertEquals(HttpGenerator.Result.CONTINUE, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - result = gen.generateResponse(null, null, null, null, true); + result = gen.generateResponse(null, false, null, null, null, true); assertEquals(HttpGenerator.Result.DONE, result); assertEquals(HttpGenerator.State.END, gen.getState()); @@ -546,18 +714,18 @@ public class HttpGeneratorServerTest ByteBuffer content1 = BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. "); HttpGenerator gen = new HttpGenerator(); - HttpGenerator.Result result = gen.generateResponse(null, null, null, content0, false); + HttpGenerator.Result result = gen.generateResponse(null, false, null, null, content0, false); assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), -1); info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); info.getFields().add("Content-Length",""+(content0.remaining()+content1.remaining())); - result = gen.generateResponse(info, null, null, content0, false); + result = gen.generateResponse(info, false, null, null, content0, false); assertEquals(HttpGenerator.Result.NEED_HEADER, result); assertEquals(HttpGenerator.State.START, gen.getState()); - result = gen.generateResponse(info, header, null, content0, false); + result = gen.generateResponse(info, false, header, null, content0, false); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); @@ -566,17 +734,17 @@ public class HttpGeneratorServerTest out += BufferUtil.toString(content0); BufferUtil.clear(content0); - result = gen.generateResponse(null, null, null, content1, false); + result = gen.generateResponse(null, false, null, null, content1, false); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); out += BufferUtil.toString(content1); BufferUtil.clear(content1); - result = gen.generateResponse(null, null, null, null, true); + result = gen.generateResponse(null, false, null, null, null, true); assertEquals(HttpGenerator.Result.CONTINUE, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - result = gen.generateResponse(null, null, null, null, true); + result = gen.generateResponse(null, false, null, null, null, true); assertEquals(HttpGenerator.Result.DONE, result); assertEquals(HttpGenerator.State.END, gen.getState()); @@ -598,32 +766,32 @@ public class HttpGeneratorServerTest ByteBuffer content1 = BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. "); HttpGenerator gen = new HttpGenerator(); - HttpGenerator.Result result = gen.generateResponse(HttpGenerator.CONTINUE_100_INFO, null, null, null, false); + HttpGenerator.Result result = gen.generateResponse(HttpGenerator.CONTINUE_100_INFO, false, null, null, null, false); assertEquals(HttpGenerator.Result.NEED_HEADER, result); assertEquals(HttpGenerator.State.START, gen.getState()); - result = gen.generateResponse(HttpGenerator.CONTINUE_100_INFO, header, null, null, false); + result = gen.generateResponse(HttpGenerator.CONTINUE_100_INFO, false, header, null, null, false); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMPLETING_1XX, gen.getState()); String out = BufferUtil.toString(header); - result = gen.generateResponse(null, null, null, null, false); + result = gen.generateResponse(null, false, null, null, null, false); assertEquals(HttpGenerator.Result.DONE, result); assertEquals(HttpGenerator.State.START, gen.getState()); assertThat(out, containsString("HTTP/1.1 100 Continue")); - result = gen.generateResponse(null, null, null, content0, false); + result = gen.generateResponse(null, false, null, null, content0, false); assertEquals(HttpGenerator.Result.NEED_INFO, result); assertEquals(HttpGenerator.State.START, gen.getState()); MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_1, 200, null, new HttpFields(), BufferUtil.length(content0)+BufferUtil.length(content1)); info.getFields().add("Last-Modified", DateGenerator.__01Jan1970); - result = gen.generateResponse(info, null, null, content0, false); + result = gen.generateResponse(info, false, null, null, content0, false); assertEquals(HttpGenerator.Result.NEED_HEADER, result); assertEquals(HttpGenerator.State.START, gen.getState()); - result = gen.generateResponse(info, header, null, content0, false); + result = gen.generateResponse(info, false, header, null, content0, false); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); @@ -632,17 +800,17 @@ public class HttpGeneratorServerTest out += BufferUtil.toString(content0); BufferUtil.clear(content0); - result = gen.generateResponse(null, null, null, content1, false); + result = gen.generateResponse(null, false, null, null, content1, false); assertEquals(HttpGenerator.Result.FLUSH, result); assertEquals(HttpGenerator.State.COMMITTED, gen.getState()); out += BufferUtil.toString(content1); BufferUtil.clear(content1); - result = gen.generateResponse(null, null, null, null, true); + result = gen.generateResponse(null, false, null, null, null, true); assertEquals(HttpGenerator.Result.CONTINUE, result); assertEquals(HttpGenerator.State.COMPLETING, gen.getState()); - result = gen.generateResponse(null, null, null, null, true); + result = gen.generateResponse(null, false, null, null, null, true); assertEquals(HttpGenerator.Result.DONE, result); assertEquals(HttpGenerator.State.END, gen.getState()); @@ -664,7 +832,7 @@ public class HttpGeneratorServerTest fields.add(HttpHeader.CONNECTION, customValue); MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_1_0, 200, "OK", fields, -1); ByteBuffer header = BufferUtil.allocate(4096); - HttpGenerator.Result result = generator.generateResponse(info, header, null, null, true); + HttpGenerator.Result result = generator.generateResponse(info, false, header, null, null, true); Assert.assertSame(HttpGenerator.Result.FLUSH, result); String headers = BufferUtil.toString(header); Assert.assertTrue(headers.contains(HttpHeaderValue.KEEP_ALIVE.asString())); diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpTester.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpTester.java index 03cc292e137..164e5965b09 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpTester.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpTester.java @@ -404,6 +404,10 @@ public class HttpTester chunk=BufferUtil.allocate(HttpGenerator.CHUNK_SIZE); continue; + case NEED_CHUNK_TRAILER: + chunk=BufferUtil.allocate(8192); + continue; + case NEED_INFO: throw new IllegalStateException(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index fbc79579c79..e5c146333aa 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -716,6 +716,13 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http chunk = _chunk = _bufferPool.acquire(HttpGenerator.CHUNK_SIZE, CHUNK_BUFFER_DIRECT); continue; } + case NEED_CHUNK_TRAILER: + { + if (_chunk!=null) + _bufferPool.release(_chunk); + chunk = _chunk = _bufferPool.acquire(_config.getResponseHeaderSize(), CHUNK_BUFFER_DIRECT); + continue; + } case FLUSH: { // Don't write the chunk or the content if this is a HEAD response, or any other type of response that should have no content