Performance improvements to HTTP client after profiling session.

The profiling suggested to reduce the number of unneeded allocations
and this required a couple of API changes.
This commit is contained in:
Simone Bordet 2012-12-19 16:22:30 +01:00
parent c9f4513a89
commit b6e4f98cf7
19 changed files with 372 additions and 133 deletions

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.client;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -98,7 +99,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
return;
}
final String uri = request.getURI();
final URI uri = request.getURI();
Authentication authentication = null;
WWWAuthenticate wwwAuthenticate = null;
for (WWWAuthenticate wwwAuthn : wwwAuthenticates)
@ -125,7 +126,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
return;
}
Request newRequest = client.copyRequest(request, request.getURI());
Request newRequest = client.copyRequest(request, uri);
authnResult.apply(newRequest);
newRequest.onResponseSuccess(new Response.SuccessListener()
{

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.client;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -29,7 +30,7 @@ import org.eclipse.jetty.client.api.AuthenticationStore;
public class HttpAuthenticationStore implements AuthenticationStore
{
private final List<Authentication> authentications = new CopyOnWriteArrayList<>();
private final Map<String, Authentication.Result> results = new ConcurrentHashMap<>();
private final Map<URI, Authentication.Result> results = new ConcurrentHashMap<>();
@Override
public void addAuthentication(Authentication authentication)
@ -50,7 +51,7 @@ public class HttpAuthenticationStore implements AuthenticationStore
}
@Override
public Authentication findAuthentication(String type, String uri, String realm)
public Authentication findAuthentication(String type, URI uri, String realm)
{
for (Authentication authentication : authentications)
{
@ -79,12 +80,12 @@ public class HttpAuthenticationStore implements AuthenticationStore
}
@Override
public Authentication.Result findAuthenticationResult(String uri)
public Authentication.Result findAuthenticationResult(URI uri)
{
// TODO: I should match the longest URI
for (Map.Entry<String, Authentication.Result> entry : results.entrySet())
for (Map.Entry<URI, Authentication.Result> entry : results.entrySet())
{
if (uri.startsWith(entry.getKey()))
if (uri.toString().startsWith(entry.getKey().toString()))
return entry.getValue();
}
return null;

View File

@ -30,14 +30,15 @@ import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
@ -111,10 +112,10 @@ public class HttpClient extends ContainerLifeCycle
private final ConcurrentMap<String, HttpDestination> destinations = new ConcurrentHashMap<>();
private final ConcurrentMap<Long, HttpConversation> conversations = new ConcurrentHashMap<>();
private final List<ProtocolHandler> handlers = new CopyOnWriteArrayList<>();
private final List<Request.Listener> requestListeners = new CopyOnWriteArrayList<>();
private final List<ProtocolHandler> handlers = new ArrayList<>();
private final List<Request.Listener> requestListeners = new ArrayList<>();
private final AuthenticationStore authenticationStore = new HttpAuthenticationStore();
private final Set<ContentDecoder.Factory> decoderFactories = Collections.newSetFromMap(new ConcurrentHashMap<ContentDecoder.Factory, Boolean>());
private final Set<ContentDecoder.Factory> decoderFactories = new ContentDecoderFactorySet();
private final SslContextFactory sslContextFactory;
private volatile CookieManager cookieManager;
private volatile CookieStore cookieStore;
@ -122,7 +123,7 @@ public class HttpClient extends ContainerLifeCycle
private volatile ByteBufferPool byteBufferPool;
private volatile Scheduler scheduler;
private volatile SelectorManager selectorManager;
private volatile String agent = "Jetty/" + Jetty.VERSION;
private volatile HttpField agentField = new HttpField(HttpHeader.USER_AGENT, "Jetty/" + Jetty.VERSION);
private volatile boolean followRedirects = true;
private volatile int maxConnectionsPerDestination = 64;
private volatile int maxRequestsQueuedPerDestination = 1024;
@ -135,6 +136,7 @@ public class HttpClient extends ContainerLifeCycle
private volatile boolean tcpNoDelay = true;
private volatile boolean dispatchIO = true;
private volatile ProxyConfiguration proxyConfig;
private volatile HttpField encodingField;
/**
* Creates a {@link HttpClient} instance that can perform requests to non-TLS destinations only
@ -249,6 +251,9 @@ public class HttpClient extends ContainerLifeCycle
}
/**
* Returns a <em>non</em> thread-safe list of {@link Request.Listener}s that can be modified before
* performing requests.
*
* @return a list of {@link Request.Listener} that can be used to add and remove listeners
*/
public List<Request.Listener> getRequestListeners()
@ -293,6 +298,9 @@ public class HttpClient extends ContainerLifeCycle
}
/**
* Returns a <em>non</em> thread-safe set of {@link ContentDecoder.Factory}s that can be modified before
* performing requests.
*
* @return a set of {@link ContentDecoder.Factory} that can be used to add and remove content decoder factories
*/
public Set<ContentDecoder.Factory> getContentDecoderFactories()
@ -381,9 +389,9 @@ public class HttpClient extends ContainerLifeCycle
return new HttpRequest(this, uri);
}
protected Request copyRequest(Request oldRequest, String newURI)
protected Request copyRequest(Request oldRequest, URI newURI)
{
Request newRequest = new HttpRequest(this, oldRequest.getConversationID(), URI.create(newURI));
Request newRequest = new HttpRequest(this, oldRequest.getConversationID(), newURI);
newRequest.method(oldRequest.getMethod())
.version(oldRequest.getVersion())
.content(oldRequest.getContent());
@ -537,8 +545,11 @@ public class HttpClient extends ContainerLifeCycle
protected ProtocolHandler findProtocolHandler(Request request, Response response)
{
for (ProtocolHandler handler : getProtocolHandlers())
// Optimized to avoid allocations of iterator instances
List<ProtocolHandler> protocolHandlers = getProtocolHandlers();
for (int i = 0; i < protocolHandlers.size(); ++i)
{
ProtocolHandler handler = protocolHandlers.get(i);
if (handler.accept(request, response))
return handler;
}
@ -614,19 +625,21 @@ public class HttpClient extends ContainerLifeCycle
}
/**
* @return the "User-Agent" HTTP header string of this {@link HttpClient}
* @return the "User-Agent" HTTP field of this {@link HttpClient}
*/
public String getUserAgent()
public HttpField getUserAgentField()
{
return agent;
return agentField;
}
/**
* @param agent the "User-Agent" HTTP header string of this {@link HttpClient}
*/
public void setUserAgent(String agent)
public void setUserAgentField(HttpField agent)
{
this.agent = agent;
if (agent.getHeader() != HttpHeader.USER_AGENT)
throw new IllegalArgumentException();
this.agentField = agent;
}
/**
@ -844,6 +857,11 @@ public class HttpClient extends ContainerLifeCycle
this.proxyConfig = proxyConfig;
}
protected HttpField getAcceptEncodingField()
{
return encodingField;
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
@ -930,4 +948,118 @@ public class HttpClient extends ContainerLifeCycle
this.promise = promise;
}
}
private class ContentDecoderFactorySet implements Set<ContentDecoder.Factory>
{
private final Set<ContentDecoder.Factory> set = new HashSet<>();
@Override
public boolean add(ContentDecoder.Factory e)
{
boolean result = set.add(e);
invalidate();
return result;
}
@Override
public boolean addAll(Collection<? extends ContentDecoder.Factory> c)
{
boolean result = set.addAll(c);
invalidate();
return result;
}
@Override
public boolean remove(Object o)
{
boolean result = set.remove(o);
invalidate();
return result;
}
@Override
public boolean removeAll(Collection<?> c)
{
boolean result = set.removeAll(c);
invalidate();
return result;
}
@Override
public boolean retainAll(Collection<?> c)
{
boolean result = set.retainAll(c);
invalidate();
return result;
}
@Override
public void clear()
{
set.clear();
invalidate();
}
@Override
public int size()
{
return set.size();
}
@Override
public boolean isEmpty()
{
return set.isEmpty();
}
@Override
public boolean contains(Object o)
{
return set.contains(o);
}
@Override
public boolean containsAll(Collection<?> c)
{
return set.containsAll(c);
}
@Override
public Iterator<ContentDecoder.Factory> iterator()
{
return set.iterator();
}
@Override
public Object[] toArray()
{
return set.toArray();
}
@Override
public <T> T[] toArray(T[] a)
{
return set.toArray(a);
}
protected void invalidate()
{
if (set.isEmpty())
{
encodingField = null;
}
else
{
StringBuilder value = new StringBuilder();
for (Iterator<ContentDecoder.Factory> iterator = set.iterator(); iterator.hasNext();)
{
ContentDecoder.Factory decoderFactory = iterator.next();
value.append(decoderFactory.getEncoding());
if (iterator.hasNext())
value.append(",");
}
encodingField = new HttpField(HttpHeader.ACCEPT_ENCODING, value.toString());
}
}
}
}

View File

@ -20,14 +20,12 @@ package org.eclipse.jetty.client;
import java.io.UnsupportedEncodingException;
import java.net.HttpCookie;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@ -37,8 +35,10 @@ import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MimeTypes;
@ -51,6 +51,7 @@ import org.eclipse.jetty.util.log.Logger;
public class HttpConnection extends AbstractConnection implements Connection
{
private static final Logger LOG = Log.getLogger(HttpConnection.class);
private static final HttpField CHUNKED_FIELD = new HttpField(HttpHeader.TRANSFER_ENCODING, HttpHeaderValue.CHUNKED);
private final AtomicReference<HttpExchange> exchange = new AtomicReference<>();
private final HttpClient client;
@ -139,9 +140,6 @@ public class HttpConnection extends AbstractConnection implements Connection
if (request.getVersion() == null)
request.version(HttpVersion.HTTP_1_1);
if (request.getAgent() == null)
request.agent(client.getUserAgent());
if (request.getIdleTimeout() <= 0)
request.idleTimeout(client.getIdleTimeout(), TimeUnit.MILLISECONDS);
@ -150,16 +148,19 @@ public class HttpConnection extends AbstractConnection implements Connection
HttpFields headers = request.getHeaders();
ContentProvider content = request.getContent();
if (request.getAgent() == null)
headers.put(client.getUserAgentField());
// Make sure the path is there
String path = request.getPath();
if (path.matches("\\s*"))
if (path.trim().length() == 0)
{
path = "/";
request.path(path);
}
if (destination.isProxied() && HttpMethod.CONNECT != request.getMethod())
{
path = request.getURI();
path = request.getURI().toString();
request.path(path);
}
@ -208,13 +209,7 @@ public class HttpConnection extends AbstractConnection implements Connection
if (version.getVersion() > 10)
{
if (!headers.containsKey(HttpHeader.HOST.asString()))
{
String value = request.getHost();
int port = request.getPort();
if (port > 0)
value += ":" + port;
headers.put(HttpHeader.HOST, value);
}
headers.put(getDestination().getHostField());
}
// Add content headers
@ -229,12 +224,12 @@ public class HttpConnection extends AbstractConnection implements Connection
else
{
if (!headers.containsKey(HttpHeader.TRANSFER_ENCODING.asString()))
headers.put(HttpHeader.TRANSFER_ENCODING, "chunked");
headers.put(CHUNKED_FIELD);
}
}
// Cookies
List<HttpCookie> cookies = client.getCookieStore().get(URI.create(request.getURI()));
List<HttpCookie> cookies = client.getCookieStore().get(request.getURI());
StringBuilder cookieString = null;
for (int i = 0; i < cookies.size(); ++i)
{
@ -255,19 +250,9 @@ public class HttpConnection extends AbstractConnection implements Connection
if (!headers.containsKey(HttpHeader.ACCEPT_ENCODING.asString()))
{
Set<ContentDecoder.Factory> decoderFactories = client.getContentDecoderFactories();
if (!decoderFactories.isEmpty())
{
StringBuilder value = new StringBuilder();
for (Iterator<ContentDecoder.Factory> iterator = decoderFactories.iterator(); iterator.hasNext();)
{
ContentDecoder.Factory decoderFactory = iterator.next();
value.append(decoderFactory.getEncoding());
if (iterator.hasNext())
value.append(",");
}
headers.put(HttpHeader.ACCEPT_ENCODING, value.toString());
}
HttpField acceptEncodingField = client.getAcceptEncodingField();
if (acceptEncodingField != null)
headers.put(acceptEncodingField);
}
}

View File

@ -38,6 +38,7 @@ 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.client.util.TimedResponseListener;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
@ -63,6 +64,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
private final RequestNotifier requestNotifier;
private final ResponseNotifier responseNotifier;
private final InetSocketAddress proxyAddress;
private final HttpField hostField;
public HttpDestination(HttpClient client, String scheme, String host, int port)
{
@ -79,6 +81,12 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
ProxyConfiguration proxyConfig = client.getProxyConfiguration();
proxyAddress = proxyConfig != null && proxyConfig.matches(host, port) ?
new InetSocketAddress(proxyConfig.getHost(), proxyConfig.getPort()) : null;
String hostValue = host;
if ("https".equalsIgnoreCase(scheme) && port != 443 ||
"http".equalsIgnoreCase(scheme) && port != 80)
hostValue += ":" + port;
hostField = new HttpField(HttpHeader.HOST, hostValue);
}
protected BlockingQueue<Connection> getIdleConnections()
@ -121,6 +129,11 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
return proxyAddress != null;
}
public HttpField getHostField()
{
return hostField;
}
public void send(Request request, List<Response.ResponseListener> listeners)
{
if (!scheme.equals(request.getScheme()))

View File

@ -224,13 +224,13 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
return false;
}
private void storeCookie(String uri, HttpField field)
private void storeCookie(URI uri, HttpField field)
{
try
{
Map<String, List<String>> header = new HashMap<>(1);
header.put(field.getHeader().asString(), Collections.singletonList(field.getValue()));
connection.getHttpClient().getCookieManager().put(URI.create(uri), header);
connection.getHttpClient().getCookieManager().put(uri, header);
}
catch (IOException x)
{

View File

@ -59,6 +59,7 @@ public class HttpRequest implements Request
private final long conversation;
private final String host;
private final int port;
private URI uri;
private String scheme;
private String path;
private HttpMethod method;
@ -91,6 +92,7 @@ public class HttpRequest implements Request
param(parts[0], parts.length < 2 ? "" : urlDecode(parts[1]));
}
}
this.uri = buildURI();
followRedirects(client.isFollowRedirects());
}
@ -123,6 +125,7 @@ public class HttpRequest implements Request
public Request scheme(String scheme)
{
this.scheme = scheme;
this.uri = buildURI();
return this;
}
@ -161,19 +164,14 @@ public class HttpRequest implements Request
public Request path(String path)
{
this.path = path;
this.uri = buildURI();
return this;
}
@Override
public String getURI()
public URI getURI()
{
String scheme = getScheme();
String result = scheme + "://" + getHost();
int port = getPort();
result += "http".equals(scheme) && port != 80 ? ":" + port : "";
result += "https".equals(scheme) && port != 443 ? ":" + port : "";
result += getPath();
return result;
return uri;
}
@Override
@ -247,9 +245,14 @@ public class HttpRequest implements Request
@Override
public <T extends RequestListener> List<T> getRequestListeners(Class<T> type)
{
// This method is invoked often in a request/response conversation,
// so we avoid allocation if there is no need to filter.
if (type == null)
return (List<T>)requestListeners;
ArrayList<T> result = new ArrayList<>();
for (RequestListener listener : requestListeners)
if (type == null || type.isInstance(listener))
if (type.isInstance(listener))
result.add((T)listener);
return result;
}
@ -466,6 +469,11 @@ public class HttpRequest implements Request
return aborted;
}
private URI buildURI()
{
return URI.create(getScheme() + "://" + getHost() + ":" + getPort() + getPath());
}
@Override
public String toString()
{

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.client;
import java.net.URI;
import java.util.List;
import org.eclipse.jetty.client.api.Request;
@ -65,7 +66,7 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
{
Request request = result.getRequest();
Response response = result.getResponse();
String location = response.getHeaders().get("location");
URI location = URI.create(response.getHeaders().get("location"));
int status = response.getStatus();
switch (status)
{
@ -103,7 +104,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, URI location)
{
final Request request = result.getRequest();
HttpConversation conversation = client.getConversation(request.getConversationID(), false);

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.client;
import java.util.List;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -35,18 +37,27 @@ public class RequestNotifier
public void notifyQueued(Request request)
{
for (Request.QueuedListener listener : request.getRequestListeners(Request.QueuedListener.class))
notifyQueued(listener, request);
for (Request.Listener listener : client.getRequestListeners())
// Optimized to avoid allocations of iterator instances
List<Request.RequestListener> requestListeners = request.getRequestListeners(null);
for (int i = 0; i < requestListeners.size(); ++i)
{
Request.RequestListener listener = requestListeners.get(i);
if (listener instanceof Request.QueuedListener)
notifyQueued((Request.QueuedListener)listener, request);
}
List<Request.Listener> listeners = client.getRequestListeners();
for (int i = 0; i < listeners.size(); ++i)
{
Request.Listener listener = listeners.get(i);
notifyQueued(listener, request);
}
}
private void notifyQueued(Request.QueuedListener listener, Request request)
{
try
{
if (listener != null)
listener.onQueued(request);
listener.onQueued(request);
}
catch (Exception x)
{
@ -56,18 +67,27 @@ public class RequestNotifier
public void notifyBegin(Request request)
{
for (Request.BeginListener listener : request.getRequestListeners(Request.BeginListener.class))
notifyBegin(listener, request);
for (Request.Listener listener : client.getRequestListeners())
// Optimized to avoid allocations of iterator instances
List<Request.RequestListener> requestListeners = request.getRequestListeners(null);
for (int i = 0; i < requestListeners.size(); ++i)
{
Request.RequestListener listener = requestListeners.get(i);
if (listener instanceof Request.BeginListener)
notifyBegin((Request.BeginListener)listener, request);
}
List<Request.Listener> listeners = client.getRequestListeners();
for (int i = 0; i < listeners.size(); ++i)
{
Request.Listener listener = listeners.get(i);
notifyBegin(listener, request);
}
}
private void notifyBegin(Request.BeginListener listener, Request request)
{
try
{
if (listener != null)
listener.onBegin(request);
listener.onBegin(request);
}
catch (Exception x)
{
@ -77,18 +97,27 @@ public class RequestNotifier
public void notifyHeaders(Request request)
{
for (Request.HeadersListener listener : request.getRequestListeners(Request.HeadersListener.class))
notifyHeaders(listener, request);
for (Request.Listener listener : client.getRequestListeners())
// Optimized to avoid allocations of iterator instances
List<Request.RequestListener> requestListeners = request.getRequestListeners(null);
for (int i = 0; i < requestListeners.size(); ++i)
{
Request.RequestListener listener = requestListeners.get(i);
if (listener instanceof Request.HeadersListener)
notifyHeaders((Request.HeadersListener)listener, request);
}
List<Request.Listener> listeners = client.getRequestListeners();
for (int i = 0; i < listeners.size(); ++i)
{
Request.Listener listener = listeners.get(i);
notifyHeaders(listener, request);
}
}
private void notifyHeaders(Request.HeadersListener listener, Request request)
{
try
{
if (listener != null)
listener.onHeaders(request);
listener.onHeaders(request);
}
catch (Exception x)
{
@ -98,18 +127,27 @@ public class RequestNotifier
public void notifyCommit(Request request)
{
for (Request.CommitListener listener : request.getRequestListeners(Request.CommitListener.class))
notifyCommit(listener, request);
for (Request.Listener listener : client.getRequestListeners())
// Optimized to avoid allocations of iterator instances
List<Request.RequestListener> requestListeners = request.getRequestListeners(null);
for (int i = 0; i < requestListeners.size(); ++i)
{
Request.RequestListener listener = requestListeners.get(i);
if (listener instanceof Request.CommitListener)
notifyCommit((Request.CommitListener)listener, request);
}
List<Request.Listener> listeners = client.getRequestListeners();
for (int i = 0; i < listeners.size(); ++i)
{
Request.Listener listener = listeners.get(i);
notifyCommit(listener, request);
}
}
private void notifyCommit(Request.CommitListener listener, Request request)
{
try
{
if (listener != null)
listener.onCommit(request);
listener.onCommit(request);
}
catch (Exception x)
{
@ -119,18 +157,27 @@ public class RequestNotifier
public void notifySuccess(Request request)
{
for (Request.SuccessListener listener : request.getRequestListeners(Request.SuccessListener.class))
notifySuccess(listener, request);
for (Request.Listener listener : client.getRequestListeners())
// Optimized to avoid allocations of iterator instances
List<Request.RequestListener> requestListeners = request.getRequestListeners(null);
for (int i = 0; i < requestListeners.size(); ++i)
{
Request.RequestListener listener = requestListeners.get(i);
if (listener instanceof Request.SuccessListener)
notifySuccess((Request.SuccessListener)listener, request);
}
List<Request.Listener> listeners = client.getRequestListeners();
for (int i = 0; i < listeners.size(); ++i)
{
Request.Listener listener = listeners.get(i);
notifySuccess(listener, request);
}
}
private void notifySuccess(Request.SuccessListener listener, Request request)
{
try
{
if (listener != null)
listener.onSuccess(request);
listener.onSuccess(request);
}
catch (Exception x)
{
@ -140,18 +187,27 @@ public class RequestNotifier
public void notifyFailure(Request request, Throwable failure)
{
for (Request.FailureListener listener : request.getRequestListeners(Request.FailureListener.class))
notifyFailure(listener, request, failure);
for (Request.Listener listener : client.getRequestListeners())
// Optimized to avoid allocations of iterator instances
List<Request.RequestListener> requestListeners = request.getRequestListeners(null);
for (int i = 0; i < requestListeners.size(); ++i)
{
Request.RequestListener listener = requestListeners.get(i);
if (listener instanceof Request.FailureListener)
notifyFailure((Request.FailureListener)listener, request, failure);
}
List<Request.Listener> listeners = client.getRequestListeners();
for (int i = 0; i < listeners.size(); ++i)
{
Request.Listener listener = listeners.get(i);
notifyFailure(listener, request, failure);
}
}
private void notifyFailure(Request.FailureListener listener, Request request, Throwable failure)
{
try
{
if (listener != null)
listener.onFailure(request, failure);
listener.onFailure(request, failure);
}
catch (Exception x)
{

View File

@ -42,9 +42,13 @@ public class ResponseNotifier
public void notifyBegin(List<Response.ResponseListener> listeners, Response response)
{
for (Response.ResponseListener listener : listeners)
// Optimized to avoid allocations of iterator instances
for (int i = 0; i < listeners.size(); ++i)
{
Response.ResponseListener listener = listeners.get(i);
if (listener instanceof Response.BeginListener)
notifyBegin((Response.BeginListener)listener, response);
}
}
private void notifyBegin(Response.BeginListener listener, Response response)
@ -62,9 +66,13 @@ public class ResponseNotifier
public boolean notifyHeader(List<Response.ResponseListener> listeners, Response response, HttpField field)
{
boolean result = true;
for (Response.ResponseListener listener : listeners)
// Optimized to avoid allocations of iterator instances
for (int i = 0; i < listeners.size(); ++i)
{
Response.ResponseListener listener = listeners.get(i);
if (listener instanceof Response.HeaderListener)
result &= notifyHeader((Response.HeaderListener)listener, response, field);
}
return result;
}
@ -83,9 +91,13 @@ public class ResponseNotifier
public void notifyHeaders(List<Response.ResponseListener> listeners, Response response)
{
for (Response.ResponseListener listener : listeners)
// Optimized to avoid allocations of iterator instances
for (int i = 0; i < listeners.size(); ++i)
{
Response.ResponseListener listener = listeners.get(i);
if (listener instanceof Response.HeadersListener)
notifyHeaders((Response.HeadersListener)listener, response);
}
}
private void notifyHeaders(Response.HeadersListener listener, Response response)
@ -102,10 +114,13 @@ public class ResponseNotifier
public void notifyContent(List<Response.ResponseListener> listeners, Response response, ByteBuffer buffer)
{
for (Response.ResponseListener listener : listeners)
// Optimized to avoid allocations of iterator instances
for (int i = 0; i < listeners.size(); ++i)
{
Response.ResponseListener listener = listeners.get(i);
if (listener instanceof Response.ContentListener)
notifyContent((Response.ContentListener)listener, response, buffer);
}
}
private void notifyContent(Response.ContentListener listener, Response response, ByteBuffer buffer)
@ -122,9 +137,13 @@ public class ResponseNotifier
public void notifySuccess(List<Response.ResponseListener> listeners, Response response)
{
for (Response.ResponseListener listener : listeners)
// Optimized to avoid allocations of iterator instances
for (int i = 0; i < listeners.size(); ++i)
{
Response.ResponseListener listener = listeners.get(i);
if (listener instanceof Response.SuccessListener)
notifySuccess((Response.SuccessListener)listener, response);
}
}
private void notifySuccess(Response.SuccessListener listener, Response response)
@ -141,9 +160,13 @@ public class ResponseNotifier
public void notifyFailure(List<Response.ResponseListener> listeners, Response response, Throwable failure)
{
for (Response.ResponseListener listener : listeners)
// Optimized to avoid allocations of iterator instances
for (int i = 0; i < listeners.size(); ++i)
{
Response.ResponseListener listener = listeners.get(i);
if (listener instanceof Response.FailureListener)
notifyFailure((Response.FailureListener)listener, response, failure);
}
}
private void notifyFailure(Response.FailureListener listener, Response response, Throwable failure)
@ -160,9 +183,13 @@ public class ResponseNotifier
public void notifyComplete(List<Response.ResponseListener> listeners, Result result)
{
for (Response.ResponseListener listener : listeners)
// Optimized to avoid allocations of iterator instances
for (int i = 0; i < listeners.size(); ++i)
{
Response.ResponseListener listener = listeners.get(i);
if (listener instanceof Response.CompleteListener)
notifyComplete((Response.CompleteListener)listener, result);
}
}
private void notifyComplete(Response.CompleteListener listener, Result result)

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.client.api;
import java.net.URI;
import org.eclipse.jetty.util.Attributes;
/**
@ -42,7 +44,7 @@ public interface Authentication
* @param realm the authentication realm as provided in the {@code WWW-Authenticate} response header
* @return true if this authentication matches, false otherwise
*/
boolean matches(String type, String uri, String realm);
boolean matches(String type, URI uri, String realm);
/**
* Executes the authentication mechanism for the given request, returning a {@link Result} that can be
@ -70,7 +72,7 @@ public interface Authentication
/**
* @return the URI of the request that has been used to generate this {@link Result}
*/
String getURI();
URI getURI();
/**
* Applies the authentication result to the given request.

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.client.api;
import java.net.URI;
/**
* A store for {@link Authentication}s and {@link Authentication.Result}s.
*/
@ -48,7 +50,7 @@ public interface AuthenticationStore
* @param realm the authentication realm
* @return the authentication that matches the given parameters, or null
*/
public Authentication findAuthentication(String type, String uri, String realm);
public Authentication findAuthentication(String type, URI uri, String realm);
/**
* @param result the {@link Authentication.Result} to add
@ -72,5 +74,5 @@ public interface AuthenticationStore
* @param uri the request URI
* @return the {@link Authentication.Result} that matches the given URI, or null
*/
public Authentication.Result findAuthenticationResult(String uri);
public Authentication.Result findAuthenticationResult(URI uri);
}

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.client.api;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Path;
import java.util.EventListener;
import java.util.List;
@ -96,7 +97,7 @@ public interface Request
/**
* @return the full URI of this request such as "http://host:port/path"
*/
String getURI();
URI getURI();
/**
* @return the HTTP version of this request, such as "HTTP/1.1"

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.client.util;
import java.net.URI;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.api.AuthenticationStore;
@ -37,7 +39,7 @@ import org.eclipse.jetty.util.StringUtil;
*/
public class BasicAuthentication implements Authentication
{
private final String uri;
private final URI uri;
private final String realm;
private final String user;
private final String password;
@ -48,7 +50,7 @@ public class BasicAuthentication implements Authentication
* @param user the user that wants to authenticate
* @param password the password of the user
*/
public BasicAuthentication(String uri, String realm, String user, String password)
public BasicAuthentication(URI uri, String realm, String user, String password)
{
this.uri = uri;
this.realm = realm;
@ -57,12 +59,12 @@ public class BasicAuthentication implements Authentication
}
@Override
public boolean matches(String type, String uri, String realm)
public boolean matches(String type, URI uri, String realm)
{
if (!"basic".equalsIgnoreCase(type))
return false;
if (!uri.startsWith(this.uri))
if (!uri.toString().startsWith(this.uri.toString()))
return false;
return this.realm.equals(realm);
@ -78,17 +80,17 @@ public class BasicAuthentication implements Authentication
private static class BasicResult implements Result
{
private final String uri;
private final URI uri;
private final String value;
public BasicResult(String uri, String value)
public BasicResult(URI uri, String value)
{
this.uri = uri;
this.value = value;
}
@Override
public String getURI()
public URI getURI()
{
return uri;
}
@ -96,7 +98,7 @@ public class BasicAuthentication implements Authentication
@Override
public void apply(Request request)
{
if (request.getURI().startsWith(uri))
if (request.getURI().toString().startsWith(uri.toString()))
request.header(HttpHeader.AUTHORIZATION.asString(), value);
}

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.client.util;
import java.net.URI;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@ -52,7 +53,7 @@ public class DigestAuthentication implements Authentication
{
private static final Pattern PARAM_PATTERN = Pattern.compile("([^=]+)=(.*)");
private final String uri;
private final URI uri;
private final String realm;
private final String user;
private final String password;
@ -63,7 +64,7 @@ public class DigestAuthentication implements Authentication
* @param user the user that wants to authenticate
* @param password the password of the user
*/
public DigestAuthentication(String uri, String realm, String user, String password)
public DigestAuthentication(URI uri, String realm, String user, String password)
{
this.uri = uri;
this.realm = realm;
@ -72,12 +73,12 @@ public class DigestAuthentication implements Authentication
}
@Override
public boolean matches(String type, String uri, String realm)
public boolean matches(String type, URI uri, String realm)
{
if (!"digest".equalsIgnoreCase(type))
return false;
if (!uri.startsWith(this.uri))
if (!uri.toString().startsWith(this.uri.toString()))
return false;
return this.realm.equals(realm);
@ -180,7 +181,7 @@ public class DigestAuthentication implements Authentication
private class DigestResult implements Result
{
private final AtomicInteger nonceCount = new AtomicInteger();
private final String uri;
private final URI uri;
private final byte[] content;
private final String realm;
private final String user;
@ -190,7 +191,7 @@ public class DigestAuthentication implements Authentication
private final String qop;
private final String opaque;
public DigestResult(String uri, byte[] content, String realm, String user, String password, String algorithm, String nonce, String qop, String opaque)
public DigestResult(URI uri, byte[] content, String realm, String user, String password, String algorithm, String nonce, String qop, String opaque)
{
this.uri = uri;
this.content = content;
@ -204,7 +205,7 @@ public class DigestAuthentication implements Authentication
}
@Override
public String getURI()
public URI getURI()
{
return uri;
}
@ -212,7 +213,7 @@ public class DigestAuthentication implements Authentication
@Override
public void apply(Request request)
{
if (!request.getURI().startsWith(uri))
if (!request.getURI().toString().startsWith(uri.toString()))
return;
MessageDigest digester = getMessageDigest(algorithm);

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.client;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@ -34,7 +35,6 @@ import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.BasicAuthentication;
import org.eclipse.jetty.client.util.DigestAuthentication;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.security.Authenticator;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
@ -99,7 +99,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
public void test_BasicAuthentication() throws Exception
{
startBasic(new EmptyServerHandler());
String uri = scheme + "://localhost:" + connector.getLocalPort();
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
test_Authentication(new BasicAuthentication(uri, realm, "basic", "basic"));
}
@ -107,7 +107,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
public void test_DigestAuthentication() throws Exception
{
startDigest(new EmptyServerHandler());
String uri = scheme + "://localhost:" + connector.getLocalPort();
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
test_Authentication(new DigestAuthentication(uri, realm, "digest", "digest"));
}
@ -148,6 +148,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
client.getRequestListeners().add(requestListener);
// Request with authentication causes a 401 (no previous successful authentication) + 200
request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
response = request.timeout(5, TimeUnit.SECONDS).send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
@ -167,7 +168,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
// Further requests do not trigger 401 because there is a previous successful authentication
// Remove existing header to be sure it's added by the implementation
request.header(HttpHeader.AUTHORIZATION.asString(), null);
request = client.newRequest("localhost", connector.getLocalPort()).scheme(scheme).path("/secure");
response = request.timeout(5, TimeUnit.SECONDS).send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
@ -191,7 +192,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
}
});
String uri = scheme + "://localhost:" + connector.getLocalPort();
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, "basic", "basic"));
final CountDownLatch requests = new CountDownLatch(3);
@ -230,7 +231,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
}
});
String uri = scheme + "://localhost:" + connector.getLocalPort();
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, "basic", "basic"));
final CountDownLatch requests = new CountDownLatch(3);
@ -272,7 +273,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
client.getRequestListeners().add(requestListener);
AuthenticationStore authenticationStore = client.getAuthenticationStore();
String uri = scheme + "://localhost:" + connector.getLocalPort();
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
BasicAuthentication authentication = new BasicAuthentication(uri, realm, "basic", "basic");
authenticationStore.addAuthentication(authentication);

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.client;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
@ -113,13 +114,14 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest
Assert.assertTrue(failures.toString(), failures.isEmpty());
}
private void test(Random random, final CountDownLatch latch, final List<String> failures)
private void test(Random random, final CountDownLatch latch, final List<String> failures) throws InterruptedException
{
int maxContentLength = 64 * 1024;
// Choose a random destination
String host = random.nextBoolean() ? "localhost" : "127.0.0.1";
Request request = client.newRequest(host, connector.getLocalPort()).scheme(scheme);
URI uri = URI.create(scheme + "://" + host + ":" + connector.getLocalPort());
Request request = client.newRequest(uri);
// Choose a random method
HttpMethod method = random.nextBoolean() ? HttpMethod.GET : HttpMethod.POST;
@ -147,6 +149,7 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest
break;
}
final CountDownLatch requestLatch = new CountDownLatch(1);
request.send(new Response.Listener.Empty()
{
private final AtomicInteger contentLength = new AtomicInteger();
@ -175,9 +178,11 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest
}
if (contentLength.get() != 0)
failures.add("Content length mismatch " + contentLength);
requestLatch.countDown();
latch.countDown();
}
});
requestLatch.await(5, TimeUnit.SECONDS);
}
private class LoadHandler extends AbstractHandler

View File

@ -192,7 +192,7 @@ public class Usage
HttpClient client = new HttpClient();
client.start();
String uri = "http://localhost:8080/secure";
URI uri = URI.create("http://localhost:8080/secure");
// Setup Basic authentication credentials for TestRealm
client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, "TestRealm", "username", "password"));

View File

@ -25,6 +25,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.DriverManager;
@ -301,7 +302,7 @@ public class JdbcLoginServiceTest
executor.setName(executor.getName() + "-client");
_client.setExecutor(executor);
AuthenticationStore authStore = _client.getAuthenticationStore();
authStore.addAuthentication(new BasicAuthentication(_baseUrl, __realm, "jetty", "jetty"));
authStore.addAuthentication(new BasicAuthentication(URI.create(_baseUrl), __realm, "jetty", "jetty"));
_client.start();
}