Merge branch 'jetty-9' of ssh://git.eclipse.org/gitroot/jetty/org.eclipse.jetty.project into jetty-9
This commit is contained in:
commit
d173e91ef5
|
@ -18,7 +18,6 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.client;
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -40,9 +39,9 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
|
||||||
public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class);
|
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 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 HttpClient client;
|
||||||
private final int maxContentLength;
|
private final int maxContentLength;
|
||||||
|
private final ResponseNotifier notifier;
|
||||||
|
|
||||||
public AuthenticationProtocolHandler(HttpClient client)
|
public AuthenticationProtocolHandler(HttpClient client)
|
||||||
{
|
{
|
||||||
|
@ -53,6 +52,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
|
||||||
{
|
{
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.maxContentLength = maxContentLength;
|
this.maxContentLength = maxContentLength;
|
||||||
|
this.notifier = new ResponseNotifier(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -64,6 +64,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
|
||||||
@Override
|
@Override
|
||||||
public Response.Listener getResponseListener()
|
public Response.Listener getResponseListener()
|
||||||
{
|
{
|
||||||
|
// Return new instances every time to keep track of the response content
|
||||||
return new AuthenticationListener();
|
return new AuthenticationListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,12 +79,14 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
|
||||||
public void onComplete(Result result)
|
public void onComplete(Result result)
|
||||||
{
|
{
|
||||||
Request request = result.getRequest();
|
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());
|
ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getEncoding());
|
||||||
if (result.isFailed())
|
if (result.isFailed())
|
||||||
{
|
{
|
||||||
Throwable failure = result.getFailure();
|
Throwable failure = result.getFailure();
|
||||||
LOG.debug("Authentication challenge failed {}", failure);
|
LOG.debug("Authentication challenge failed {}", failure);
|
||||||
forwardFailure(request, response, failure);
|
notifier.forwardFailureComplete(listener, request, result.getRequestFailure(), response, result.getResponseFailure());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +94,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
|
||||||
if (wwwAuthenticates.isEmpty())
|
if (wwwAuthenticates.isEmpty())
|
||||||
{
|
{
|
||||||
LOG.debug("Authentication challenge without WWW-Authenticate header");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,16 +113,15 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
|
||||||
if (authentication == null)
|
if (authentication == null)
|
||||||
{
|
{
|
||||||
LOG.debug("No authentication available for {}", request);
|
LOG.debug("No authentication available for {}", request);
|
||||||
forwardSuccess(request, response);
|
notifier.forwardSuccessComplete(listener, request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpConversation conversation = client.getConversation(request);
|
|
||||||
final Authentication.Result authnResult = authentication.authenticate(request, response, wwwAuthenticate.value, conversation);
|
final Authentication.Result authnResult = authentication.authenticate(request, response, wwwAuthenticate.value, conversation);
|
||||||
LOG.debug("Authentication result {}", authnResult);
|
LOG.debug("Authentication result {}", authnResult);
|
||||||
if (authnResult == null)
|
if (authnResult == null)
|
||||||
{
|
{
|
||||||
forwardSuccess(request, response);
|
notifier.forwardSuccessComplete(listener, request, response);
|
||||||
return;
|
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)
|
private List<WWWAuthenticate> parseWWWAuthenticate(Response response)
|
||||||
{
|
{
|
||||||
// TODO: these should be ordered by strength
|
// TODO: these should be ordered by strength
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -164,6 +164,7 @@ public class HttpClient extends ContainerLifeCycle
|
||||||
selectorManager = newSelectorManager();
|
selectorManager = newSelectorManager();
|
||||||
addBean(selectorManager);
|
addBean(selectorManager);
|
||||||
|
|
||||||
|
handlers.add(new ContinueProtocolHandler(this));
|
||||||
handlers.add(new RedirectProtocolHandler(this));
|
handlers.add(new RedirectProtocolHandler(this));
|
||||||
handlers.add(new AuthenticationProtocolHandler(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);
|
HttpConversation conversation = conversations.get(id);
|
||||||
if (conversation == null)
|
if (conversation == null)
|
||||||
{
|
{
|
||||||
|
@ -375,13 +375,17 @@ public class HttpClient extends ContainerLifeCycle
|
||||||
LOG.debug("{} removed", conversation);
|
LOG.debug("{} removed", conversation);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: find a better method name
|
protected List<ProtocolHandler> getProtocolHandlers()
|
||||||
protected Response.Listener lookup(Request request, Response response)
|
|
||||||
{
|
{
|
||||||
for (ProtocolHandler handler : handlers)
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ProtocolHandler findProtocolHandler(Request request, Response response)
|
||||||
|
{
|
||||||
|
for (ProtocolHandler handler : getProtocolHandlers())
|
||||||
{
|
{
|
||||||
if (handler.accept(request, response))
|
if (handler.accept(request, response))
|
||||||
return handler.getResponseListener();
|
return handler;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,7 +111,7 @@ public class HttpConnection extends AbstractConnection implements Connection
|
||||||
idleTimeout = endPoint.getIdleTimeout();
|
idleTimeout = endPoint.getIdleTimeout();
|
||||||
endPoint.setIdleTimeout(request.idleTimeout());
|
endPoint.setIdleTimeout(request.idleTimeout());
|
||||||
|
|
||||||
HttpConversation conversation = client.getConversation(request);
|
HttpConversation conversation = client.getConversation(request.conversation());
|
||||||
HttpExchange exchange = new HttpExchange(conversation, this, request, listener);
|
HttpExchange exchange = new HttpExchange(conversation, this, request, listener);
|
||||||
setExchange(exchange);
|
setExchange(exchange);
|
||||||
conversation.exchanges().offer(exchange);
|
conversation.exchanges().offer(exchange);
|
||||||
|
@ -348,6 +348,11 @@ public class HttpConnection extends AbstractConnection implements Connection
|
||||||
receiver.fail(new HttpResponseException("Response aborted", response));
|
receiver.fail(new HttpResponseException("Response aborted", response));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void proceed(boolean proceed)
|
||||||
|
{
|
||||||
|
sender.proceed(proceed);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,6 +39,12 @@ public class HttpContentResponse implements ContentResponse
|
||||||
this.encoding = encoding;
|
this.encoding = encoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long conversation()
|
||||||
|
{
|
||||||
|
return response.conversation();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Listener listener()
|
public Listener listener()
|
||||||
{
|
{
|
||||||
|
@ -94,4 +100,15 @@ public class HttpContentResponse implements ContentResponse
|
||||||
throw new UnsupportedCharsetException(encoding);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,15 +63,24 @@ public class HttpConversation implements Attributes
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the exchange that has been identified as the last of this conversation
|
||||||
|
* @see #last(HttpExchange)
|
||||||
|
*/
|
||||||
public HttpExchange last()
|
public HttpExchange last()
|
||||||
{
|
{
|
||||||
return 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)
|
public void last(HttpExchange exchange)
|
||||||
{
|
{
|
||||||
if (last == null)
|
if (last == null)
|
||||||
|
|
||||||
last = exchange;
|
last = exchange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,6 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
|
||||||
private static final Logger LOG = Log.getLogger(HttpDestination.class);
|
private static final Logger LOG = Log.getLogger(HttpDestination.class);
|
||||||
|
|
||||||
private final AtomicInteger connectionCount = new AtomicInteger();
|
private final AtomicInteger connectionCount = new AtomicInteger();
|
||||||
private final ResponseNotifier responseNotifier = new ResponseNotifier();
|
|
||||||
private final HttpClient client;
|
private final HttpClient client;
|
||||||
private final String scheme;
|
private final String scheme;
|
||||||
private final String host;
|
private final String host;
|
||||||
|
@ -55,6 +54,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
|
||||||
private final BlockingQueue<Connection> idleConnections;
|
private final BlockingQueue<Connection> idleConnections;
|
||||||
private final BlockingQueue<Connection> activeConnections;
|
private final BlockingQueue<Connection> activeConnections;
|
||||||
private final RequestNotifier requestNotifier;
|
private final RequestNotifier requestNotifier;
|
||||||
|
private final ResponseNotifier responseNotifier;
|
||||||
|
|
||||||
public HttpDestination(HttpClient client, String scheme, String host, int port)
|
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.idleConnections = new ArrayBlockingQueue<>(client.getMaxConnectionsPerAddress());
|
||||||
this.activeConnections = new ArrayBlockingQueue<>(client.getMaxConnectionsPerAddress());
|
this.activeConnections = new ArrayBlockingQueue<>(client.getMaxConnectionsPerAddress());
|
||||||
this.requestNotifier = new RequestNotifier(client);
|
this.requestNotifier = new RequestNotifier(client);
|
||||||
|
this.responseNotifier = new ResponseNotifier(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected BlockingQueue<Connection> getIdleConnections()
|
protected BlockingQueue<Connection> getIdleConnections()
|
||||||
|
|
|
@ -58,6 +58,11 @@ public class HttpExchange
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Throwable requestFailure()
|
||||||
|
{
|
||||||
|
return requestFailure;
|
||||||
|
}
|
||||||
|
|
||||||
public Response.Listener listener()
|
public Response.Listener listener()
|
||||||
{
|
{
|
||||||
return listener;
|
return listener;
|
||||||
|
@ -68,6 +73,11 @@ public class HttpExchange
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Throwable responseFailure()
|
||||||
|
{
|
||||||
|
return responseFailure;
|
||||||
|
}
|
||||||
|
|
||||||
public void receive()
|
public void receive()
|
||||||
{
|
{
|
||||||
connection.receive();
|
connection.receive();
|
||||||
|
@ -84,9 +94,17 @@ public class HttpExchange
|
||||||
public Result responseComplete(Throwable failure)
|
public Result responseComplete(Throwable failure)
|
||||||
{
|
{
|
||||||
this.responseFailure = failure;
|
this.responseFailure = failure;
|
||||||
|
if (failure == null)
|
||||||
|
{
|
||||||
int responseSuccess = 0b1100;
|
int responseSuccess = 0b1100;
|
||||||
|
return complete(responseSuccess);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
proceed(false);
|
||||||
int responseFailure = 0b0100;
|
int responseFailure = 0b0100;
|
||||||
return complete(failure == null ? responseSuccess : responseFailure);
|
return complete(responseFailure);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -117,7 +135,7 @@ public class HttpExchange
|
||||||
if (this == conversation.last())
|
if (this == conversation.last())
|
||||||
conversation.complete();
|
conversation.complete();
|
||||||
connection.complete(this, success);
|
connection.complete(this, success);
|
||||||
return new Result(request, requestFailure, response, responseFailure);
|
return new Result(request(), requestFailure(), response(), responseFailure());
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -128,6 +146,19 @@ public class HttpExchange
|
||||||
connection.abort(response);
|
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
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|
|
@ -42,14 +42,15 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
|
||||||
private static final Logger LOG = Log.getLogger(HttpReceiver.class);
|
private static final Logger LOG = Log.getLogger(HttpReceiver.class);
|
||||||
|
|
||||||
private final HttpParser parser = new HttpParser(this);
|
private final HttpParser parser = new HttpParser(this);
|
||||||
private final ResponseNotifier notifier = new ResponseNotifier();
|
|
||||||
private final HttpConnection connection;
|
private final HttpConnection connection;
|
||||||
|
private final ResponseNotifier notifier;
|
||||||
private ContentDecoder decoder;
|
private ContentDecoder decoder;
|
||||||
private State state = State.IDLE;
|
private State state = State.IDLE;
|
||||||
|
|
||||||
public HttpReceiver(HttpConnection connection)
|
public HttpReceiver(HttpConnection connection)
|
||||||
{
|
{
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
|
this.notifier = new ResponseNotifier(connection.getHttpClient());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void receive()
|
public void receive()
|
||||||
|
@ -115,7 +116,8 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
|
||||||
Response.Listener currentListener = exchange.listener();
|
Response.Listener currentListener = exchange.listener();
|
||||||
Response.Listener initialListener = conversation.exchanges().peekFirst().listener();
|
Response.Listener initialListener = conversation.exchanges().peekFirst().listener();
|
||||||
HttpClient client = connection.getHttpClient();
|
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)
|
if (handlerListener == null)
|
||||||
{
|
{
|
||||||
conversation.last(exchange);
|
conversation.last(exchange);
|
||||||
|
@ -126,6 +128,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
LOG.debug("Found protocol handler {}", protocolHandler);
|
||||||
if (currentListener == initialListener)
|
if (currentListener == initialListener)
|
||||||
conversation.listener(handlerListener);
|
conversation.listener(handlerListener);
|
||||||
else
|
else
|
||||||
|
@ -298,7 +301,6 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
|
||||||
|
|
||||||
private class DoubleResponseListener implements Response.Listener
|
private class DoubleResponseListener implements Response.Listener
|
||||||
{
|
{
|
||||||
private final ResponseNotifier notifier = new ResponseNotifier();
|
|
||||||
private final Response.Listener listener1;
|
private final Response.Listener listener1;
|
||||||
private final Response.Listener listener2;
|
private final Response.Listener listener2;
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,7 @@ public class HttpRequest implements Request
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long id()
|
public long conversation()
|
||||||
{
|
{
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,12 @@ public class HttpResponse implements Response
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long conversation()
|
||||||
|
{
|
||||||
|
return exchange.request().conversation();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Listener listener()
|
public Listener listener()
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.client;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
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.Request;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.http.HttpGenerator;
|
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.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
@ -38,22 +41,22 @@ import org.eclipse.jetty.util.log.Logger;
|
||||||
public class HttpSender
|
public class HttpSender
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(HttpSender.class);
|
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 HttpGenerator generator = new HttpGenerator();
|
||||||
private final ResponseNotifier responseNotifier = new ResponseNotifier();
|
|
||||||
private final HttpConnection connection;
|
private final HttpConnection connection;
|
||||||
private final RequestNotifier requestNotifier;
|
private final RequestNotifier requestNotifier;
|
||||||
private long contentLength;
|
private final ResponseNotifier responseNotifier;
|
||||||
private Iterator<ByteBuffer> contentChunks;
|
private Iterator<ByteBuffer> contentIterator;
|
||||||
private ByteBuffer header;
|
private ContentInfo expectedContent;
|
||||||
private ByteBuffer chunk;
|
private boolean committed;
|
||||||
private volatile boolean committed;
|
private boolean failed;
|
||||||
private volatile boolean failed;
|
|
||||||
|
|
||||||
public HttpSender(HttpConnection connection)
|
public HttpSender(HttpConnection connection)
|
||||||
{
|
{
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.requestNotifier = new RequestNotifier(connection.getHttpClient());
|
this.requestNotifier = new RequestNotifier(connection.getHttpClient());
|
||||||
|
this.responseNotifier = new ResponseNotifier(connection.getHttpClient());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void send(HttpExchange exchange)
|
public void send(HttpExchange exchange)
|
||||||
|
@ -68,42 +71,70 @@ public class HttpSender
|
||||||
LOG.debug("Sending {}", request);
|
LOG.debug("Sending {}", request);
|
||||||
requestNotifier.notifyBegin(request);
|
requestNotifier.notifyBegin(request);
|
||||||
ContentProvider content = request.content();
|
ContentProvider content = request.content();
|
||||||
this.contentLength = content == null ? -1 : content.length();
|
this.contentIterator = content == null ? Collections.<ByteBuffer>emptyIterator() : content.iterator();
|
||||||
this.contentChunks = content == null ? Collections.<ByteBuffer>emptyIterator() : content.iterator();
|
|
||||||
send();
|
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()
|
private void send()
|
||||||
{
|
{
|
||||||
|
HttpClient client = connection.getHttpClient();
|
||||||
|
ByteBufferPool bufferPool = client.getByteBufferPool();
|
||||||
|
ByteBuffer header = null;
|
||||||
|
ByteBuffer chunk = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
HttpClient client = connection.getHttpClient();
|
|
||||||
EndPoint endPoint = connection.getEndPoint();
|
EndPoint endPoint = connection.getEndPoint();
|
||||||
HttpExchange exchange = connection.getExchange();
|
HttpExchange exchange = connection.getExchange();
|
||||||
ByteBufferPool byteBufferPool = client.getByteBufferPool();
|
|
||||||
final Request request = exchange.request();
|
final Request request = exchange.request();
|
||||||
HttpGenerator.RequestInfo info = null;
|
HttpConversation conversation = client.getConversation(request.conversation());
|
||||||
ByteBuffer content = contentChunks.hasNext() ? contentChunks.next() : BufferUtil.EMPTY_BUFFER;
|
HttpGenerator.RequestInfo requestInfo = null;
|
||||||
boolean lastContent = !contentChunks.hasNext();
|
|
||||||
|
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)
|
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)
|
switch (result)
|
||||||
{
|
{
|
||||||
case NEED_INFO:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case NEED_HEADER:
|
case NEED_HEADER:
|
||||||
{
|
{
|
||||||
header = byteBufferPool.acquire(client.getRequestBufferSize(), false);
|
header = bufferPool.acquire(client.getRequestBufferSize(), false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NEED_CHUNK:
|
case NEED_CHUNK:
|
||||||
{
|
{
|
||||||
chunk = byteBufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
|
chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FLUSH:
|
case FLUSH:
|
||||||
|
@ -119,10 +150,21 @@ public class HttpSender
|
||||||
@Override
|
@Override
|
||||||
protected void pendingCompleted()
|
protected void pendingCompleted()
|
||||||
{
|
{
|
||||||
|
LOG.debug("Write completed for {}", request);
|
||||||
|
|
||||||
if (!committed)
|
if (!committed)
|
||||||
committed(request);
|
committed(request);
|
||||||
|
|
||||||
|
if (expectedContent == null)
|
||||||
|
{
|
||||||
send();
|
send();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG.debug("Expecting 100 Continue for {}", request);
|
||||||
|
expectedContent.ready();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void failed(Throwable x)
|
protected void failed(Throwable x)
|
||||||
|
@ -130,22 +172,37 @@ public class HttpSender
|
||||||
fail(x);
|
fail(x);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (header == null)
|
|
||||||
header = BufferUtil.EMPTY_BUFFER;
|
if (expect100)
|
||||||
if (chunk == null)
|
{
|
||||||
chunk = BufferUtil.EMPTY_BUFFER;
|
// Save the expected content waiting for the 100 Continue response
|
||||||
endPoint.write(null, callback, header, chunk, content);
|
expectedContent = contentInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
write(callback, header, chunk, expect100 ? null : contentInfo.content);
|
||||||
|
|
||||||
if (callback.pending())
|
if (callback.pending())
|
||||||
|
{
|
||||||
|
LOG.debug("Write pending for {}", request);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (callback.completed())
|
if (callback.completed())
|
||||||
{
|
{
|
||||||
if (!committed)
|
if (!committed)
|
||||||
committed(request);
|
committed(request);
|
||||||
|
|
||||||
releaseBuffers();
|
if (expect100)
|
||||||
content = contentChunks.hasNext() ? contentChunks.next() : BufferUtil.EMPTY_BUFFER;
|
{
|
||||||
lastContent = !contentChunks.hasNext();
|
LOG.debug("Expecting 100 Continue for {}", request);
|
||||||
|
expectedContent.ready();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Send further content
|
||||||
|
contentInfo = new ContentInfo(contentIterator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -179,7 +236,49 @@ public class HttpSender
|
||||||
}
|
}
|
||||||
finally
|
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)
|
protected void fail(Throwable failure)
|
||||||
{
|
{
|
||||||
// Cleanup first
|
// Cleanup first
|
||||||
BufferUtil.clear(header);
|
|
||||||
BufferUtil.clear(chunk);
|
|
||||||
releaseBuffers();
|
|
||||||
generator.abort();
|
generator.abort();
|
||||||
failed = true;
|
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))
|
if (!BufferUtil.hasContent(header))
|
||||||
{
|
|
||||||
bufferPool.release(header);
|
bufferPool.release(header);
|
||||||
header = null;
|
|
||||||
}
|
|
||||||
if (!BufferUtil.hasContent(chunk))
|
if (!BufferUtil.hasContent(chunk))
|
||||||
{
|
|
||||||
bufferPool.release(chunk);
|
bufferPool.release(chunk);
|
||||||
chunk = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static abstract class StatefulExecutorCallback implements Callback<Void>, Runnable
|
private static abstract class StatefulExecutorCallback implements Callback<Void>, Runnable
|
||||||
|
@ -341,4 +430,34 @@ public class HttpSender
|
||||||
INCOMPLETE, PENDING, COMPLETE, FAILED
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,12 +28,13 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
|
||||||
{
|
{
|
||||||
private static final String ATTRIBUTE = RedirectProtocolHandler.class.getName() + ".redirect";
|
private static final String ATTRIBUTE = RedirectProtocolHandler.class.getName() + ".redirect";
|
||||||
|
|
||||||
private final ResponseNotifier notifier = new ResponseNotifier();
|
|
||||||
private final HttpClient client;
|
private final HttpClient client;
|
||||||
|
private final ResponseNotifier notifier;
|
||||||
|
|
||||||
public RedirectProtocolHandler(HttpClient client)
|
public RedirectProtocolHandler(HttpClient client)
|
||||||
{
|
{
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
this.notifier = new ResponseNotifier(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -104,7 +105,7 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
|
||||||
private void redirect(Result result, HttpMethod method, String location)
|
private void redirect(Result result, HttpMethod method, String location)
|
||||||
{
|
{
|
||||||
Request request = result.getRequest();
|
Request request = result.getRequest();
|
||||||
HttpConversation conversation = client.getConversation(request);
|
HttpConversation conversation = client.getConversation(request.conversation());
|
||||||
Integer redirects = (Integer)conversation.getAttribute(ATTRIBUTE);
|
Integer redirects = (Integer)conversation.getAttribute(ATTRIBUTE);
|
||||||
if (redirects == null)
|
if (redirects == null)
|
||||||
redirects = 0;
|
redirects = 0;
|
||||||
|
@ -114,7 +115,7 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
|
||||||
++redirects;
|
++redirects;
|
||||||
conversation.setAttribute(ATTRIBUTE, redirects);
|
conversation.setAttribute(ATTRIBUTE, redirects);
|
||||||
|
|
||||||
Request redirect = client.newRequest(request.id(), location);
|
Request redirect = client.newRequest(request.conversation(), location);
|
||||||
|
|
||||||
// Use given method
|
// Use given method
|
||||||
redirect.method(method);
|
redirect.method(method);
|
||||||
|
@ -140,7 +141,7 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
|
||||||
{
|
{
|
||||||
Request request = result.getRequest();
|
Request request = result.getRequest();
|
||||||
Response response = result.getResponse();
|
Response response = result.getResponse();
|
||||||
HttpConversation conversation = client.getConversation(request);
|
HttpConversation conversation = client.getConversation(request.conversation());
|
||||||
Response.Listener listener = conversation.exchanges().peekFirst().listener();
|
Response.Listener listener = conversation.exchanges().peekFirst().listener();
|
||||||
// TODO: should we reply all event, or just the failure ?
|
// TODO: should we reply all event, or just the failure ?
|
||||||
notifier.notifyFailure(listener, response, failure);
|
notifier.notifyFailure(listener, response, failure);
|
||||||
|
|
|
@ -20,6 +20,8 @@ package org.eclipse.jetty.client;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
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.Response;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
@ -28,6 +30,12 @@ import org.eclipse.jetty.util.log.Logger;
|
||||||
public class ResponseNotifier
|
public class ResponseNotifier
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(ResponseNotifier.class);
|
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)
|
public void notifyBegin(Response.Listener listener, Response response)
|
||||||
{
|
{
|
||||||
|
@ -106,4 +114,38 @@ public class ResponseNotifier
|
||||||
LOG.info("Exception while notifying listener " + listener, x);
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ public interface Request
|
||||||
/**
|
/**
|
||||||
* @return the conversation id
|
* @return the conversation id
|
||||||
*/
|
*/
|
||||||
long id();
|
long conversation();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the scheme of this request, such as "http" or "https"
|
* @return the scheme of this request, such as "http" or "https"
|
||||||
|
|
|
@ -36,6 +36,11 @@ import org.eclipse.jetty.http.HttpVersion;
|
||||||
*/
|
*/
|
||||||
public interface Response
|
public interface Response
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @return the conversation id
|
||||||
|
*/
|
||||||
|
long conversation();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the response listener passed to {@link Request#send(Listener)}
|
* @return the response listener passed to {@link Request#send(Listener)}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ package org.eclipse.jetty.client;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
|
import java.net.URI;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
@ -67,7 +68,8 @@ public class HttpReceiverTest
|
||||||
|
|
||||||
protected HttpExchange newExchange(Response.Listener listener)
|
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);
|
conversation.exchanges().offer(exchange);
|
||||||
connection.setExchange(exchange);
|
connection.setExchange(exchange);
|
||||||
exchange.requestComplete(null);
|
exchange.requestComplete(null);
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<resourceBundles>
|
<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>
|
</resourceBundles>
|
||||||
<outputDirectory>${assembly-directory}</outputDirectory>
|
<outputDirectory>${assembly-directory}</outputDirectory>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -198,6 +198,14 @@ public class HttpGenerator
|
||||||
else
|
else
|
||||||
generateHeaders(info,header,content,last);
|
generateHeaders(info,header,content,last);
|
||||||
|
|
||||||
|
boolean expect100 = info.getHttpFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
|
||||||
|
|
||||||
|
if (expect100)
|
||||||
|
{
|
||||||
|
_state = State.COMMITTED;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// handle the content.
|
// handle the content.
|
||||||
int len = BufferUtil.length(content);
|
int len = BufferUtil.length(content);
|
||||||
if (len>0)
|
if (len>0)
|
||||||
|
@ -208,6 +216,9 @@ public class HttpGenerator
|
||||||
}
|
}
|
||||||
_state = last?State.COMPLETING:State.COMMITTED;
|
_state = last?State.COMPLETING:State.COMMITTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Result.FLUSH;
|
||||||
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
|
String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
|
||||||
|
@ -217,8 +228,6 @@ public class HttpGenerator
|
||||||
{
|
{
|
||||||
BufferUtil.flipToFlush(header,pos);
|
BufferUtil.flipToFlush(header,pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.FLUSH;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case COMMITTED:
|
case COMMITTED:
|
||||||
|
|
|
@ -58,9 +58,8 @@ public class MappedByteBufferPool implements ByteBufferPool
|
||||||
int capacity = bucket * factor;
|
int capacity = bucket * factor;
|
||||||
result = direct ? BufferUtil.allocateDirect(capacity) : BufferUtil.allocate(capacity);
|
result = direct ? BufferUtil.allocateDirect(capacity) : BufferUtil.allocate(capacity);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
BufferUtil.clear(result);
|
|
||||||
|
|
||||||
|
BufferUtil.clear(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,10 +34,10 @@ import org.eclipse.jetty.spdy.client.SPDYClient;
|
||||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Rule;
|
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.runner.RunWith;
|
||||||
import org.junit.runners.Parameterized;
|
import org.junit.runners.Parameterized;
|
||||||
import org.junit.runners.model.FrameworkMethod;
|
|
||||||
|
|
||||||
@RunWith(Parameterized.class)
|
@RunWith(Parameterized.class)
|
||||||
public abstract class AbstractHTTPSPDYTest
|
public abstract class AbstractHTTPSPDYTest
|
||||||
|
@ -49,15 +49,16 @@ public abstract class AbstractHTTPSPDYTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final TestWatchman testName = new TestWatchman()
|
public final TestWatcher testName = new TestWatcher()
|
||||||
{
|
{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void starting(FrameworkMethod method)
|
public void starting(Description description)
|
||||||
{
|
{
|
||||||
super.starting(method);
|
super.starting(description);
|
||||||
System.err.printf("Running %s.%s()%n",
|
System.err.printf("Running %s.%s()%n",
|
||||||
method.getMethod().getDeclaringClass().getName(),
|
description.getClassName(),
|
||||||
method.getName());
|
description.getMethodName());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -36,21 +36,22 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.TestWatchman;
|
import org.junit.rules.TestWatcher;
|
||||||
import org.junit.runners.model.FrameworkMethod;
|
import org.junit.runner.Description;
|
||||||
|
|
||||||
public class ProtocolNegotiationTest
|
public class ProtocolNegotiationTest
|
||||||
{
|
{
|
||||||
@Rule
|
@Rule
|
||||||
public final TestWatchman testName = new TestWatchman()
|
public final TestWatcher testName = new TestWatcher()
|
||||||
{
|
{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void starting(FrameworkMethod method)
|
public void starting(Description description)
|
||||||
{
|
{
|
||||||
super.starting(method);
|
super.starting(description);
|
||||||
System.err.printf("Running %s.%s()%n",
|
System.err.printf("Running %s.%s()%n",
|
||||||
method.getMethod().getDeclaringClass().getName(),
|
description.getClassName(),
|
||||||
method.getName());
|
description.getMethodName());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -57,25 +57,26 @@ import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
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.runner.RunWith;
|
||||||
import org.junit.runners.Parameterized;
|
import org.junit.runners.Parameterized;
|
||||||
import org.junit.runners.model.FrameworkMethod;
|
|
||||||
|
|
||||||
@Ignore // TODO: make these tests pass
|
@Ignore // TODO: make these tests pass
|
||||||
@RunWith(value = Parameterized.class)
|
@RunWith(value = Parameterized.class)
|
||||||
public class ProxyHTTPSPDYTest
|
public class ProxyHTTPSPDYTest
|
||||||
{
|
{
|
||||||
@Rule
|
@Rule
|
||||||
public final TestWatchman testName = new TestWatchman()
|
public final TestWatcher testName = new TestWatcher()
|
||||||
{
|
{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void starting(FrameworkMethod method)
|
public void starting(Description description)
|
||||||
{
|
{
|
||||||
super.starting(method);
|
super.starting(description);
|
||||||
System.err.printf("Running %s.%s()%n",
|
System.err.printf("Running %s.%s()%n",
|
||||||
method.getMethod().getDeclaringClass().getName(),
|
description.getClassName(),
|
||||||
method.getName());
|
description.getMethodName());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private final short version;
|
private final short version;
|
||||||
|
|
Loading…
Reference in New Issue