From dcffc52d7860884669c8d5c2acabe02728621e34 Mon Sep 17 00:00:00 2001 From: Michael Gorovoy Date: Mon, 22 Aug 2011 09:58:13 -0400 Subject: [PATCH 1/4] JETTY-1410 HTTP client handles CONTINUE 100 response correctly --- VERSION.txt | 1 + .../eclipse/jetty/client/HttpConnection.java | 19 ++- .../jetty/client/Http100ContinueTest.java | 140 ++++++++++++++++++ .../org/eclipse/jetty/http/HttpParser.java | 16 +- 4 files changed, 167 insertions(+), 9 deletions(-) create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/Http100ContinueTest.java diff --git a/VERSION.txt b/VERSION.txt index 2d6cc403992..06db974f39f 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -3,6 +3,7 @@ jetty-7.5.0.RC1 - 19 August 2011 + 335001 Eliminate expected exceptions from log when running in JBoss + 355103 Make allowCredentials default to true in CrossOriginFilter + 355162 Allow creating an empty resource collection + + JETTY-1410 HTTP client handles CONTINUE 100 response correctly + JETTY-1414 HashLoginService doesn't refresh realm if specified config filename is not an absolute platform specific value jetty-7.5.0.RC0 - 15 August 2011 diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java index fc01818e681..8b205ac6609 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java @@ -70,7 +70,7 @@ public class HttpConnection extends AbstractConnection implements Dumpable HttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endp) { super(endp); - + _generator = new HttpGenerator(requestBuffers,endp); _parser = new HttpParser(responseBuffers,endp,new Handler()); } @@ -575,10 +575,19 @@ public class HttpConnection extends AbstractConnection implements Dumpable HttpExchange exchange = _exchange; if (exchange!=null) { - // handle special case for CONNECT 200 responses - if (status==HttpStatus.OK_200 && HttpMethods.CONNECT.equalsIgnoreCase(exchange.getMethod())) - _parser.setHeadResponse(true); - + switch(status) + { + case HttpStatus.CONTINUE_100: + // handle special case for CONTINUE 100 responses + return; + + case HttpStatus.OK_200: + // handle special case for CONNECT 200 responses + if (HttpMethods.CONNECT.equalsIgnoreCase(exchange.getMethod())) + _parser.setHeadResponse(true); + break; + } + _http11 = HttpVersions.HTTP_1_1_BUFFER.equals(version); _status=status; exchange.getEventListener().onResponseStatus(version,status,reason); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/Http100ContinueTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/Http100ContinueTest.java new file mode 100644 index 00000000000..f9b6eaa8155 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/Http100ContinueTest.java @@ -0,0 +1,140 @@ +// ======================================================================== +// Copyright (c) Webtide LLC +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + +package org.eclipse.jetty.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + + +/* ------------------------------------------------------------ */ +public class Http100ContinueTest +{ + private static final String CONTENT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. " + + "Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque " + + "habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. " + + "Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam " + + "at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate " + + "velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum. " + + "Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum " + + "eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa " + + "sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam " + + "consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque. " + + "Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse " + + "et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque."; + + private Server _server; + private TestHandler _handler; + private int _port; + + @Before + public void init() throws Exception + { + File docRoot = new File("target/test-output/docroot/"); + if (!docRoot.exists()) + assertTrue(docRoot.mkdirs()); + docRoot.deleteOnExit(); + + _server = new Server(); + Connector connector = new SelectChannelConnector(); + _server.addConnector(connector); + + _handler = new TestHandler(); + _server.setHandler(_handler); + + _server.start(); + + _port = connector.getLocalPort(); + } + + @After + public void destroy() throws Exception + { + _server.stop(); + _server.join(); + } + + @Test + public void testSelectConnector() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + httpClient.start(); + try + { + String requestUrl = "http://localhost:" + _port + "/header"; + + ContentExchange exchange = new ContentExchange(); + exchange.setURL(requestUrl); + exchange.setMethod(HttpMethods.GET); + exchange.addRequestHeader("User-Agent","Jetty-Client/7.0"); + exchange.addRequestHeader("Expect","100-continue"); //server to send CONTINUE 100 + + httpClient.send(exchange); + + int state = exchange.waitForDone(); + assertEquals(HttpExchange.STATUS_COMPLETED, state); + int responseStatus = exchange.getResponseStatus(); + assertEquals(HttpStatus.OK_200,responseStatus); + + String content = exchange.getResponseContent(); + + assertEquals(Http100ContinueTest.CONTENT,content); + } + finally + { + httpClient.stop(); + } + } + + private static class TestHandler extends AbstractHandler + { + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (baseRequest.isHandled()) + return; + + // force 100 Continue response to be sent + request.getInputStream(); + + response.setContentType("text/plain"); + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().print(CONTENT); + + baseRequest.setHandled(true); + } + + } +} 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 4922b82c90b..be18ca7a2de 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 @@ -557,10 +557,18 @@ public class HttpParser implements Parser break; case HttpTokens.NO_CONTENT: - _state=STATE_END; - returnBuffers(); - _handler.headerComplete(); - _handler.messageComplete(_contentPosition); + if (_responseStatus != 100) // continue + { + _state=STATE_END; + returnBuffers(); + _handler.headerComplete(); + _handler.messageComplete(_contentPosition); + } + else + { + reset(); + } + break; default: From 07b8f08a37198aecdf0557640c5721870d29d6f8 Mon Sep 17 00:00:00 2001 From: Michael Gorovoy Date: Mon, 22 Aug 2011 09:58:13 -0400 Subject: [PATCH 2/4] JETTY-1410 HTTP client handles CONTINUE 100 response correctly --- VERSION.txt | 1 + .../eclipse/jetty/client/HttpConnection.java | 19 ++- .../jetty/client/Http100ContinueTest.java | 140 ++++++++++++++++++ .../org/eclipse/jetty/http/HttpParser.java | 16 +- 4 files changed, 167 insertions(+), 9 deletions(-) create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/Http100ContinueTest.java diff --git a/VERSION.txt b/VERSION.txt index 3deb8c528aa..237144ab7f4 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -5,6 +5,7 @@ jetty-7.5.0.RC1 - 19 August 2011 + 335001 Eliminate expected exceptions from log when running in JBoss + 355103 Make allowCredentials default to true in CrossOriginFilter + 355162 Allow creating an empty resource collection + + JETTY-1410 HTTP client handles CONTINUE 100 response correctly + JETTY-1414 HashLoginService doesn't refresh realm if specified config filename is not an absolute platform specific value jetty-7.5.0.RC0 - 15 August 2011 diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java index fc01818e681..8b205ac6609 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java @@ -70,7 +70,7 @@ public class HttpConnection extends AbstractConnection implements Dumpable HttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endp) { super(endp); - + _generator = new HttpGenerator(requestBuffers,endp); _parser = new HttpParser(responseBuffers,endp,new Handler()); } @@ -575,10 +575,19 @@ public class HttpConnection extends AbstractConnection implements Dumpable HttpExchange exchange = _exchange; if (exchange!=null) { - // handle special case for CONNECT 200 responses - if (status==HttpStatus.OK_200 && HttpMethods.CONNECT.equalsIgnoreCase(exchange.getMethod())) - _parser.setHeadResponse(true); - + switch(status) + { + case HttpStatus.CONTINUE_100: + // handle special case for CONTINUE 100 responses + return; + + case HttpStatus.OK_200: + // handle special case for CONNECT 200 responses + if (HttpMethods.CONNECT.equalsIgnoreCase(exchange.getMethod())) + _parser.setHeadResponse(true); + break; + } + _http11 = HttpVersions.HTTP_1_1_BUFFER.equals(version); _status=status; exchange.getEventListener().onResponseStatus(version,status,reason); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/Http100ContinueTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/Http100ContinueTest.java new file mode 100644 index 00000000000..f9b6eaa8155 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/Http100ContinueTest.java @@ -0,0 +1,140 @@ +// ======================================================================== +// Copyright (c) Webtide LLC +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + + +package org.eclipse.jetty.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpMethods; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + + +/* ------------------------------------------------------------ */ +public class Http100ContinueTest +{ + private static final String CONTENT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. " + + "Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque " + + "habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. " + + "Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam " + + "at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate " + + "velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum. " + + "Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum " + + "eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa " + + "sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam " + + "consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque. " + + "Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse " + + "et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque."; + + private Server _server; + private TestHandler _handler; + private int _port; + + @Before + public void init() throws Exception + { + File docRoot = new File("target/test-output/docroot/"); + if (!docRoot.exists()) + assertTrue(docRoot.mkdirs()); + docRoot.deleteOnExit(); + + _server = new Server(); + Connector connector = new SelectChannelConnector(); + _server.addConnector(connector); + + _handler = new TestHandler(); + _server.setHandler(_handler); + + _server.start(); + + _port = connector.getLocalPort(); + } + + @After + public void destroy() throws Exception + { + _server.stop(); + _server.join(); + } + + @Test + public void testSelectConnector() throws Exception + { + HttpClient httpClient = new HttpClient(); + httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + httpClient.start(); + try + { + String requestUrl = "http://localhost:" + _port + "/header"; + + ContentExchange exchange = new ContentExchange(); + exchange.setURL(requestUrl); + exchange.setMethod(HttpMethods.GET); + exchange.addRequestHeader("User-Agent","Jetty-Client/7.0"); + exchange.addRequestHeader("Expect","100-continue"); //server to send CONTINUE 100 + + httpClient.send(exchange); + + int state = exchange.waitForDone(); + assertEquals(HttpExchange.STATUS_COMPLETED, state); + int responseStatus = exchange.getResponseStatus(); + assertEquals(HttpStatus.OK_200,responseStatus); + + String content = exchange.getResponseContent(); + + assertEquals(Http100ContinueTest.CONTENT,content); + } + finally + { + httpClient.stop(); + } + } + + private static class TestHandler extends AbstractHandler + { + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (baseRequest.isHandled()) + return; + + // force 100 Continue response to be sent + request.getInputStream(); + + response.setContentType("text/plain"); + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().print(CONTENT); + + baseRequest.setHandled(true); + } + + } +} 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 4922b82c90b..be18ca7a2de 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 @@ -557,10 +557,18 @@ public class HttpParser implements Parser break; case HttpTokens.NO_CONTENT: - _state=STATE_END; - returnBuffers(); - _handler.headerComplete(); - _handler.messageComplete(_contentPosition); + if (_responseStatus != 100) // continue + { + _state=STATE_END; + returnBuffers(); + _handler.headerComplete(); + _handler.messageComplete(_contentPosition); + } + else + { + reset(); + } + break; default: From da517c815ac86104ace4d79acfdb60dbcfd33f35 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Tue, 23 Aug 2011 17:07:31 +1000 Subject: [PATCH 3/4] JETTY-1410 handle 1xx in similar fashion to 401s and 302s --- .../eclipse/jetty/client/HttpConnection.java | 72 ++++++++++++++++++- .../eclipse/jetty/client/HttpExchange.java | 15 ++-- .../org/eclipse/jetty/http/HttpParser.java | 16 ++--- 3 files changed, 84 insertions(+), 19 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java index 8b205ac6609..f7fdb7b7f17 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java @@ -571,15 +571,16 @@ public class HttpConnection extends AbstractConnection implements Dumpable @Override public void startResponse(Buffer version, int status, Buffer reason) throws IOException { - HttpExchange exchange = _exchange; if (exchange!=null) { switch(status) { case HttpStatus.CONTINUE_100: - // handle special case for CONTINUE 100 responses - return; + case HttpStatus.PROCESSING_102: + // TODO check if appropriate expect was sent in the request. + exchange.setEventListener(new NonFinalResponseListener(exchange)); + break; case HttpStatus.OK_200: // handle special case for CONNECT 200 responses @@ -755,4 +756,69 @@ public class HttpConnection extends AbstractConnection implements Dumpable } } } + + + private class NonFinalResponseListener implements HttpEventListener + { + final HttpExchange _exchange; + final HttpEventListener _next; + + public NonFinalResponseListener(HttpExchange exchange) + { + _exchange=exchange; + _next=exchange.getEventListener(); + } + + public void onRequestCommitted() throws IOException + { + } + + public void onRequestComplete() throws IOException + { + } + + public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException + { + } + + public void onResponseHeader(Buffer name, Buffer value) throws IOException + { + } + + public void onResponseHeaderComplete() throws IOException + { + } + + public void onResponseContent(Buffer content) throws IOException + { + } + + public void onResponseComplete() throws IOException + { + _exchange.setEventListener(_next); + _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE); + _parser.reset(); + } + + public void onConnectionFailed(Throwable ex) + { + } + + public void onException(Throwable ex) + { + _exchange.setEventListener(_next); + _next.onException(ex); + } + + public void onExpire() + { + _exchange.setEventListener(_next); + _next.onExpire(); + } + + public void onRetry() + { + } + + } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java index f9454cceda1..d4de784909a 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java @@ -302,6 +302,7 @@ public class HttpExchange { case STATUS_START: case STATUS_EXCEPTED: + case STATUS_WAITING_FOR_RESPONSE: set=_status.compareAndSet(oldStatus,newStatus); break; case STATUS_CANCELLING: @@ -344,7 +345,7 @@ public class HttpExchange } if (!set) - throw new IllegalStateException(oldStatus + " => " + newStatus); + throw new IllegalStateException(toState(oldStatus) + " => " + toState(newStatus)); } catch (IOException x) { @@ -726,11 +727,10 @@ public class HttpExchange return result; } - @Override - public String toString() + public static String toState(int s) { String state; - switch(getStatus()) + switch(s) { case STATUS_START: state="START"; break; case STATUS_WAITING_FOR_CONNECTION: state="CONNECTING"; break; @@ -746,6 +746,13 @@ public class HttpExchange case STATUS_CANCELLED: state="CANCELLED"; break; default: state="UNKNOWN"; } + return state; + } + + @Override + public String toString() + { + String state=toState(getStatus()); long now=System.currentTimeMillis(); long forMs = now -_lastStateChange; String s= String.format("%s@%x=%s//%s%s#%s(%dms)",getClass().getSimpleName(),hashCode(),_method,_address,_uri,state,forMs); 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 be18ca7a2de..4922b82c90b 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 @@ -557,18 +557,10 @@ public class HttpParser implements Parser break; case HttpTokens.NO_CONTENT: - if (_responseStatus != 100) // continue - { - _state=STATE_END; - returnBuffers(); - _handler.headerComplete(); - _handler.messageComplete(_contentPosition); - } - else - { - reset(); - } - + _state=STATE_END; + returnBuffers(); + _handler.headerComplete(); + _handler.messageComplete(_contentPosition); break; default: From 5aced832872d87121535cfd7460c71fd7eca2107 Mon Sep 17 00:00:00 2001 From: Michael Gorovoy Date: Tue, 23 Aug 2011 16:56:05 -0400 Subject: [PATCH 4/4] Added tests for missing CONTINUE 100, error response, and timeout --- .../eclipse/jetty/client/HttpConnection.java | 22 +- .../jetty/client/Http100ContinueTest.java | 196 +++++++++++++----- 2 files changed, 169 insertions(+), 49 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java index f7fdb7b7f17..d6d6d60a615 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java @@ -744,8 +744,10 @@ public class HttpConnection extends AbstractConnection implements Dumpable } } + /* ------------------------------------------------------------ */ private class ConnectionIdleTask extends Timeout.Task { + /* ------------------------------------------------------------ */ @Override public void expired() { @@ -758,41 +760,52 @@ public class HttpConnection extends AbstractConnection implements Dumpable } + /* ------------------------------------------------------------ */ private class NonFinalResponseListener implements HttpEventListener { final HttpExchange _exchange; final HttpEventListener _next; + /* ------------------------------------------------------------ */ public NonFinalResponseListener(HttpExchange exchange) { _exchange=exchange; _next=exchange.getEventListener(); } + /* ------------------------------------------------------------ */ public void onRequestCommitted() throws IOException { } + /* ------------------------------------------------------------ */ public void onRequestComplete() throws IOException { } + /* ------------------------------------------------------------ */ public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException { } + /* ------------------------------------------------------------ */ public void onResponseHeader(Buffer name, Buffer value) throws IOException { + _next.onResponseHeader(name,value); } + /* ------------------------------------------------------------ */ public void onResponseHeaderComplete() throws IOException { + _next.onResponseHeaderComplete(); } + /* ------------------------------------------------------------ */ public void onResponseContent(Buffer content) throws IOException { } + /* ------------------------------------------------------------ */ public void onResponseComplete() throws IOException { _exchange.setEventListener(_next); @@ -800,25 +813,32 @@ public class HttpConnection extends AbstractConnection implements Dumpable _parser.reset(); } + /* ------------------------------------------------------------ */ public void onConnectionFailed(Throwable ex) { + _exchange.setEventListener(_next); + _next.onConnectionFailed(ex); } + /* ------------------------------------------------------------ */ public void onException(Throwable ex) { _exchange.setEventListener(_next); _next.onException(ex); } + /* ------------------------------------------------------------ */ public void onExpire() { _exchange.setEventListener(_next); _next.onExpire(); } + /* ------------------------------------------------------------ */ public void onRetry() { + _exchange.setEventListener(_next); + _next.onRetry(); } - } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/Http100ContinueTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/Http100ContinueTest.java index f9b6eaa8155..b764de3c788 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/Http100ContinueTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/Http100ContinueTest.java @@ -19,9 +19,8 @@ import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -34,14 +33,17 @@ import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.nio.SelectChannelConnector; -import org.junit.After; -import org.junit.Before; +import org.eclipse.jetty.util.log.Log; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; /* ------------------------------------------------------------ */ public class Http100ContinueTest { + private static final int TIMEOUT = 500; + private static final String CONTENT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. " + "Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque " + "habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. " @@ -55,12 +57,15 @@ public class Http100ContinueTest + "Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse " + "et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque."; - private Server _server; - private TestHandler _handler; - private int _port; - - @Before - public void init() throws Exception + private static TestFeature _feature; + + private static Server _server; + private static TestHandler _handler; + private static HttpClient _client; + private static String _requestUrl; + + @BeforeClass + public static void init() throws Exception { File docRoot = new File("target/test-output/docroot/"); if (!docRoot.exists()) @@ -76,47 +81,114 @@ public class Http100ContinueTest _server.start(); - _port = connector.getLocalPort(); + _client = new HttpClient(); + _client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + _client.setTimeout(TIMEOUT); + _client.setMaxRetries(0); + _client.start(); + + _requestUrl = "http://localhost:" + connector.getLocalPort() + "/"; + } - @After - public void destroy() throws Exception + @AfterClass + public static void destroy() throws Exception { + _client.stop(); + _server.stop(); _server.join(); } @Test - public void testSelectConnector() throws Exception + public void testSuccess() throws Exception { - HttpClient httpClient = new HttpClient(); - httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); - httpClient.start(); - try + // Handler to send CONTINUE 100 + _feature = TestFeature.CONTINUE; + + ContentExchange exchange = sendExchange(); + + int state = exchange.waitForDone(); + assertEquals(HttpExchange.STATUS_COMPLETED, state); + + int responseStatus = exchange.getResponseStatus(); + assertEquals(HttpStatus.OK_200,responseStatus); + + String content = exchange.getResponseContent(); + assertEquals(Http100ContinueTest.CONTENT,content); + } + + @Test + public void testMissingContinue() throws Exception + { + // Handler does not send CONTINUE 100 + _feature = TestFeature.NORMAL; + + ContentExchange exchange = sendExchange(); + + int state = exchange.waitForDone(); + assertEquals(HttpExchange.STATUS_COMPLETED, state); + + int responseStatus = exchange.getResponseStatus(); + assertEquals(HttpStatus.OK_200,responseStatus); + + String content = exchange.getResponseContent(); + assertEquals(Http100ContinueTest.CONTENT,content); + } + + @Test + public void testError() throws Exception + { + // Handler sends NOT FOUND 404 response + _feature = TestFeature.NOTFOUND; + + ContentExchange exchange = sendExchange(); + + int state = exchange.waitForDone(); + assertEquals(HttpExchange.STATUS_COMPLETED, state); + + int responseStatus = exchange.getResponseStatus(); + assertEquals(HttpStatus.NOT_FOUND_404,responseStatus); + } + + @Test + public void testTimeout() throws Exception + { + // Handler delays response till client times out + _feature = TestFeature.TIMEOUT; + + final CountDownLatch expires = new CountDownLatch(1); + ContentExchange exchange = new ContentExchange() { - String requestUrl = "http://localhost:" + _port + "/header"; + @Override + protected void onExpire() + { + expires.countDown(); + } + }; + + configureExchange(exchange); + _client.send(exchange); + + assertTrue(expires.await(TIMEOUT*10,TimeUnit.MILLISECONDS)); + } + + public ContentExchange sendExchange() throws Exception + { + ContentExchange exchange = new ContentExchange(); + + configureExchange(exchange); + _client.send(exchange); + + return exchange; + } - ContentExchange exchange = new ContentExchange(); - exchange.setURL(requestUrl); - exchange.setMethod(HttpMethods.GET); - exchange.addRequestHeader("User-Agent","Jetty-Client/7.0"); - exchange.addRequestHeader("Expect","100-continue"); //server to send CONTINUE 100 - - httpClient.send(exchange); - - int state = exchange.waitForDone(); - assertEquals(HttpExchange.STATUS_COMPLETED, state); - int responseStatus = exchange.getResponseStatus(); - assertEquals(HttpStatus.OK_200,responseStatus); - - String content = exchange.getResponseContent(); - - assertEquals(Http100ContinueTest.CONTENT,content); - } - finally - { - httpClient.stop(); - } + public void configureExchange(ContentExchange exchange) + { + exchange.setURL(_requestUrl); + exchange.setMethod(HttpMethods.GET); + exchange.addRequestHeader("User-Agent","Jetty-Client/7.0"); + exchange.addRequestHeader("Expect","100-continue"); //server to send CONTINUE 100 } private static class TestHandler extends AbstractHandler @@ -126,15 +198,43 @@ public class Http100ContinueTest if (baseRequest.isHandled()) return; - // force 100 Continue response to be sent - request.getInputStream(); - - response.setContentType("text/plain"); - response.setStatus(HttpServletResponse.SC_OK); - response.getWriter().print(CONTENT); - baseRequest.setHandled(true); + + switch (_feature) + { + case CONTINUE: + // force 100 Continue response to be sent + request.getInputStream(); + // next send data + + case NORMAL: + response.setContentType("text/plain"); + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().print(CONTENT); + break; + + case NOTFOUND: + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + break; + + case TIMEOUT: + try + { + Thread.sleep(TIMEOUT*4); + } + catch (InterruptedException ex) + { + Log.ignore(ex); + } + break; + } } + } + enum TestFeature { + CONTINUE, + NORMAL, + NOTFOUND, + TIMEOUT } }