Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGreg Wilkins2012-10-12 02:05:31 +0000
committerGreg Wilkins2012-10-12 02:05:31 +0000
commitd173e91ef5014e40225749196b1a6844826ff54f (patch)
treeda03508e95b925627750052ce7498d9b4607a561
parent8c3fa91b261d0a7caf151959d0bbcee2b17e6f40 (diff)
parentb6fcdf0f8ff3253cd3a6acc4b5fcb3ff5918e2b8 (diff)
downloadorg.eclipse.jetty.project-d173e91ef5014e40225749196b1a6844826ff54f.tar.gz
org.eclipse.jetty.project-d173e91ef5014e40225749196b1a6844826ff54f.tar.xz
org.eclipse.jetty.project-d173e91ef5014e40225749196b1a6844826ff54f.zip
Merge branch 'jetty-9' of ssh://git.eclipse.org/gitroot/jetty/org.eclipse.jetty.project into jetty-9
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java42
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java106
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java16
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java7
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/HttpContentResponse.java17
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java13
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java3
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java39
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java8
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java2
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java6
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java197
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java9
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java42
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java2
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java5
-rw-r--r--jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientContinueTest.java440
-rw-r--r--jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java4
-rw-r--r--jetty-distribution/pom.xml2
-rw-r--r--jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java27
-rw-r--r--jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java3
-rw-r--r--jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java15
-rw-r--r--jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ProtocolNegotiationTest.java15
-rw-r--r--jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYTest.java15
24 files changed, 906 insertions, 129 deletions
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
index 16c779fa20..5cc8e05783 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
@@ -18,7 +18,6 @@
package org.eclipse.jetty.client;
-import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -40,9 +39,9 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class);
private static final Pattern WWW_AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+realm=\"([^\"]+)\".*", Pattern.CASE_INSENSITIVE);
- private final ResponseNotifier notifier = new ResponseNotifier();
private final HttpClient client;
private final int maxContentLength;
+ private final ResponseNotifier notifier;
public AuthenticationProtocolHandler(HttpClient client)
{
@@ -53,6 +52,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
{
this.client = client;
this.maxContentLength = maxContentLength;
+ this.notifier = new ResponseNotifier(client);
}
@Override
@@ -64,6 +64,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
@Override
public Response.Listener getResponseListener()
{
+ // Return new instances every time to keep track of the response content
return new AuthenticationListener();
}
@@ -78,12 +79,14 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
public void onComplete(Result result)
{
Request request = result.getRequest();
+ HttpConversation conversation = client.getConversation(request.conversation());
+ Response.Listener listener = conversation.exchanges().peekFirst().listener();
ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getEncoding());
if (result.isFailed())
{
Throwable failure = result.getFailure();
LOG.debug("Authentication challenge failed {}", failure);
- forwardFailure(request, response, failure);
+ notifier.forwardFailureComplete(listener, request, result.getRequestFailure(), response, result.getResponseFailure());
return;
}
@@ -91,7 +94,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
if (wwwAuthenticates.isEmpty())
{
LOG.debug("Authentication challenge without WWW-Authenticate header");
- forwardFailure(request, response, new HttpResponseException("HTTP protocol violation: 401 without WWW-Authenticate header", response));
+ notifier.forwardFailureComplete(listener, request, null, response, new HttpResponseException("HTTP protocol violation: 401 without WWW-Authenticate header", response));
return;
}
@@ -110,16 +113,15 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
if (authentication == null)
{
LOG.debug("No authentication available for {}", request);
- forwardSuccess(request, response);
+ notifier.forwardSuccessComplete(listener, request, response);
return;
}
- HttpConversation conversation = client.getConversation(request);
final Authentication.Result authnResult = authentication.authenticate(request, response, wwwAuthenticate.value, conversation);
LOG.debug("Authentication result {}", authnResult);
if (authnResult == null)
{
- forwardSuccess(request, response);
+ notifier.forwardSuccessComplete(listener, request, response);
return;
}
@@ -134,32 +136,6 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
});
}
- private void forwardFailure(Request request, Response response, Throwable failure)
- {
- HttpConversation conversation = client.getConversation(request);
- Response.Listener listener = conversation.exchanges().peekFirst().listener();
- notifier.notifyBegin(listener, response);
- notifier.notifyHeaders(listener, response);
- if (response instanceof ContentResponse)
- notifier.notifyContent(listener, response, ByteBuffer.wrap(((ContentResponse)response).content()));
- notifier.notifyFailure(listener, response, failure);
- conversation.complete();
- notifier.notifyComplete(listener, new Result(request, response, failure));
- }
-
- private void forwardSuccess(Request request, Response response)
- {
- HttpConversation conversation = client.getConversation(request);
- Response.Listener listener = conversation.exchanges().peekFirst().listener();
- notifier.notifyBegin(listener, response);
- notifier.notifyHeaders(listener, response);
- if (response instanceof ContentResponse)
- notifier.notifyContent(listener, response, ByteBuffer.wrap(((ContentResponse)response).content()));
- notifier.notifySuccess(listener, response);
- conversation.complete();
- notifier.notifyComplete(listener, new Result(request, response));
- }
-
private List<WWWAuthenticate> parseWWWAuthenticate(Response response)
{
// TODO: these should be ordered by strength
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java
new file mode 100644
index 0000000000..2d12c70437
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ContinueProtocolHandler.java
@@ -0,0 +1,106 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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 org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+
+public class ContinueProtocolHandler implements ProtocolHandler
+{
+ private static final String ATTRIBUTE = ContinueProtocolHandler.class.getName() + ".100continue";
+
+ private final HttpClient client;
+ private final ResponseNotifier notifier;
+
+ public ContinueProtocolHandler(HttpClient client)
+ {
+ this.client = client;
+ this.notifier = new ResponseNotifier(client);
+ }
+
+ @Override
+ public boolean accept(Request request, Response response)
+ {
+ boolean expect100 = request.headers().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
+ boolean handled100 = client.getConversation(request.conversation()).getAttribute(ATTRIBUTE) != null;
+ return expect100 && !handled100;
+ }
+
+ @Override
+ public Response.Listener getResponseListener()
+ {
+ // Return new instances every time to keep track of the response content
+ return new ContinueListener();
+ }
+
+ private class ContinueListener extends BufferingResponseListener
+ {
+ @Override
+ public void onSuccess(Response response)
+ {
+ // Handling of success must be done here and not from onComplete(),
+ // since the onComplete() is not invoked because the request is not completed yet.
+
+ HttpConversation conversation = client.getConversation(response.conversation());
+ // Mark the 100 Continue response as handled
+ conversation.setAttribute(ATTRIBUTE, Boolean.TRUE);
+
+ HttpExchange exchange = conversation.exchanges().peekLast();
+ assert exchange.response() == response;
+ Response.Listener listener = exchange.listener();
+ switch (response.status())
+ {
+ case 100:
+ {
+ // All good, continue
+ exchange.resetResponse(true);
+ conversation.listener(listener);
+ exchange.proceed(true);
+ break;
+ }
+ default:
+ {
+ // Server either does not support 100 Continue, or it does and wants to refuse the request content
+ HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getEncoding());
+ notifier.forwardSuccess(listener, contentResponse);
+ conversation.listener(listener);
+ exchange.proceed(false);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onFailure(Response response, Throwable failure)
+ {
+ HttpConversation conversation = client.getConversation(response.conversation());
+ // Mark the 100 Continue response as handled
+ conversation.setAttribute(ATTRIBUTE, Boolean.TRUE);
+
+ HttpExchange exchange = conversation.exchanges().peekLast();
+ assert exchange.response() == response;
+ Response.Listener listener = exchange.listener();
+ HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getEncoding());
+ notifier.forwardFailureComplete(listener, exchange.request(), exchange.requestFailure(), contentResponse, failure);
+ }
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
index 60a13d2393..e825c7a390 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
@@ -164,6 +164,7 @@ public class HttpClient extends ContainerLifeCycle
selectorManager = newSelectorManager();
addBean(selectorManager);
+ handlers.add(new ContinueProtocolHandler(this));
handlers.add(new RedirectProtocolHandler(this));
handlers.add(new AuthenticationProtocolHandler(this));
@@ -353,9 +354,8 @@ public class HttpClient extends ContainerLifeCycle
}
}
- protected HttpConversation getConversation(Request request)
+ protected HttpConversation getConversation(long id)
{
- long id = request.id();
HttpConversation conversation = conversations.get(id);
if (conversation == null)
{
@@ -375,13 +375,17 @@ public class HttpClient extends ContainerLifeCycle
LOG.debug("{} removed", conversation);
}
- // TODO: find a better method name
- protected Response.Listener lookup(Request request, Response response)
+ protected List<ProtocolHandler> getProtocolHandlers()
{
- for (ProtocolHandler handler : handlers)
+ return handlers;
+ }
+
+ protected ProtocolHandler findProtocolHandler(Request request, Response response)
+ {
+ for (ProtocolHandler handler : getProtocolHandlers())
{
if (handler.accept(request, response))
- return handler.getResponseListener();
+ return handler;
}
return null;
}
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 f6f94b4215..c0490c55d5 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
@@ -111,7 +111,7 @@ public class HttpConnection extends AbstractConnection implements Connection
idleTimeout = endPoint.getIdleTimeout();
endPoint.setIdleTimeout(request.idleTimeout());
- HttpConversation conversation = client.getConversation(request);
+ HttpConversation conversation = client.getConversation(request.conversation());
HttpExchange exchange = new HttpExchange(conversation, this, request, listener);
setExchange(exchange);
conversation.exchanges().offer(exchange);
@@ -348,6 +348,11 @@ public class HttpConnection extends AbstractConnection implements Connection
receiver.fail(new HttpResponseException("Response aborted", response));
}
+ public void proceed(boolean proceed)
+ {
+ sender.proceed(proceed);
+ }
+
@Override
public void close()
{
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContentResponse.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContentResponse.java
index 86345830b5..ea76d9d32d 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContentResponse.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContentResponse.java
@@ -40,6 +40,12 @@ public class HttpContentResponse implements ContentResponse
}
@Override
+ public long conversation()
+ {
+ return response.conversation();
+ }
+
+ @Override
public Listener listener()
{
return response.listener();
@@ -94,4 +100,15 @@ public class HttpContentResponse implements ContentResponse
throw new UnsupportedCharsetException(encoding);
}
}
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[%s %d %s - %d bytes]",
+ HttpContentResponse.class.getSimpleName(),
+ version(),
+ status(),
+ reason(),
+ content().length);
+ }
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java
index ccd9dd9d86..1b4f7a4818 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java
@@ -63,16 +63,25 @@ public class HttpConversation implements Attributes
this.listener = listener;
}
+ /**
+ * @return the exchange that has been identified as the last of this conversation
+ * @see #last(HttpExchange)
+ */
public HttpExchange last()
{
return last;
}
+ /**
+ * Remembers the given {@code exchange} as the last of this conversation.
+ *
+ * @param exchange the exchange that is the last of this conversation
+ * @see #last()
+ */
public void last(HttpExchange exchange)
{
if (last == null)
-
- last = exchange;
+ last = exchange;
}
public void complete()
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
index b600a4d822..b569805711 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
@@ -46,7 +46,6 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
private static final Logger LOG = Log.getLogger(HttpDestination.class);
private final AtomicInteger connectionCount = new AtomicInteger();
- private final ResponseNotifier responseNotifier = new ResponseNotifier();
private final HttpClient client;
private final String scheme;
private final String host;
@@ -55,6 +54,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
private final BlockingQueue<Connection> idleConnections;
private final BlockingQueue<Connection> activeConnections;
private final RequestNotifier requestNotifier;
+ private final ResponseNotifier responseNotifier;
public HttpDestination(HttpClient client, String scheme, String host, int port)
{
@@ -66,6 +66,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
this.idleConnections = new ArrayBlockingQueue<>(client.getMaxConnectionsPerAddress());
this.activeConnections = new ArrayBlockingQueue<>(client.getMaxConnectionsPerAddress());
this.requestNotifier = new RequestNotifier(client);
+ this.responseNotifier = new ResponseNotifier(client);
}
protected BlockingQueue<Connection> getIdleConnections()
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 43fddb5014..2905af672e 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
@@ -58,6 +58,11 @@ public class HttpExchange
return request;
}
+ public Throwable requestFailure()
+ {
+ return requestFailure;
+ }
+
public Response.Listener listener()
{
return listener;
@@ -68,6 +73,11 @@ public class HttpExchange
return response;
}
+ public Throwable responseFailure()
+ {
+ return responseFailure;
+ }
+
public void receive()
{
connection.receive();
@@ -84,9 +94,17 @@ public class HttpExchange
public Result responseComplete(Throwable failure)
{
this.responseFailure = failure;
- int responseSuccess = 0b1100;
- int responseFailure = 0b0100;
- return complete(failure == null ? responseSuccess : responseFailure);
+ if (failure == null)
+ {
+ int responseSuccess = 0b1100;
+ return complete(responseSuccess);
+ }
+ else
+ {
+ proceed(false);
+ int responseFailure = 0b0100;
+ return complete(responseFailure);
+ }
}
/**
@@ -117,7 +135,7 @@ public class HttpExchange
if (this == conversation.last())
conversation.complete();
connection.complete(this, success);
- return new Result(request, requestFailure, response, responseFailure);
+ return new Result(request(), requestFailure(), response(), responseFailure());
}
return null;
}
@@ -128,6 +146,19 @@ public class HttpExchange
connection.abort(response);
}
+ public void resetResponse(boolean success)
+ {
+ int responseSuccess = 0b1100;
+ int responseFailure = 0b0100;
+ int code = success ? responseSuccess : responseFailure;
+ complete.addAndGet(-code);
+ }
+
+ public void proceed(boolean proceed)
+ {
+ connection.proceed(proceed);
+ }
+
@Override
public String toString()
{
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
index e2bacb67a5..560972ef94 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
@@ -42,14 +42,15 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
private static final Logger LOG = Log.getLogger(HttpReceiver.class);
private final HttpParser parser = new HttpParser(this);
- private final ResponseNotifier notifier = new ResponseNotifier();
private final HttpConnection connection;
+ private final ResponseNotifier notifier;
private ContentDecoder decoder;
private State state = State.IDLE;
public HttpReceiver(HttpConnection connection)
{
this.connection = connection;
+ this.notifier = new ResponseNotifier(connection.getHttpClient());
}
public void receive()
@@ -115,7 +116,8 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
Response.Listener currentListener = exchange.listener();
Response.Listener initialListener = conversation.exchanges().peekFirst().listener();
HttpClient client = connection.getHttpClient();
- Response.Listener handlerListener = client.lookup(exchange.request(), response);
+ ProtocolHandler protocolHandler = client.findProtocolHandler(exchange.request(), response);
+ Response.Listener handlerListener = protocolHandler == null ? null : protocolHandler.getResponseListener();
if (handlerListener == null)
{
conversation.last(exchange);
@@ -126,6 +128,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
}
else
{
+ LOG.debug("Found protocol handler {}", protocolHandler);
if (currentListener == initialListener)
conversation.listener(handlerListener);
else
@@ -298,7 +301,6 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
private class DoubleResponseListener implements Response.Listener
{
- private final ResponseNotifier notifier = new ResponseNotifier();
private final Response.Listener listener1;
private final Response.Listener listener2;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
index 01f52c738b..9837442296 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
@@ -101,7 +101,7 @@ public class HttpRequest implements Request
}
@Override
- public long id()
+ public long conversation()
{
return id;
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java
index 42be83cc9c..0c4c3215d3 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java
@@ -78,6 +78,12 @@ public class HttpResponse implements Response
}
@Override
+ public long conversation()
+ {
+ return exchange.request().conversation();
+ }
+
+ @Override
public Listener listener()
{
return listener;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
index adf28a3bdc..5de75ff5c0 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.client;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Iterator;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
@@ -28,6 +29,8 @@ import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil;
@@ -38,22 +41,22 @@ import org.eclipse.jetty.util.log.Logger;
public class HttpSender
{
private static final Logger LOG = Log.getLogger(HttpSender.class);
+ private static final String EXPECT_100_ATTRIBUTE = HttpSender.class.getName() + ".expect100";
private final HttpGenerator generator = new HttpGenerator();
- private final ResponseNotifier responseNotifier = new ResponseNotifier();
private final HttpConnection connection;
private final RequestNotifier requestNotifier;
- private long contentLength;
- private Iterator<ByteBuffer> contentChunks;
- private ByteBuffer header;
- private ByteBuffer chunk;
- private volatile boolean committed;
- private volatile boolean failed;
+ private final ResponseNotifier responseNotifier;
+ private Iterator<ByteBuffer> contentIterator;
+ private ContentInfo expectedContent;
+ private boolean committed;
+ private boolean failed;
public HttpSender(HttpConnection connection)
{
this.connection = connection;
this.requestNotifier = new RequestNotifier(connection.getHttpClient());
+ this.responseNotifier = new ResponseNotifier(connection.getHttpClient());
}
public void send(HttpExchange exchange)
@@ -68,42 +71,70 @@ public class HttpSender
LOG.debug("Sending {}", request);
requestNotifier.notifyBegin(request);
ContentProvider content = request.content();
- this.contentLength = content == null ? -1 : content.length();
- this.contentChunks = content == null ? Collections.<ByteBuffer>emptyIterator() : content.iterator();
+ this.contentIterator = content == null ? Collections.<ByteBuffer>emptyIterator() : content.iterator();
send();
}
}
+ public void proceed(boolean proceed)
+ {
+ ContentInfo contentInfo = expectedContent;
+ if (contentInfo != null)
+ {
+ contentInfo.await();
+ if (proceed)
+ send();
+ else
+ fail(new HttpRequestException("Expectation failed", connection.getExchange().request()));
+ }
+ }
+
private void send()
{
+ HttpClient client = connection.getHttpClient();
+ ByteBufferPool bufferPool = client.getByteBufferPool();
+ ByteBuffer header = null;
+ ByteBuffer chunk = null;
try
{
- HttpClient client = connection.getHttpClient();
EndPoint endPoint = connection.getEndPoint();
HttpExchange exchange = connection.getExchange();
- ByteBufferPool byteBufferPool = client.getByteBufferPool();
final Request request = exchange.request();
- HttpGenerator.RequestInfo info = null;
- ByteBuffer content = contentChunks.hasNext() ? contentChunks.next() : BufferUtil.EMPTY_BUFFER;
- boolean lastContent = !contentChunks.hasNext();
+ HttpConversation conversation = client.getConversation(request.conversation());
+ HttpGenerator.RequestInfo requestInfo = null;
+
+ boolean expect100 = request.headers().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
+ expect100 &= conversation.getAttribute(EXPECT_100_ATTRIBUTE) == null;
+ if (expect100)
+ conversation.setAttribute(EXPECT_100_ATTRIBUTE, Boolean.TRUE);
+
+ ContentInfo contentInfo = this.expectedContent;
+ if (contentInfo == null)
+ contentInfo = new ContentInfo(contentIterator);
+ else
+ expect100 = false;
+ this.expectedContent = null;
+
while (true)
{
- HttpGenerator.Result result = generator.generateRequest(info, header, chunk, content, lastContent);
+ HttpGenerator.Result result = generator.generateRequest(requestInfo, header, chunk, contentInfo.content, contentInfo.lastContent);
switch (result)
{
case NEED_INFO:
{
- info = new HttpGenerator.RequestInfo(request.version(), request.headers(), contentLength, request.method().asString(), request.path());
+ ContentProvider content = request.content();
+ long contentLength = content == null ? -1 : content.length();
+ requestInfo = new HttpGenerator.RequestInfo(request.version(), request.headers(), contentLength, request.method().asString(), request.path());
break;
}
case NEED_HEADER:
{
- header = byteBufferPool.acquire(client.getRequestBufferSize(), false);
+ header = bufferPool.acquire(client.getRequestBufferSize(), false);
break;
}
case NEED_CHUNK:
{
- chunk = byteBufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
+ chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
break;
}
case FLUSH:
@@ -119,9 +150,20 @@ public class HttpSender
@Override
protected void pendingCompleted()
{
+ LOG.debug("Write completed for {}", request);
+
if (!committed)
committed(request);
- send();
+
+ if (expectedContent == null)
+ {
+ send();
+ }
+ else
+ {
+ LOG.debug("Expecting 100 Continue for {}", request);
+ expectedContent.ready();
+ }
}
@Override
@@ -130,22 +172,37 @@ public class HttpSender
fail(x);
}
};
- if (header == null)
- header = BufferUtil.EMPTY_BUFFER;
- if (chunk == null)
- chunk = BufferUtil.EMPTY_BUFFER;
- endPoint.write(null, callback, header, chunk, content);
+
+ if (expect100)
+ {
+ // Save the expected content waiting for the 100 Continue response
+ expectedContent = contentInfo;
+ }
+
+ write(callback, header, chunk, expect100 ? null : contentInfo.content);
+
if (callback.pending())
+ {
+ LOG.debug("Write pending for {}", request);
return;
+ }
if (callback.completed())
{
if (!committed)
committed(request);
- releaseBuffers();
- content = contentChunks.hasNext() ? contentChunks.next() : BufferUtil.EMPTY_BUFFER;
- lastContent = !contentChunks.hasNext();
+ if (expect100)
+ {
+ LOG.debug("Expecting 100 Continue for {}", request);
+ expectedContent.ready();
+ return;
+ }
+ else
+ {
+ // Send further content
+ contentInfo = new ContentInfo(contentIterator);
+ }
}
}
break;
@@ -179,7 +236,49 @@ public class HttpSender
}
finally
{
- releaseBuffers();
+ releaseBuffers(bufferPool, header, chunk);
+ }
+ }
+
+ private void write(Callback<Void> callback, ByteBuffer header, ByteBuffer chunk, ByteBuffer content)
+ {
+ int mask = 0;
+ if (header != null)
+ mask += 1;
+ if (chunk != null)
+ mask += 2;
+ if (content != null)
+ mask += 4;
+
+ EndPoint endPoint = connection.getEndPoint();
+ switch (mask)
+ {
+ case 0:
+ endPoint.write(null, callback, BufferUtil.EMPTY_BUFFER);
+ break;
+ case 1:
+ endPoint.write(null, callback, header);
+ break;
+ case 2:
+ endPoint.write(null, callback, chunk);
+ break;
+ case 3:
+ endPoint.write(null, callback, header, chunk);
+ break;
+ case 4:
+ endPoint.write(null, callback, content);
+ break;
+ case 5:
+ endPoint.write(null, callback, header, content);
+ break;
+ case 6:
+ endPoint.write(null, callback, chunk, content);
+ break;
+ case 7:
+ endPoint.write(null, callback, header, chunk, content);
+ break;
+ default:
+ throw new IllegalStateException();
}
}
@@ -216,9 +315,6 @@ public class HttpSender
protected void fail(Throwable failure)
{
// Cleanup first
- BufferUtil.clear(header);
- BufferUtil.clear(chunk);
- releaseBuffers();
generator.abort();
failed = true;
@@ -245,19 +341,12 @@ public class HttpSender
}
}
- private void releaseBuffers()
+ private void releaseBuffers(ByteBufferPool bufferPool, ByteBuffer header, ByteBuffer chunk)
{
- ByteBufferPool bufferPool = connection.getHttpClient().getByteBufferPool();
if (!BufferUtil.hasContent(header))
- {
bufferPool.release(header);
- header = null;
- }
if (!BufferUtil.hasContent(chunk))
- {
bufferPool.release(chunk);
- chunk = null;
- }
}
private static abstract class StatefulExecutorCallback implements Callback<Void>, Runnable
@@ -341,4 +430,34 @@ public class HttpSender
INCOMPLETE, PENDING, COMPLETE, FAILED
}
}
+
+ private class ContentInfo
+ {
+ private final CountDownLatch latch = new CountDownLatch(1);
+ public final boolean lastContent;
+ public final ByteBuffer content;
+
+ public ContentInfo(Iterator<ByteBuffer> contentIterator)
+ {
+ lastContent = !contentIterator.hasNext();
+ content = lastContent ? BufferUtil.EMPTY_BUFFER : contentIterator.next();
+ }
+
+ public void ready()
+ {
+ latch.countDown();
+ }
+
+ public void await()
+ {
+ try
+ {
+ latch.await();
+ }
+ catch (InterruptedException x)
+ {
+ throw new IllegalStateException(x);
+ }
+ }
+ }
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java
index 5d9506606b..c600f802c8 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java
@@ -28,12 +28,13 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
{
private static final String ATTRIBUTE = RedirectProtocolHandler.class.getName() + ".redirect";
- private final ResponseNotifier notifier = new ResponseNotifier();
private final HttpClient client;
+ private final ResponseNotifier notifier;
public RedirectProtocolHandler(HttpClient client)
{
this.client = client;
+ this.notifier = new ResponseNotifier(client);
}
@Override
@@ -104,7 +105,7 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
private void redirect(Result result, HttpMethod method, String location)
{
Request request = result.getRequest();
- HttpConversation conversation = client.getConversation(request);
+ HttpConversation conversation = client.getConversation(request.conversation());
Integer redirects = (Integer)conversation.getAttribute(ATTRIBUTE);
if (redirects == null)
redirects = 0;
@@ -114,7 +115,7 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
++redirects;
conversation.setAttribute(ATTRIBUTE, redirects);
- Request redirect = client.newRequest(request.id(), location);
+ Request redirect = client.newRequest(request.conversation(), location);
// Use given method
redirect.method(method);
@@ -140,7 +141,7 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
{
Request request = result.getRequest();
Response response = result.getResponse();
- HttpConversation conversation = client.getConversation(request);
+ HttpConversation conversation = client.getConversation(request.conversation());
Response.Listener listener = conversation.exchanges().peekFirst().listener();
// TODO: should we reply all event, or just the failure ?
notifier.notifyFailure(listener, response, failure);
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
index ccebe2fe12..e1ac25dabf 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
@@ -20,6 +20,8 @@ package org.eclipse.jetty.client;
import java.nio.ByteBuffer;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.util.log.Log;
@@ -28,6 +30,12 @@ import org.eclipse.jetty.util.log.Logger;
public class ResponseNotifier
{
private static final Logger LOG = Log.getLogger(ResponseNotifier.class);
+ private final HttpClient client;
+
+ public ResponseNotifier(HttpClient client)
+ {
+ this.client = client;
+ }
public void notifyBegin(Response.Listener listener, Response response)
{
@@ -106,4 +114,38 @@ public class ResponseNotifier
LOG.info("Exception while notifying listener " + listener, x);
}
}
+
+ public void forwardSuccess(Response.Listener listener, Response response)
+ {
+ notifyBegin(listener, response);
+ notifyHeaders(listener, response);
+ if (response instanceof ContentResponse)
+ notifyContent(listener, response, ByteBuffer.wrap(((ContentResponse)response).content()));
+ notifySuccess(listener, response);
+ }
+
+ public void forwardSuccessComplete(Response.Listener listener, Request request, Response response)
+ {
+ HttpConversation conversation = client.getConversation(request.conversation());
+ forwardSuccess(listener, response);
+ conversation.complete();
+ notifyComplete(listener, new Result(request, response));
+ }
+
+ public void forwardFailure(Response.Listener listener, Response response, Throwable failure)
+ {
+ notifyBegin(listener, response);
+ notifyHeaders(listener, response);
+ if (response instanceof ContentResponse)
+ notifyContent(listener, response, ByteBuffer.wrap(((ContentResponse)response).content()));
+ notifyFailure(listener, response, failure);
+ }
+
+ public void forwardFailureComplete(Response.Listener listener, Request request, Throwable requestFailure, Response response, Throwable responseFailure)
+ {
+ HttpConversation conversation = client.getConversation(request.conversation());
+ forwardFailure(listener, response, responseFailure);
+ conversation.complete();
+ notifyComplete(listener, new Result(request, requestFailure, response, responseFailure));
+ }
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
index 330b59c554..72a4ab769c 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
@@ -44,7 +44,7 @@ public interface Request
/**
* @return the conversation id
*/
- long id();
+ long conversation();
/**
* @return the scheme of this request, such as "http" or "https"
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java
index 79e09752b3..8dd362cabd 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java
@@ -37,6 +37,11 @@ import org.eclipse.jetty.http.HttpVersion;
public interface Response
{
/**
+ * @return the conversation id
+ */
+ long conversation();
+
+ /**
* @return the response listener passed to {@link Request#send(Listener)}
*/
Listener listener();
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientContinueTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientContinueTest.java
new file mode 100644
index 0000000000..8aa84edfe7
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientContinueTest.java
@@ -0,0 +1,440 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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 java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.annotation.Slow;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class HttpClientContinueTest extends AbstractHttpClientServerTest
+{
+ public HttpClientContinueTest(SslContextFactory sslContextFactory)
+ {
+ super(sslContextFactory);
+ }
+
+ @Test
+ public void test_Expect100Continue_WithOneContent_Respond100Continue() throws Exception
+ {
+ test_Expect100Continue_Respond100Continue("data1".getBytes("UTF-8"));
+ }
+
+ @Test
+ public void test_Expect100Continue_WithMultipleContents_Respond100Continue() throws Exception
+ {
+ test_Expect100Continue_Respond100Continue("data1".getBytes("UTF-8"), "data2".getBytes("UTF-8"), "data3".getBytes("UTF-8"));
+ }
+
+ private void test_Expect100Continue_Respond100Continue(byte[]... contents) throws Exception
+ {
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ // Send 100-Continue and copy the content back
+ IO.copy(request.getInputStream(), response.getOutputStream());
+ }
+ });
+
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .header(HttpHeader.EXPECT.asString(), HttpHeaderValue.CONTINUE.asString())
+ .content(new BytesContentProvider(contents))
+ .send()
+ .get(5, TimeUnit.SECONDS);
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.status());
+
+ int index = 0;
+ byte[] responseContent = response.content();
+ for (byte[] content : contents)
+ {
+ for (byte b : content)
+ {
+ Assert.assertEquals(b, responseContent[index++]);
+ }
+ }
+ }
+
+ @Test
+ public void test_Expect100Continue_WithChunkedContent_Respond100Continue() throws Exception
+ {
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ // Send 100-Continue and copy the content back
+ ServletInputStream input = request.getInputStream();
+ // Make sure we chunk the response too
+ response.flushBuffer();
+ IO.copy(input, response.getOutputStream());
+ }
+ });
+
+ byte[] content1 = new byte[10240];
+ byte[] content2 = new byte[16384];
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .header(HttpHeader.EXPECT.asString(), HttpHeaderValue.CONTINUE.asString())
+ .content(new BytesContentProvider(content1, content2)
+ {
+ @Override
+ public long length()
+ {
+ return -1;
+ }
+ })
+ .send()
+ .get(5, TimeUnit.SECONDS);
+
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.status());
+
+ int index = 0;
+ byte[] responseContent = response.content();
+ for (byte b : content1)
+ Assert.assertEquals(b, responseContent[index++]);
+ for (byte b : content2)
+ Assert.assertEquals(b, responseContent[index++]);
+ }
+
+ @Test
+ public void test_Expect100Continue_WithContent_Respond417ExpectationFailed() throws Exception
+ {
+ test_Expect100Continue_WithContent_RespondError(417);
+ }
+
+ @Test
+ public void test_Expect100Continue_WithContent_Respond413RequestEntityTooLarge() throws Exception
+ {
+ test_Expect100Continue_WithContent_RespondError(413);
+ }
+
+ private void test_Expect100Continue_WithContent_RespondError(final int error) throws Exception
+ {
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ response.sendError(error);
+ }
+ });
+
+ byte[] content1 = new byte[10240];
+ byte[] content2 = new byte[16384];
+ final CountDownLatch latch = new CountDownLatch(1);
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .header(HttpHeader.EXPECT.asString(), HttpHeaderValue.CONTINUE.asString())
+ .content(new BytesContentProvider(content1, content2))
+ .send(new BufferingResponseListener()
+ {
+ @Override
+ public void onComplete(Result result)
+ {
+ Assert.assertTrue(result.isFailed());
+ Assert.assertNotNull(result.getRequestFailure());
+ Assert.assertNull(result.getResponseFailure());
+ byte[] content = getContent();
+ Assert.assertNotNull(content);
+ Assert.assertTrue(content.length > 0);
+ Assert.assertEquals(error, result.getResponse().status());
+ latch.countDown();
+ }
+ });
+
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void test_Expect100Continue_WithContent_WithRedirect() throws Exception
+ {
+ final String data = "success";
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ if (request.getRequestURI().endsWith("/done"))
+ {
+ response.getOutputStream().print(data);
+ }
+ else
+ {
+ // Send 100-Continue and consume the content
+ IO.copy(request.getInputStream(), new ByteArrayOutputStream());
+ // Send a redirect
+ response.sendRedirect("/done");
+ }
+ }
+ });
+
+ byte[] content = new byte[10240];
+ final CountDownLatch latch = new CountDownLatch(1);
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .method(HttpMethod.POST)
+ .path("/continue")
+ .header(HttpHeader.EXPECT.asString(), HttpHeaderValue.CONTINUE.asString())
+ .content(new BytesContentProvider(content))
+ .send(new BufferingResponseListener()
+ {
+ @Override
+ public void onComplete(Result result)
+ {
+ Assert.assertFalse(result.isFailed());
+ Assert.assertEquals(200, result.getResponse().status());
+ Assert.assertEquals(data, getContentAsString());
+ latch.countDown();
+ }
+ });
+
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void test_Redirect_WithExpect100Continue_WithContent() throws Exception
+ {
+ // A request with Expect: 100-Continue cannot receive non-final responses like 3xx
+
+ final String data = "success";
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ if (request.getRequestURI().endsWith("/done"))
+ {
+ // Send 100-Continue and consume the content
+ IO.copy(request.getInputStream(), new ByteArrayOutputStream());
+ response.getOutputStream().print(data);
+ }
+ else
+ {
+ // Send a redirect
+ response.sendRedirect("/done");
+ }
+ }
+ });
+
+ byte[] content = new byte[10240];
+ final CountDownLatch latch = new CountDownLatch(1);
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .method(HttpMethod.POST)
+ .path("/redirect")
+ .header(HttpHeader.EXPECT.asString(), HttpHeaderValue.CONTINUE.asString())
+ .content(new BytesContentProvider(content))
+ .send(new BufferingResponseListener()
+ {
+ @Override
+ public void onComplete(Result result)
+ {
+ Assert.assertTrue(result.isFailed());
+ Assert.assertNotNull(result.getRequestFailure());
+ Assert.assertNull(result.getResponseFailure());
+ Assert.assertEquals(302, result.getResponse().status());
+ latch.countDown();
+ }
+ });
+
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+ }
+
+ @Slow
+ @Test
+ public void test_Expect100Continue_WithContent_WithResponseFailure_Before100Continue() throws Exception
+ {
+ final long idleTimeout = 1000;
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ try
+ {
+ TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
+ }
+ catch (InterruptedException x)
+ {
+ throw new ServletException(x);
+ }
+ }
+ });
+
+ client.setIdleTimeout(idleTimeout);
+
+ byte[] content = new byte[1024];
+ final CountDownLatch latch = new CountDownLatch(1);
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .header(HttpHeader.EXPECT.asString(), HttpHeaderValue.CONTINUE.asString())
+ .content(new BytesContentProvider(content))
+ .send(new BufferingResponseListener()
+ {
+ @Override
+ public void onComplete(Result result)
+ {
+ Assert.assertTrue(result.isFailed());
+ Assert.assertNotNull(result.getRequestFailure());
+ Assert.assertNotNull(result.getResponseFailure());
+ latch.countDown();
+ }
+ });
+
+ Assert.assertTrue(latch.await(3 * idleTimeout, TimeUnit.MILLISECONDS));
+ }
+
+ @Slow
+ @Test
+ public void test_Expect100Continue_WithContent_WithResponseFailure_After100Continue() throws Exception
+ {
+ final long idleTimeout = 1000;
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ // Send 100-Continue and consume the content
+ IO.copy(request.getInputStream(), new ByteArrayOutputStream());
+ try
+ {
+ TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
+ }
+ catch (InterruptedException x)
+ {
+ throw new ServletException(x);
+ }
+ }
+ });
+
+ client.setIdleTimeout(idleTimeout);
+
+ byte[] content = new byte[1024];
+ final CountDownLatch latch = new CountDownLatch(1);
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .header(HttpHeader.EXPECT.asString(), HttpHeaderValue.CONTINUE.asString())
+ .content(new BytesContentProvider(content))
+ .send(new BufferingResponseListener()
+ {
+ @Override
+ public void onComplete(Result result)
+ {
+ Assert.assertTrue(result.isFailed());
+ Assert.assertNull(result.getRequestFailure());
+ Assert.assertNotNull(result.getResponseFailure());
+ latch.countDown();
+ }
+ });
+
+ Assert.assertTrue(latch.await(3 * idleTimeout, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ public void test_Expect100Continue_WithContent_WithResponseFailure_During100Continue() throws Exception
+ {
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ // Send 100-Continue and consume the content
+ IO.copy(request.getInputStream(), new ByteArrayOutputStream());
+ }
+ });
+
+ client.getProtocolHandlers().clear();
+ client.getProtocolHandlers().add(new ContinueProtocolHandler(client)
+ {
+ @Override
+ public Response.Listener getResponseListener()
+ {
+ final Response.Listener listener = super.getResponseListener();
+ return new Response.Listener.Empty()
+ {
+ @Override
+ public void onBegin(Response response)
+ {
+ response.abort();
+ }
+
+ @Override
+ public void onFailure(Response response, Throwable failure)
+ {
+ listener.onFailure(response, failure);
+ }
+ };
+ }
+ });
+
+ byte[] content = new byte[1024];
+ final CountDownLatch latch = new CountDownLatch(1);
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .header(HttpHeader.EXPECT.asString(), HttpHeaderValue.CONTINUE.asString())
+ .content(new BytesContentProvider(content))
+ .send(new BufferingResponseListener()
+ {
+ @Override
+ public void onComplete(Result result)
+ {
+ Assert.assertTrue(result.isFailed());
+ Assert.assertNotNull(result.getRequestFailure());
+ Assert.assertNotNull(result.getResponseFailure());
+ latch.countDown();
+ }
+ });
+
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+ }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java
index 446b3db0ea..e075ac53d3 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java
@@ -20,6 +20,7 @@ package org.eclipse.jetty.client;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
+import java.net.URI;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
@@ -67,7 +68,8 @@ public class HttpReceiverTest
protected HttpExchange newExchange(Response.Listener listener)
{
- HttpExchange exchange = new HttpExchange(conversation, connection, null, listener);
+ HttpRequest request = new HttpRequest(client, URI.create("http://localhost"));
+ HttpExchange exchange = new HttpExchange(conversation, connection, request, listener);
conversation.exchanges().offer(exchange);
connection.setExchange(exchange);
exchange.requestComplete(null);
diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml
index ed61519cdc..c1a454c1aa 100644
--- a/jetty-distribution/pom.xml
+++ b/jetty-distribution/pom.xml
@@ -64,7 +64,7 @@
</goals>
<configuration>
<resourceBundles>
- <resourceBundle>org.eclipse.jetty.toolchain:jetty-distribution-remote-resources:1.1</resourceBundle>
+ <resourceBundle>org.eclipse.jetty.toolchain:jetty-distribution-remote-resources:1.2</resourceBundle>
</resourceBundles>
<outputDirectory>${assembly-directory}</outputDirectory>
</configuration>
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 81108d7285..dc0eb62825 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
@@ -198,15 +198,26 @@ public class HttpGenerator
else
generateHeaders(info,header,content,last);
- // handle the content.
- int len = BufferUtil.length(content);
- if (len>0)
+ boolean expect100 = info.getHttpFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
+
+ if (expect100)
{
- _contentPrepared+=len;
- if (isChunking())
- prepareChunk(header,len);
+ _state = State.COMMITTED;
}
- _state = last?State.COMPLETING:State.COMMITTED;
+ else
+ {
+ // handle the content.
+ int len = BufferUtil.length(content);
+ if (len>0)
+ {
+ _contentPrepared+=len;
+ if (isChunking())
+ prepareChunk(header,len);
+ }
+ _state = last?State.COMPLETING:State.COMMITTED;
+ }
+
+ return Result.FLUSH;
}
catch(Exception e)
{
@@ -217,8 +228,6 @@ public class HttpGenerator
{
BufferUtil.flipToFlush(header,pos);
}
-
- return Result.FLUSH;
}
case COMMITTED:
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java b/jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java
index cb9a7f3385..a4dddf6347 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java
@@ -58,9 +58,8 @@ public class MappedByteBufferPool implements ByteBufferPool
int capacity = bucket * factor;
result = direct ? BufferUtil.allocateDirect(capacity) : BufferUtil.allocate(capacity);
}
- else
- BufferUtil.clear(result);
+ BufferUtil.clear(result);
return result;
}
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java
index 9cf73fc29f..e4499bf4b0 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/AbstractHTTPSPDYTest.java
@@ -34,10 +34,10 @@ import org.eclipse.jetty.spdy.client.SPDYClient;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.After;
import org.junit.Rule;
-import org.junit.rules.TestWatchman;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.junit.runners.model.FrameworkMethod;
@RunWith(Parameterized.class)
public abstract class AbstractHTTPSPDYTest
@@ -49,15 +49,16 @@ public abstract class AbstractHTTPSPDYTest
}
@Rule
- public final TestWatchman testName = new TestWatchman()
+ public final TestWatcher testName = new TestWatcher()
{
+
@Override
- public void starting(FrameworkMethod method)
+ public void starting(Description description)
{
- super.starting(method);
+ super.starting(description);
System.err.printf("Running %s.%s()%n",
- method.getMethod().getDeclaringClass().getName(),
- method.getName());
+ description.getClassName(),
+ description.getMethodName());
}
};
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ProtocolNegotiationTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ProtocolNegotiationTest.java
index 83f0fa95d7..4dd0bb661b 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ProtocolNegotiationTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/http/ProtocolNegotiationTest.java
@@ -36,21 +36,22 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TestWatchman;
-import org.junit.runners.model.FrameworkMethod;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
public class ProtocolNegotiationTest
{
@Rule
- public final TestWatchman testName = new TestWatchman()
+ public final TestWatcher testName = new TestWatcher()
{
+
@Override
- public void starting(FrameworkMethod method)
+ public void starting(Description description)
{
- super.starting(method);
+ super.starting(description);
System.err.printf("Running %s.%s()%n",
- method.getMethod().getDeclaringClass().getName(),
- method.getName());
+ description.getClassName(),
+ description.getMethodName());
}
};
diff --git a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYTest.java b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYTest.java
index c383de7dd7..69f52ec0f0 100644
--- a/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYTest.java
+++ b/jetty-spdy/spdy-http-server/src/test/java/org/eclipse/jetty/spdy/server/proxy/ProxyHTTPSPDYTest.java
@@ -57,25 +57,26 @@ import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TestWatchman;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.junit.runners.model.FrameworkMethod;
@Ignore // TODO: make these tests pass
@RunWith(value = Parameterized.class)
public class ProxyHTTPSPDYTest
{
@Rule
- public final TestWatchman testName = new TestWatchman()
+ public final TestWatcher testName = new TestWatcher()
{
+
@Override
- public void starting(FrameworkMethod method)
+ public void starting(Description description)
{
- super.starting(method);
+ super.starting(description);
System.err.printf("Running %s.%s()%n",
- method.getMethod().getDeclaringClass().getName(),
- method.getName());
+ description.getClassName(),
+ description.getMethodName());
}
};
private final short version;

Back to the top