440106 - Improve ProtocolHandler APIs.

Introduced ProtocolHandlers, the container for ProtocolHandler
instances, which now have also a name.
This commit is contained in:
Simone Bordet 2015-03-31 19:17:02 +02:00
parent 6263972d1e
commit 48dd4d8b56
9 changed files with 183 additions and 32 deletions

View File

@ -27,19 +27,27 @@ import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpHeaderValue;
/**
* <p>A protocol handler that handles the 100 response code.</p>
*/
public class ContinueProtocolHandler implements ProtocolHandler public class ContinueProtocolHandler implements ProtocolHandler
{ {
public static final String NAME = "continue";
private static final String ATTRIBUTE = ContinueProtocolHandler.class.getName() + ".100continue"; private static final String ATTRIBUTE = ContinueProtocolHandler.class.getName() + ".100continue";
private final HttpClient client;
private final ResponseNotifier notifier; private final ResponseNotifier notifier;
public ContinueProtocolHandler(HttpClient client) public ContinueProtocolHandler()
{ {
this.client = client;
this.notifier = new ResponseNotifier(); this.notifier = new ResponseNotifier();
} }
@Override
public String getName()
{
return NAME;
}
@Override @Override
public boolean accept(Request request, Response response) public boolean accept(Request request, Response response)
{ {

View File

@ -109,7 +109,7 @@ public class HttpClient extends ContainerLifeCycle
private static final Logger LOG = Log.getLogger(HttpClient.class); private static final Logger LOG = Log.getLogger(HttpClient.class);
private final ConcurrentMap<Origin, HttpDestination> destinations = new ConcurrentHashMap<>(); private final ConcurrentMap<Origin, HttpDestination> destinations = new ConcurrentHashMap<>();
private final List<ProtocolHandler> handlers = new ArrayList<>(); private final ProtocolHandlers handlers = new ProtocolHandlers();
private final List<Request.Listener> requestListeners = new ArrayList<>(); private final List<Request.Listener> requestListeners = new ArrayList<>();
private final AuthenticationStore authenticationStore = new HttpAuthenticationStore(); private final AuthenticationStore authenticationStore = new HttpAuthenticationStore();
private final Set<ContentDecoder.Factory> decoderFactories = new ContentDecoderFactorySet(); private final Set<ContentDecoder.Factory> decoderFactories = new ContentDecoderFactorySet();
@ -210,10 +210,10 @@ public class HttpClient extends ContainerLifeCycle
resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout()); resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout());
handlers.add(new ContinueProtocolHandler(this)); handlers.put(new ContinueProtocolHandler());
handlers.add(new RedirectProtocolHandler(this)); handlers.put(new RedirectProtocolHandler(this));
handlers.add(new WWWAuthenticationProtocolHandler(this)); handlers.put(new WWWAuthenticationProtocolHandler(this));
handlers.add(new ProxyAuthenticationProtocolHandler(this)); handlers.put(new ProxyAuthenticationProtocolHandler(this));
decoderFactories.add(new GZIPContentDecoder.Factory()); decoderFactories.add(new GZIPContentDecoder.Factory());
@ -547,22 +547,14 @@ public class HttpClient extends ContainerLifeCycle
return new HttpConversation(); return new HttpConversation();
} }
protected List<ProtocolHandler> getProtocolHandlers() public ProtocolHandlers getProtocolHandlers()
{ {
return handlers; return handlers;
} }
protected ProtocolHandler findProtocolHandler(Request request, Response response) protected ProtocolHandler findProtocolHandler(Request request, Response response)
{ {
// Optimized to avoid allocations of iterator instances return handlers.find(request, response);
List<ProtocolHandler> protocolHandlers = getProtocolHandlers();
for (int i = 0; i < protocolHandlers.size(); ++i)
{
ProtocolHandler handler = protocolHandlers.get(i);
if (handler.accept(request, response))
return handler;
}
return null;
} }
/** /**

View File

@ -21,9 +21,38 @@ package org.eclipse.jetty.client;
import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Response;
/**
* <p>A protocol handler performs HTTP protocol operations on
* behalf of the application, typically like a browser would.</p>
* <p>A typical example is handling HTTP redirects. {@link HttpClient}
* could just return the redirect response to the application,
* but the application would have to implement the redirect
* functionality (while browsers do this automatically).</p>
*/
public interface ProtocolHandler public interface ProtocolHandler
{ {
/**
* @return a unique name among protocol handlers
*/
public String getName();
/**
* <p>Inspects the given {@code request} and {@code response}
* to detect whether this protocol handler should handle them.</p>
* <p>For example, a redirect protocol handler can inspect the
* response code and return true if it is a redirect response code.</p>
* <p>This method is being called just after the response line has
* been parsed, and before the response headers are available.</p>
*
* @param request the request to accept
* @param response the response to accept
* @return true if this protocol handler can handle the given request and response
*/
public boolean accept(Request request, Response response); public boolean accept(Request request, Response response);
/**
* @return a response listener that will handle the request and response
* on behalf of the application.
*/
public Response.Listener getResponseListener(); public Response.Listener getResponseListener();
} }

View File

@ -0,0 +1,94 @@
//
// ========================================================================
// Copyright (c) 1995-2015 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.util.LinkedHashMap;
import java.util.Map;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
/**
* <p>A container for {@link ProtocolHandler}s accessible from {@link HttpClient#getProtocolHandlers()}.</p>
*/
public class ProtocolHandlers
{
private final Map<String, ProtocolHandler> handlers = new LinkedHashMap<>();
protected ProtocolHandlers()
{
}
/**
* <p>Stores the given {@code protocolHandler} in this container.</p>
* <p>If a protocol handler with the same name exists, it is
* replaced by the given one, and the existing returned.</p>
*
* @param protocolHandler the protocol handler to store
* @return the existing protocol handler with the same name,
* or null if no protocol handler with that name was already stored
* @see #remove(String)
*/
public ProtocolHandler put(ProtocolHandler protocolHandler)
{
return handlers.put(protocolHandler.getName(), protocolHandler);
}
/**
* <p>Removes the protocol handler with the given name.</p>
*
* @param name the name of the protocol handler to remove
* @return the removed protocol handler, or null if no
* protocol handler with that name was already stored
* @see #put(ProtocolHandler)
* @see #clear()
*/
public ProtocolHandler remove(String name)
{
return handlers.remove(name);
}
/**
* <p>Removes all protocol handlers from this container.</p>
*/
public void clear()
{
handlers.clear();
}
/**
* <p>Finds the first protocol handler that
* {@link ProtocolHandler#accept(Request, Response) accepts}
* the given request and response.</p>
*
* @param request the request to accept
* @param response the response to accept
* @return the protocol handler that accepted the request and response,
* or null if none of the protocol handlers accepted the request and response
*/
public ProtocolHandler find(Request request, Response response)
{
for (ProtocolHandler handler : handlers.values())
{
if (handler.accept(request, response))
return handler;
}
return null;
}
}

View File

@ -25,8 +25,16 @@ import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
/**
* <p>A protocol handler that handles the 401 response code
* in association with the {@code Proxy-Authenticate} header.</p>
*
* @see WWWAuthenticationProtocolHandler
*/
public class ProxyAuthenticationProtocolHandler extends AuthenticationProtocolHandler public class ProxyAuthenticationProtocolHandler extends AuthenticationProtocolHandler
{ {
public static final String NAME = "proxy-authenticate";
public ProxyAuthenticationProtocolHandler(HttpClient client) public ProxyAuthenticationProtocolHandler(HttpClient client)
{ {
this(client, DEFAULT_MAX_CONTENT_LENGTH); this(client, DEFAULT_MAX_CONTENT_LENGTH);
@ -37,6 +45,12 @@ public class ProxyAuthenticationProtocolHandler extends AuthenticationProtocolHa
super(client, maxContentLength); super(client, maxContentLength);
} }
@Override
public String getName()
{
return NAME;
}
@Override @Override
public boolean accept(Request request, Response response) public boolean accept(Request request, Response response)
{ {

View File

@ -22,8 +22,13 @@ 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;
/**
* <p>A protocol handler that handles redirect status codes 301, 302, 303, 307 and 308.</p>
*/
public class RedirectProtocolHandler extends Response.Listener.Adapter implements ProtocolHandler public class RedirectProtocolHandler extends Response.Listener.Adapter implements ProtocolHandler
{ {
public static final String NAME = "redirect";
private final HttpRedirector redirector; private final HttpRedirector redirector;
public RedirectProtocolHandler(HttpClient client) public RedirectProtocolHandler(HttpClient client)
@ -31,6 +36,12 @@ public class RedirectProtocolHandler extends Response.Listener.Adapter implement
redirector = new HttpRedirector(client); redirector = new HttpRedirector(client);
} }
@Override
public String getName()
{
return NAME;
}
@Override @Override
public boolean accept(Request request, Response response) public boolean accept(Request request, Response response)
{ {

View File

@ -25,8 +25,16 @@ import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
/**
* <p>A protocol handler that handles the 401 response code
* in association with the {@code WWW-Authenticate} header.</p>
*
* @see ProxyAuthenticationProtocolHandler
*/
public class WWWAuthenticationProtocolHandler extends AuthenticationProtocolHandler public class WWWAuthenticationProtocolHandler extends AuthenticationProtocolHandler
{ {
public static final String NAME = "www-authenticate";
public WWWAuthenticationProtocolHandler(HttpClient client) public WWWAuthenticationProtocolHandler(HttpClient client)
{ {
this(client, DEFAULT_MAX_CONTENT_LENGTH); this(client, DEFAULT_MAX_CONTENT_LENGTH);
@ -37,6 +45,12 @@ public class WWWAuthenticationProtocolHandler extends AuthenticationProtocolHand
super(client, maxContentLength); super(client, maxContentLength);
} }
@Override
public String getName()
{
return NAME;
}
@Override @Override
public boolean accept(Request request, Response response) public boolean accept(Request request, Response response)
{ {

View File

@ -27,11 +27,8 @@ import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletInputStream; import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -405,7 +402,7 @@ public class HttpClientContinueTest extends AbstractHttpClientServerTest
}); });
client.getProtocolHandlers().clear(); client.getProtocolHandlers().clear();
client.getProtocolHandlers().add(new ContinueProtocolHandler(client) client.getProtocolHandlers().put(new ContinueProtocolHandler()
{ {
@Override @Override
public Response.Listener getResponseListener() public Response.Listener getResponseListener()
@ -609,14 +606,7 @@ public class HttpClientContinueTest extends AbstractHttpClientServerTest
final DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(chunk1)); final DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(chunk1));
List<ProtocolHandler> protocolHandlers = client.getProtocolHandlers(); client.getProtocolHandlers().put(new ContinueProtocolHandler()
for (Iterator<ProtocolHandler> iterator = protocolHandlers.iterator(); iterator.hasNext();)
{
ProtocolHandler protocolHandler = iterator.next();
if (protocolHandler instanceof ContinueProtocolHandler)
iterator.remove();
}
protocolHandlers.add(new ContinueProtocolHandler(client)
{ {
@Override @Override
public Response.Listener getResponseListener() public Response.Listener getResponseListener()

View File

@ -25,7 +25,6 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -528,7 +527,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
final AtomicBoolean aborted = new AtomicBoolean(); final AtomicBoolean aborted = new AtomicBoolean();
final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch latch = new CountDownLatch(1);
client.getProtocolHandlers().clear(); client.getProtocolHandlers().clear();
client.getProtocolHandlers().add(new RedirectProtocolHandler(client) client.getProtocolHandlers().put(new RedirectProtocolHandler(client)
{ {
@Override @Override
public void onComplete(Result result) public void onComplete(Result result)