jetty-9 - HTTP client: javadocs.

This commit is contained in:
Simone Bordet 2012-09-13 16:01:57 +02:00
parent 5315a6dcff
commit 6f8ebc1450
23 changed files with 324 additions and 23 deletions

View File

@ -61,7 +61,13 @@ public class HttpAuthenticationStore implements AuthenticationStore
}
@Override
public void removeAuthenticationResults()
public void removeAuthenticationResult(Authentication.Result result)
{
results.remove(result.getURI());
}
@Override
public void clearAuthenticationResults()
{
results.clear();
}

View File

@ -69,6 +69,7 @@ import org.eclipse.jetty.util.thread.TimerScheduler;
* and HTTP parameters (such as whether to follow redirects).</p>
* <p>{@link HttpClient} transparently pools connections to servers, but allows direct control of connections
* for cases where this is needed.</p>
* <p>{@link HttpClient} also acts as a central configuration point for cookies, via {@link #getCookieStore()}.</p>
* <p>Typical usage:</p>
* <pre>
* // One liner:
@ -264,6 +265,11 @@ public class HttpClient extends AggregateLifeCycle
}
public Destination getDestination(String scheme, String host, int port)
{
return provideDestination(scheme, host, port);
}
private HttpDestination provideDestination(String scheme, String host, int port)
{
String address = address(scheme, host, port);
HttpDestination destination = destinations.get(address);
@ -321,7 +327,7 @@ public class HttpClient extends AggregateLifeCycle
if (port < 0)
port = "https".equals(scheme) ? 443 : 80;
getDestination(scheme, host, port).send(request, listener);
provideDestination(scheme, host, port).send(request, listener);
}
public Executor getExecutor()

View File

@ -146,7 +146,7 @@ public class HttpConnection extends AbstractConnection implements Connection
}
// Cookies
List<HttpCookie> cookies = client.getCookieStore().getCookies(getDestination(), request.path());
List<HttpCookie> cookies = client.getCookieStore().findCookies(getDestination(), request.path());
StringBuilder cookieString = null;
for (int i = 0; i < cookies.size(); ++i)
{

View File

@ -35,7 +35,7 @@ public class HttpCookieStore implements CookieStore
private final ConcurrentMap<String, Queue<HttpCookie>> allCookies = new ConcurrentHashMap<>();
@Override
public List<HttpCookie> getCookies(Destination destination, String path)
public List<HttpCookie> findCookies(Destination destination, String path)
{
List<HttpCookie> result = new ArrayList<>();

View File

@ -91,7 +91,6 @@ public class HttpDestination implements Destination, AutoCloseable
return port;
}
@Override
public void send(Request request, Response.Listener listener)
{
if (!scheme.equals(request.scheme()))

View File

@ -56,7 +56,7 @@ public class HttpRequest implements Request
private long idleTimeout;
private Listener listener;
private ContentProvider content;
private boolean followRedirects = true;
private boolean followRedirects;
private volatile boolean aborted;
public HttpRequest(HttpClient client, URI uri)
@ -81,6 +81,7 @@ public class HttpRequest implements Request
param(parts[0], parts.length < 2 ? "" : urlDecode(parts[1]));
}
}
followRedirects(client.isFollowRedirects());
}
private String urlDecode(String value)

View File

@ -45,7 +45,7 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
case 302:
case 303:
case 307:
return request.followRedirects() || client.isFollowRedirects();
return request.followRedirects();
}
return false;
}

View File

@ -20,16 +20,65 @@ package org.eclipse.jetty.client.api;
import org.eclipse.jetty.util.Attributes;
/**
* {@link Authentication} represents a mechanism to authenticate requests for protected resources.
* <p />
* {@link Authentication}s are added to an {@link AuthenticationStore}, which is then
* {@link #matches(String, String, String) queried} to find the right
* {@link Authentication} mechanism to use based on its type, URI and realm, as returned by
* {@code WWW-Authenticate} response headers.
* <p />
* If an {@link Authentication} mechanism is found, it is then
* {@link #authenticate(Request, ContentResponse, String, Attributes) executed} for the given request,
* returning an {@link Authentication.Result}, which is then stored in the {@link AuthenticationStore}
* so that subsequent requests can be preemptively authenticated.
*/
public interface Authentication
{
/**
* Matches {@link Authentication}s based on the given parameters
* @param type the {@link Authentication} type such as "Basic" or "Digest"
* @param uri the request URI
* @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);
/**
* Executes the authentication mechanism for the given request, returning a {@link Result} that can be
* used to actually authenticate the request via {@link Result#apply(Request)}.
* <p />
* If a request for {@code "/secure"} returns a {@link Result}, then the result may be used for other
* requests such as {@code "/secure/foo"} or {@code "/secure/bar"}, unless those resources are protected
* by other realms.
*
* @param request the request to execute the authentication mechanism for
* @param response the 401 response obtained in the previous attempt to request the protected resource
* @param wwwAuthenticate the {@code WWW-Authenticate} header chosen for this authentication
* (among the many that the response may contain)
* @param context the conversation context in case the authentication needs multiple exchanges
* to be completed and information needs to be stored across exchanges
* @return the authentication result, or null if the authentication could not be performed
*/
Result authenticate(Request request, ContentResponse response, String wwwAuthenticate, Attributes context);
/**
* {@link Result} holds the information needed to authenticate a {@link Request} via {@link #apply(Request)}.
*/
public static interface Result
{
/**
* @return the URI of the request that has been used to generate this {@link Result}
*/
String getURI();
/**
* Applies the authentication result to the given request.
* Typically, a {@code Authorization} header is added to the request, with the right information to
* successfully authenticate at the server.
*
* @param request the request to authenticate
*/
void apply(Request request);
}
}

View File

@ -18,17 +18,54 @@
package org.eclipse.jetty.client.api;
/**
* A store for {@link Authentication}s and {@link Authentication.Result}s.
*/
public interface AuthenticationStore
{
/**
* @param authentication the {@link Authentication} to add
*/
public void addAuthentication(Authentication authentication);
/**
* @param authentication the {@link Authentication} to remove
*/
public void removeAuthentication(Authentication authentication);
/**
* Returns the authentication that matches the given type (for example, "Basic" or "Digest"),
* the given request URI and the given realm.
* If no such authentication can be found, returns null.
*
* @param type the {@link Authentication} type such as "Basic" or "Digest"
* @param uri the request URI
* @param realm the authentication realm
* @return the authentication that matches the given parameters, or null
*/
public Authentication findAuthentication(String type, String uri, String realm);
/**
* @param result the {@link Authentication.Result} to add
*/
public void addAuthenticationResult(Authentication.Result result);
public void removeAuthenticationResults();
/**
* @param result the {@link Authentication.Result} to remove
*/
public void removeAuthenticationResult(Authentication.Result result);
/**
* Removes all authentication results stored
*/
public void clearAuthenticationResults();
/**
* Returns an {@link Authentication.Result} that matches the given URI, or null if no
* {@link Authentication.Result}s match the given URI.
*
* @param uri the request URI
* @return the {@link Authentication.Result} that matches the given URI, or null
*/
public Authentication.Result findAuthenticationResult(String uri);
}

View File

@ -18,8 +18,25 @@
package org.eclipse.jetty.client.api;
/**
* {@link Connection} represent a connection to a {@link Destination} and allow applications to send
* requests via {@link #send(Request, Response.Listener)}.
* <p />
* {@link Connection}s are normally pooled by {@link Destination}s, but unpooled {@link Connection}s
* may be created by applications that want to do their own connection management via
* {@link Destination#newConnection()}.
*/
public interface Connection extends AutoCloseable
{
/**
* Sends a request with an associated response listener.
* <p />
* {@link Request#send(Response.Listener)} will eventually call this method to send the request.
* It is exposed to allow applications to send requests via unpooled connections.
*
* @param request the request to send
* @param listener the response listener
*/
void send(Request request, Response.Listener listener);
@Override

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.client.api;
public interface ContentDecoder
// TODO
interface ContentDecoder
{
}

View File

@ -20,7 +20,21 @@ package org.eclipse.jetty.client.api;
import java.nio.ByteBuffer;
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
import org.eclipse.jetty.client.util.PathContentProvider;
/**
* {@link ContentProvider} provides a repeatable source of request content.
* <p />
* Implementations should return a new "view" over the same content every time {@link #iterator()} is invoked.
* <p />
* Applications should rely on utility classes such as {@link ByteBufferContentProvider}
* or {@link PathContentProvider}.
*/
public interface ContentProvider extends Iterable<ByteBuffer>
{
/**
* @return the content length, if known, or -1 if the content length is unknown
*/
long length();
}

View File

@ -18,7 +18,13 @@
package org.eclipse.jetty.client.api;
/**
* A specialized {@link Response} that can hold a limited content in memory.
*/
public interface ContentResponse extends Response
{
/**
* @return the response content
*/
byte[] content();
}

View File

@ -20,13 +20,39 @@ package org.eclipse.jetty.client.api;
import java.util.List;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http.HttpCookie;
/**
* A store for HTTP cookies that offers methods to match cookies for a given destination and path.
*
* @see HttpClient#getCookieStore()
*/
public interface CookieStore
{
List<HttpCookie> getCookies(Destination destination, String path);
/**
* Returns the non-expired cookies that match the given destination and path,
* recursively matching parent paths (for the same domain) and parent domains
* (for the root path).
*
* @param destination the destination representing the domain
* @param path the request path
* @return the list of matching cookies
*/
List<HttpCookie> findCookies(Destination destination, String path);
/**
* Adds the given cookie to this store for the given destination.
* If the cookie's domain and the destination host do not match, the cookie is not added.
*
* @param destination the destination the cookie should belong to
* @param cookie the cookie to add
* @return whether the cookie has been added or not
*/
boolean addCookie(Destination destination, HttpCookie cookie);
/**
* Removes all the cookies from this store.
*/
void clear();
}

View File

@ -20,15 +20,36 @@ package org.eclipse.jetty.client.api;
import java.util.concurrent.Future;
import org.eclipse.jetty.client.HttpClient;
/**
* {@link Destination} represents the triple made of the {@link #scheme()}, the {@link #host()}
* and the {@link #port()}.
* <p />
* {@link Destination} holds a pool of {@link Connection}s, but allows to create unpooled
* connections if the application wants full control over connection management via {@link #newConnection()}.
* <p />
* {@link Destination}s may be obtained via {@link HttpClient#getDestination(String, String, int)}
*/
public interface Destination
{
/**
* @return the scheme of this destination, such as "http" or "https"
*/
String scheme();
/**
* @return the host of this destination, such as "127.0.0.1" or "google.com"
*/
String host();
/**
* @return the port of this destination such as 80 or 443
*/
int port();
/**
* @return a future to a new, unpooled, {@link Connection}
*/
Future<Connection> newConnection();
void send(Request request, Response.Listener listener);
}

View File

@ -35,6 +35,8 @@ import org.eclipse.jetty.util.Fields;
* <p>You can create {@link Request} objects via {@link HttpClient#newRequest(String)} and
* you can send them using either {@link #send()} for a blocking semantic, or
* {@link #send(Response.Listener)} for an asynchronous semantic.</p>
*
* @see Response
*/
public interface Request
{

View File

@ -20,37 +20,117 @@ package org.eclipse.jetty.client.api;
import java.nio.ByteBuffer;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpVersion;
/**
* <p>{@link Response} represents a HTTP response and offers methods to retrieve status code, HTTP version
* and headers.</p>
* <p>{@link Response} objects are passed as parameters to {@link Response.Listener} callbacks, or as
* future result of {@link Request#send()}.</p>
* <p>{@link Response} objects do not contain getters for the response content, because it may be too large
* to fit into memory.
* The response content should be retrieved via {@link Response.Listener#onContent(Response, ByteBuffer) content
* events}, or via utility classes such as {@link BufferingResponseListener}.</p>
*/
public interface Response
{
/**
* @return the response listener passed to {@link Request#send(Listener)}
*/
Listener listener();
/**
* @return the HTTP version of this response, such as "HTTP/1.1"
*/
HttpVersion version();
/**
* @return the HTTP status code of this response, such as 200 or 404
*/
int status();
/**
* @return the HTTP reason associated to the {@link #status()}
*/
String reason();
/**
* @return the headers of this response
*/
HttpFields headers();
/**
* Attempts to abort the send of this request.
*/
void abort();
/**
* Listener for response events
*/
public interface Listener
{
/**
* Callback method invoked when the response line containing HTTP version,
* HTTP status code and reason has been received and parsed.
* <p />
* This method is the best approximation to detect when the first bytes of the response arrived to the client.
*
* @param response the response containing the response line data
*/
public void onBegin(Response response);
/**
* Callback method invoked when the response headers have been received and parsed.
*
* @param response the response containing the response line data and the headers
*/
public void onHeaders(Response response);
/**
* Callback method invoked when the response content has been received.
* This method may be invoked multiple times, and the {@code content} buffer must be consumed
* before returning from this method.
*
* @param response the response containing the response line data and the headers
* @param content the content bytes received
*/
public void onContent(Response response, ByteBuffer content);
/**
* Callback method invoked when the whole response has been successfully received.
*
* @param response the response containing the response line data and the headers
*/
public void onSuccess(Response response);
/**
* Callback method invoked when the response has failed in the process of being received
*
* @param response the response containing data up to the point the failure happened
* @param failure the failure happened
*/
public void onFailure(Response response, Throwable failure);
/**
* Callback method invoked when the request <em><b>and</b></em> the response have been processed,
* either successfully or not.
* <p/>
* The {@code result} parameter contains the request, the response, and eventual failures.
* <p/>
* Requests may complete <em>after</em> response, for example in case of big uploads that are
* discarded or read asynchronously by the server.
* This method is always invoked <em>after</em> {@link #onSuccess(Response)} or
* {@link #onFailure(Response, Throwable)}, and only when request indicates that it is completed.
*
* @param result the result of the request / response exchange
*/
public void onComplete(Result result);
/**
* An empty implementation of {@link Listener}
*/
public static class Empty implements Listener
{
@Override

View File

@ -18,6 +18,10 @@
package org.eclipse.jetty.client.api;
/**
* The result of a request / response exchange, containing the {@link Request}, the {@link Response}
* and eventual failures of either.
*/
public class Result
{
private final Request request;
@ -48,21 +52,49 @@ public class Result
this.responseFailure = responseFailure;
}
/**
* @return the request object
*/
public Request getRequest()
{
return request;
}
/**
* @return the request failure, if any
*/
public Throwable getRequestFailure()
{
return requestFailure;
}
/**
* @return the response object
*/
public Response getResponse()
{
return response;
}
/**
* @return the response failure, if any
*/
public Throwable getResponseFailure()
{
return responseFailure;
}
/**
* @return whether either the response or the request failed
*/
public boolean isFailed()
{
return getFailure() != null;
}
/**
* @return the response failure, if any, otherwise the request failure, if any
*/
public Throwable getFailure()
{
return responseFailure != null ? responseFailure : requestFailure;

View File

@ -91,7 +91,8 @@ public class BasicAuthentication implements Authentication
@Override
public void apply(Request request)
{
request.header(HttpHeader.AUTHORIZATION.asString(), value);
if (request.uri().startsWith(uri))
request.header(HttpHeader.AUTHORIZATION.asString(), value);
}
@Override

View File

@ -196,6 +196,9 @@ public class DigestAuthentication implements Authentication
@Override
public void apply(Request request)
{
if (!request.uri().startsWith(uri))
return;
MessageDigest digester = getMessageDigest(algorithm);
if (digester == null)
return;

View File

@ -84,7 +84,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
Assert.assertEquals(0, client.getDestinations().size());
Assert.assertEquals(0, destination.getIdleConnections().size());
Assert.assertEquals(0, destination.getActiveConnections().size());
Assert.assertEquals(0, client.getCookieStore().getCookies(destination, path).size());
Assert.assertEquals(0, client.getCookieStore().findCookies(destination, path).size());
Assert.assertFalse(connection.getEndPoint().isOpen());
}

View File

@ -37,7 +37,7 @@ public class HttpCookieStoreTest
Destination destination = new HttpDestination(client, "http", "localhost", 80);
Assert.assertTrue(cookies.addCookie(destination, new HttpCookie("a", "1")));
List<HttpCookie> result = cookies.getCookies(destination, "/");
List<HttpCookie> result = cookies.findCookies(destination, "/");
Assert.assertNotNull(result);
Assert.assertEquals(1, result.size());
HttpCookie cookie = result.get(0);
@ -52,7 +52,7 @@ public class HttpCookieStoreTest
Destination destination = new HttpDestination(client, "http", "localhost", 80);
Assert.assertTrue(cookies.addCookie(destination, new HttpCookie("a", "1", "child.localhost", "/")));
List<HttpCookie> result = cookies.getCookies(destination, "/");
List<HttpCookie> result = cookies.findCookies(destination, "/");
Assert.assertNotNull(result);
Assert.assertEquals(1, result.size());
HttpCookie cookie = result.get(0);
@ -75,7 +75,7 @@ public class HttpCookieStoreTest
Destination destination = new HttpDestination(client, "http", "localhost", 80);
Assert.assertTrue(cookies.addCookie(destination, new HttpCookie("a", "1", null, "/path")));
List<HttpCookie> result = cookies.getCookies(destination, "/");
List<HttpCookie> result = cookies.findCookies(destination, "/");
Assert.assertNotNull(result);
Assert.assertEquals(0, result.size());
}
@ -87,7 +87,7 @@ public class HttpCookieStoreTest
Destination destination = new HttpDestination(client, "http", "localhost", 80);
Assert.assertTrue(cookies.addCookie(destination, new HttpCookie("a", "1", null, "/")));
List<HttpCookie> result = cookies.getCookies(destination, "/path");
List<HttpCookie> result = cookies.findCookies(destination, "/path");
Assert.assertNotNull(result);
Assert.assertEquals(1, result.size());
HttpCookie cookie = result.get(0);
@ -108,7 +108,7 @@ public class HttpCookieStoreTest
Destination grandChildDestination = new HttpDestination(client, "http", "grand.child.localhost.org", 80);
Assert.assertTrue(cookies.addCookie(grandChildDestination, new HttpCookie("b", "2", null, "/")));
List<HttpCookie> result = cookies.getCookies(grandChildDestination, "/path");
List<HttpCookie> result = cookies.findCookies(grandChildDestination, "/path");
Assert.assertNotNull(result);
Assert.assertEquals(2, result.size());
}
@ -120,7 +120,7 @@ public class HttpCookieStoreTest
Destination destination = new HttpDestination(client, "http", "localhost.org", 80);
Assert.assertTrue(cookies.addCookie(destination, new HttpCookie("a", "1", null, "/", 0, false, false)));
List<HttpCookie> result = cookies.getCookies(destination, "/");
List<HttpCookie> result = cookies.findCookies(destination, "/");
Assert.assertNotNull(result);
Assert.assertEquals(0, result.size());
}
@ -132,7 +132,7 @@ public class HttpCookieStoreTest
Destination destination = new HttpDestination(client, "http", "localhost.org", 80);
Assert.assertTrue(cookies.addCookie(destination, new HttpCookie("a", "1", null, "/", -1, false, true)));
List<HttpCookie> result = cookies.getCookies(destination, "/");
List<HttpCookie> result = cookies.findCookies(destination, "/");
Assert.assertNotNull(result);
Assert.assertEquals(0, result.size());
}
@ -145,6 +145,6 @@ public class HttpCookieStoreTest
Assert.assertTrue(cookies.addCookie(destination, new HttpCookie("a", "1", null, "/", -1, false, true)));
cookies.clear();
Assert.assertEquals(0, cookies.getCookies(destination, "/").size());
Assert.assertEquals(0, cookies.findCookies(destination, "/").size());
}
}

View File

@ -59,7 +59,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest
Assert.assertEquals(200, response.status());
Destination destination = client.getDestination(scheme, host, port);
List<HttpCookie> cookies = client.getCookieStore().getCookies(destination, path);
List<HttpCookie> cookies = client.getCookieStore().findCookies(destination, path);
Assert.assertNotNull(cookies);
Assert.assertEquals(1, cookies.size());
HttpCookie cookie = cookies.get(0);