From 461c6082aee62fc030b7535d3ea477ece61582ef Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 1 Feb 2017 19:18:16 +1100 Subject: [PATCH] Issue #1296 - Introduce HTTP parser "content complete" event. --- .../client/http/HttpReceiverOverHTTP.java | 7 ++ .../fcgi/parser/ResponseContentParser.java | 6 ++ .../fcgi/server/ServerFCGIConnection.java | 1 + .../org/eclipse/jetty/http/HttpParser.java | 53 ++++++++++----- .../http/HttpGeneratorServerHTTPTest.java | 6 ++ .../eclipse/jetty/http/HttpParserTest.java | 6 ++ .../org/eclipse/jetty/http/HttpTester.java | 6 ++ .../http2/server/HttpChannelOverHTTP2.java | 10 ++- .../org/eclipse/jetty/server/HttpChannel.java | 7 ++ .../jetty/server/HttpChannelOverHttp.java | 10 ++- .../eclipse/jetty/server/LocalConnector.java | 6 ++ .../jetty/server/HttpConnectionTest.java | 66 ++++++++++++++++++- 12 files changed, 161 insertions(+), 23 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java index c3c26e8a308..c006363dcda 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java @@ -260,6 +260,13 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res return !proceed || async; } + + @Override + public boolean contentComplete() + { + return false; + } + @Override public boolean messageComplete() { diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java index f28155382bd..8a186b792fe 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java @@ -284,6 +284,12 @@ public class ResponseContentParser extends StreamContentParser } } + @Override + public boolean contentComplete() + { + return false; + } + @Override public boolean messageComplete() { diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java index 9f5c44ab2ef..19e570118fc 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java @@ -175,6 +175,7 @@ public class ServerFCGIConnection extends AbstractConnection LOG.debug("Request {} end on {}", request, channel); if (channel != null) { + channel.onContentComplete(); channel.onRequestComplete(); } } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index 04b3174174c..d0a252b861d 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -582,7 +582,25 @@ public class HttpParser _length=-1; return s; } + + /* ------------------------------------------------------------------------------- */ + private boolean handleHeaderContentMessage() + { + boolean handle_header = _handler.headerComplete(); + boolean handle_content = _handler.contentComplete(); + boolean handle_message = _handler.messageComplete(); + return handle_header || handle_content || handle_message; + } + + /* ------------------------------------------------------------------------------- */ + private boolean handleContentMessage() + { + boolean handle_content = _handler.contentComplete(); + boolean handle_message = _handler.messageComplete(); + return handle_content || handle_message; + } + /* ------------------------------------------------------------------------------- */ /* Parse a request or response line */ @@ -730,10 +748,7 @@ public class HttpParser handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9); setState(State.END); BufferUtil.clear(buffer); - handle=_handler.headerComplete()||handle; - _headerComplete=true; - handle=_handler.messageComplete()||handle; - return handle; + handle = handleHeaderContentMessage() || handle; } else { @@ -801,10 +816,7 @@ public class HttpParser handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9); setState(State.END); BufferUtil.clear(buffer); - handle=_handler.headerComplete()||handle; - _headerComplete=true; - handle=_handler.messageComplete()||handle; - return handle; + handle = handleHeaderContentMessage() || handle; } } else if (ch<0) @@ -1071,10 +1083,7 @@ public class HttpParser case NO_CONTENT: setState(State.END); - handle=_handler.headerComplete()||handle; - _headerComplete=true; - handle=_handler.messageComplete()||handle; - return handle; + return handleHeaderContentMessage(); default: setState(State.CONTENT); @@ -1334,7 +1343,7 @@ public class HttpParser if (_responseStatus>0 && _headResponse) { setState(State.END); - return _handler.messageComplete(); + return handleContentMessage(); } else { @@ -1391,7 +1400,7 @@ public class HttpParser case EOF_CONTENT: case CHUNK_END: setState(State.CLOSED); - return _handler.messageComplete(); + return handleContentMessage(); case CONTENT: case CHUNKED_CONTENT: @@ -1467,7 +1476,7 @@ public class HttpParser if (content == 0) { setState(State.END); - return _handler.messageComplete(); + return handleContentMessage(); } } @@ -1491,7 +1500,7 @@ public class HttpParser if (content == 0) { setState(State.END); - return _handler.messageComplete(); + return handleContentMessage(); } else { @@ -1514,7 +1523,7 @@ public class HttpParser if(_contentPosition == _contentLength) { setState(State.END); - return _handler.messageComplete(); + return handleContentMessage(); } } break; @@ -1541,7 +1550,11 @@ public class HttpParser if (ch == HttpTokens.LINE_FEED) { if (_chunkLength == 0) + { setState(State.CHUNK_END); + if (_handler.contentComplete()) + return true; + } else setState(State.CHUNK); } @@ -1558,7 +1571,11 @@ public class HttpParser if (ch == HttpTokens.LINE_FEED) { if (_chunkLength == 0) + { setState(State.CHUNK_END); + if (_handler.contentComplete()) + return true; + } else setState(State.CHUNK); } @@ -1736,6 +1753,8 @@ public class HttpParser public boolean headerComplete(); + public boolean contentComplete(); + public boolean messageComplete(); /** 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 5f8313e9c9e..288d8c73324 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 @@ -231,6 +231,12 @@ public class HttpGeneratorServerHTTPTest return false; } + @Override + public boolean contentComplete() + { + return true; + } + @Override public boolean messageComplete() { diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java index 00ee9eada29..f4324aab6ef 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java @@ -1986,6 +1986,12 @@ public class HttpParserTest return false; } + @Override + public boolean contentComplete() + { + return false; + } + @Override public boolean messageComplete() { 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 0cc1a422499..7100621dbec 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 @@ -300,6 +300,12 @@ public class HttpTester add(field.getName(),field.getValue()); } + @Override + public boolean contentComplete() + { + return false; + } + @Override public boolean messageComplete() { diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java index fab3ce1deda..c8250890098 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java @@ -114,7 +114,10 @@ public class HttpChannelOverHTTP2 extends HttpChannel boolean endStream = frame.isEndStream(); if (endStream) + { + onContentComplete(); onRequestComplete(); + } _delayedUntilContent = getHttpConfiguration().isDelayDispatchUntilContent() && !endStream && !_expect100Continue; @@ -150,6 +153,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel { onRequest(request); getRequest().setAttribute("org.eclipse.jetty.pushed", Boolean.TRUE); + onContentComplete(); onRequestComplete(); if (LOG.isDebugEnabled()) @@ -255,7 +259,11 @@ public class HttpChannelOverHTTP2 extends HttpChannel boolean endStream = frame.isEndStream(); if (endStream) - handle |= onRequestComplete(); + { + boolean handle_content = onContentComplete(); + boolean handle_request = onRequestComplete(); + handle |= handle_content | handle_request; + } if (LOG.isDebugEnabled()) { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index 0b405fab14a..0965cf98987 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -624,6 +624,13 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor return _request.getHttpInput().addContent(content); } + public boolean onContentComplete() + { + if (LOG.isDebugEnabled()) + LOG.debug("{} onContentComplete", this); + return false; + } + public boolean onRequestComplete() { if (LOG.isDebugEnabled()) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java index 81683e23547..4cde234faa5 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java @@ -458,13 +458,19 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque } @Override - public boolean messageComplete() + public boolean contentComplete() { - boolean handle = onRequestComplete() || _delayedForContent; + boolean handle = onContentComplete() || _delayedForContent; _delayedForContent = false; return handle; } + @Override + public boolean messageComplete() + { + return onRequestComplete(); + } + @Override public int getHeaderCacheSize() { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java index 013b57a4620..7788ec40249 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java @@ -424,6 +424,12 @@ public class LocalConnector extends AbstractConnector public void parsedHeader(HttpField field) { } + + @Override + public boolean contentComplete() + { + return false; + } @Override public boolean messageComplete() diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java index 3a0e69f2276..c72fd1a2038 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java @@ -152,7 +152,7 @@ public class HttpConnectionTest @Test public void testDate() throws Exception { - String response=connector.getResponses("GET / HTTP/1.1\r\n"+ + String response=connector.getResponse("GET / HTTP/1.1\r\n"+ "Host: localhost:80\r\n"+ "Connection: close\r\n"+ "\r\n"); @@ -265,7 +265,7 @@ public class HttpConnectionTest @Test public void testEmptyChunk() throws Exception { - String response=connector.getResponses("GET /R1 HTTP/1.1\r\n"+ + String response=connector.getResponse("GET /R1 HTTP/1.1\r\n"+ "Host: localhost\r\n"+ "Transfer-Encoding: chunked\r\n"+ "Content-Type: text/plain\r\n"+ @@ -279,6 +279,66 @@ public class HttpConnectionTest checkContains(response,offset,"/R1"); } + @Test + public void testChunk() throws Exception + { + String response=connector.getResponse("GET /R1 HTTP/1.1\r\n"+ + "Host: localhost\r\n"+ + "Transfer-Encoding: chunked\r\n"+ + "Content-Type: text/plain\r\n"+ + "Connection: close\r\n"+ + "\r\n"+ + "A\r\n" + + "0123456789\r\n"+ + "0\r\n" + + "\r\n"); + + int offset=0; + offset = checkContains(response,offset,"HTTP/1.1 200"); + offset = checkContains(response,offset,"/R1"); + checkContains(response,offset,"0123456789"); + } + + @Test + public void testChunkTrailer() throws Exception + { + String response=connector.getResponse("GET /R1 HTTP/1.1\r\n"+ + "Host: localhost\r\n"+ + "Transfer-Encoding: chunked\r\n"+ + "Content-Type: text/plain\r\n"+ + "Connection: close\r\n"+ + "\r\n"+ + "A\r\n" + + "0123456789\r\n"+ + "0\r\n" + + "Trailer: ignored\r\n" + + "\r\n"); + + int offset=0; + offset = checkContains(response,offset,"HTTP/1.1 200"); + offset = checkContains(response,offset,"/R1"); + checkContains(response,offset,"0123456789"); + } + + @Test + public void testChunkNoTrailer() throws Exception + { + String response=connector.getResponse("GET /R1 HTTP/1.1\r\n"+ + "Host: localhost\r\n"+ + "Transfer-Encoding: chunked\r\n"+ + "Content-Type: text/plain\r\n"+ + "Connection: close\r\n"+ + "\r\n"+ + "A\r\n" + + "0123456789\r\n"+ + "0\r\n"); + + int offset=0; + offset = checkContains(response,offset,"HTTP/1.1 200"); + offset = checkContains(response,offset,"/R1"); + checkContains(response,offset,"0123456789"); + } + @Test public void testHead() throws Exception { @@ -723,7 +783,7 @@ public class HttpConnectionTest try { int offset=0; - response=connector.getResponses("GET /R1 HTTP/1.1\r\n"+ + response=connector.getResponse("GET /R1 HTTP/1.1\r\n"+ "Host: localhost\r\n"+ "Connection: TE, close\r\n"+ "Transfer-Encoding: chunked\r\n"+