diff --git a/example-jetty-embedded/pom.xml b/example-jetty-embedded/pom.xml
index f2408d3a287..03da5633e3e 100644
--- a/example-jetty-embedded/pom.xml
+++ b/example-jetty-embedded/pom.xml
@@ -38,6 +38,11 @@
{@link HttpClient} transparently pools connections to servers, but allows direct control of connections * for cases where this is needed.
+ *{@link HttpClient} also acts as a central configuration point for cookies, via {@link #getCookieStore()}.
*Typical usage:
** // 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() @@ -438,7 +444,7 @@ public class HttpClient extends AggregateLifeCycle } // TODO: find a better method name - public Response.Listener lookup(Request request, Response response) + protected Response.Listener lookup(Request request, Response response) { for (ProtocolHandler handler : handlers) { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java index 4f1e3286b7e..c7a03c02dbe 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java @@ -146,7 +146,7 @@ public class HttpConnection extends AbstractConnection implements Connection } // Cookies - Listcookies = client.getCookieStore().getCookies(getDestination(), request.path()); + List cookies = client.getCookieStore().findCookies(getDestination(), request.path()); StringBuilder cookieString = null; for (int i = 0; i < cookies.size(); ++i) { @@ -161,9 +161,9 @@ public class HttpConnection extends AbstractConnection implements Connection request.header(HttpHeader.COOKIE.asString(), cookieString.toString()); // Authorization - Authentication authentication = client.getAuthenticationStore().findAuthenticationResult(request.uri()); - if (authentication != null) - authentication.authenticate(request); + Authentication.Result authnResult = client.getAuthenticationStore().findAuthenticationResult(request.uri()); + if (authnResult != null) + authnResult.apply(request); // TODO: decoder headers @@ -250,6 +250,11 @@ public class HttpConnection extends AbstractConnection implements Connection } } + public void abort(HttpResponse response) + { + receiver.fail(new HttpResponseException("Response aborted", response)); + } + @Override public void close() { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpCookieStore.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpCookieStore.java index a198514c9c8..043447b3578 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpCookieStore.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpCookieStore.java @@ -35,7 +35,7 @@ public class HttpCookieStore implements CookieStore private final ConcurrentMap > allCookies = new ConcurrentHashMap<>(); @Override - public List getCookies(Destination destination, String path) + public List findCookies(Destination destination, String path) { List result = new ArrayList<>(); diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java index 894c48da364..7d965ec580c 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java @@ -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())) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java index 26024e83c96..4e6f669bc10 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java @@ -42,7 +42,7 @@ public class HttpExchange this.connection = connection; this.request = request; this.listener = listener; - this.response = new HttpResponse(listener); + this.response = new HttpResponse(this, listener); } public HttpConversation conversation() @@ -117,6 +117,12 @@ public class HttpExchange return false; } + public void abort() + { + LOG.debug("Aborting {}", response); + connection.abort(response); + } + @Override public String toString() { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java index 3ecda154f2b..f3695916768 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java @@ -27,38 +27,37 @@ import java.nio.file.Path; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicLong; -import org.eclipse.jetty.client.api.ContentDecoder; import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.client.util.BufferingResponseListener; +import org.eclipse.jetty.client.util.BlockingResponseListener; import org.eclipse.jetty.client.util.PathContentProvider; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.util.Fields; -import org.eclipse.jetty.util.FutureCallback; public class HttpRequest implements Request { private static final AtomicLong ids = new AtomicLong(); + private final HttpFields headers = new HttpFields(); + private final Fields params = new Fields(); private final HttpClient client; private final long id; - private String scheme; private final String host; private final int port; + private String scheme; private String path; private HttpMethod method; private HttpVersion version; private long idleTimeout; private Listener listener; private ContentProvider content; - private final HttpFields headers = new HttpFields(); - private final Fields params = new Fields(); + private boolean followRedirects; + private volatile boolean aborted; public HttpRequest(HttpClient client, URI uri) { @@ -82,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) @@ -199,9 +199,9 @@ public class HttpRequest implements Request } @Override - public Request agent(String userAgent) + public Request agent(String agent) { - headers.put(HttpHeader.USER_AGENT, userAgent); + headers.put(HttpHeader.USER_AGENT, agent); return this; } @@ -261,15 +261,22 @@ public class HttpRequest implements Request return content(new PathContentProvider(file)); } +// @Override +// public Request decoder(ContentDecoder decoder) +// { +// return this; +// } + @Override - public Request decoder(ContentDecoder decoder) + public boolean followRedirects() { - return this; + return followRedirects; } @Override public Request followRedirects(boolean follow) { + this.followRedirects = follow; return this; } @@ -289,22 +296,9 @@ public class HttpRequest implements Request @Override public Future send() { - final FutureCallback callback = new FutureCallback<>(); - BufferingResponseListener listener = new BufferingResponseListener() - { - @Override - public void onComplete(Result result) - { - super.onComplete(result); - HttpContentResponse contentResponse = new HttpContentResponse(result.getResponse(), content()); - if (!result.isFailed()) - callback.completed(contentResponse); - else - callback.failed(contentResponse, result.getFailure()); - } - }; + BlockingResponseListener listener = new BlockingResponseListener(); send(listener); - return callback; + return listener; } @Override @@ -313,6 +307,18 @@ public class HttpRequest implements Request client.send(this, listener); } + @Override + public void abort() + { + aborted = true; + } + + @Override + public boolean aborted() + { + return aborted; + } + @Override public String toString() { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ChannelHttpServer.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequestException.java similarity index 65% rename from jetty-server/src/test/java/org/eclipse/jetty/server/ChannelHttpServer.java rename to jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequestException.java index 51842cbbb86..0541eee14ca 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ChannelHttpServer.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequestException.java @@ -16,19 +16,22 @@ // ======================================================================== // -package org.eclipse.jetty.server; +package org.eclipse.jetty.client; -public class ChannelHttpServer +import org.eclipse.jetty.client.api.Request; + +public class HttpRequestException extends Throwable { - public static void main(String[] s) throws Exception + private final Request request; + + public HttpRequestException(String message, Request request) { - Server server = new Server(); - SelectChannelConnector connector = new SelectChannelConnector(server); - connector.setPort(8080); - server.addConnector(connector); - server.setHandler(new DumpHandler()); - server.start(); - server.dumpStdErr(); - server.join(); + super(message); + this.request = request; + } + + public Request getRequest() + { + return request; } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java index 5f9c2468574..42be83cc9c3 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpResponse.java @@ -25,13 +25,15 @@ import org.eclipse.jetty.http.HttpVersion; public class HttpResponse implements Response { private final HttpFields headers = new HttpFields(); + private final HttpExchange exchange; private final Listener listener; private HttpVersion version; private int status; private String reason; - public HttpResponse(Response.Listener listener) + public HttpResponse(HttpExchange exchange, Listener listener) { + this.exchange = exchange; this.listener = listener; } @@ -84,7 +86,7 @@ public class HttpResponse implements Response @Override public void abort() { -// request.abort(); + exchange.abort(); } @Override diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java index a9308d31353..e75854fc1bf 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java @@ -60,12 +60,20 @@ public class HttpSender public void send(HttpExchange exchange) { - LOG.debug("Sending {}", exchange.request()); - requestNotifier.notifyBegin(exchange.request()); - ContentProvider content = exchange.request().content(); - this.contentLength = content == null ? -1 : content.length(); - this.contentChunks = content == null ? Collections. emptyIterator() : content.iterator(); - send(); + Request request = exchange.request(); + if (request.aborted()) + { + fail(new HttpRequestException("Request aborted", request)); + } + else + { + LOG.debug("Sending {}", request); + requestNotifier.notifyBegin(request); + ContentProvider content = request.content(); + this.contentLength = content == null ? -1 : content.length(); + this.contentChunks = content == null ? Collections. emptyIterator() : content.iterator(); + send(); + } } private void send() @@ -122,38 +130,45 @@ public class HttpSender } case FLUSH: { - StatefulExecutorCallback callback = new StatefulExecutorCallback(client.getExecutor()) + if (request.aborted()) { - @Override - protected void pendingCompleted() + fail(new HttpRequestException("Request aborted", request)); + } + else + { + StatefulExecutorCallback callback = new StatefulExecutorCallback(client.getExecutor()) + { + @Override + protected void pendingCompleted() + { + if (!committed) + committed(request); + send(); + } + + @Override + protected void failed(Throwable x) + { + fail(x); + } + }; + if (header == null) + header = BufferUtil.EMPTY_BUFFER; + if (chunk == null) + chunk = BufferUtil.EMPTY_BUFFER; + endPoint.write(null, callback, header, chunk, content); + if (callback.pending()) + return; + + if (callback.completed()) { if (!committed) committed(request); - send(); + + releaseBuffers(); + content = contentChunks.hasNext() ? contentChunks.next() : BufferUtil.EMPTY_BUFFER; + lastContent = !contentChunks.hasNext(); } - - @Override - protected void failed(Throwable x) - { - fail(x); - } - }; - if (header == null) - header = BufferUtil.EMPTY_BUFFER; - if (chunk == null) - chunk = BufferUtil.EMPTY_BUFFER; - endPoint.write(null, callback, header, chunk, content); - if (callback.pending()) - return; - - if (callback.completed()) - { - if (!committed) - committed(request); - - releaseBuffers(); - content = contentChunks.hasNext() ? contentChunks.next() : BufferUtil.EMPTY_BUFFER; - lastContent = !contentChunks.hasNext(); } break; } @@ -234,7 +249,7 @@ public class HttpSender // Notify after HttpExchange exchange = connection.getExchange(); Request request = exchange.request(); - LOG.debug("Failed {}", request); + LOG.debug("Failed {} {}", request, failure); boolean exchangeCompleted = exchange.requestComplete(false); if (!exchangeCompleted && !committed) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java index 180e66098a7..588b1fffb6c 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/RedirectProtocolHandler.java @@ -24,7 +24,7 @@ import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpMethod; -public class RedirectProtocolHandler extends Response.Listener.Adapter implements ProtocolHandler +public class RedirectProtocolHandler extends Response.Listener.Empty implements ProtocolHandler { private static final String ATTRIBUTE = RedirectProtocolHandler.class.getName() + ".redirect"; @@ -45,7 +45,7 @@ public class RedirectProtocolHandler extends Response.Listener.Adapter implement case 302: case 303: case 307: - return true; + return request.followRedirects(); } return false; } @@ -128,7 +128,7 @@ public class RedirectProtocolHandler extends Response.Listener.Adapter implement // Copy content redirect.content(request.content()); - redirect.send(new Adapter()); + redirect.send(new Response.Listener.Empty()); } else { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Authentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Authentication.java index 5aa17ee98d4..05ad760a525 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Authentication.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Authentication.java @@ -18,31 +18,67 @@ package org.eclipse.jetty.client.api; +import org.eclipse.jetty.util.Attributes; + +/** + * {@link Authentication} represents a mechanism to authenticate requests for protected resources. + * + * {@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. + * + * 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); - void authenticate(Request request); + /** + * 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)}. + * + * 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); - public static class Result + /** + * {@link Result} holds the information needed to authenticate a {@link Request} via {@link #apply(Request)}. + */ + public static interface Result { - private final String uri; - private final Authentication authentication; + /** + * @return the URI of the request that has been used to generate this {@link Result} + */ + String getURI(); - public Result(String uri, Authentication authentication) - { - this.uri = uri; - this.authentication = authentication; - } - - public String getURI() - { - return uri; - } - - public Authentication getAuthentication() - { - return authentication; - } + /** + * 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); } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/AuthenticationStore.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/AuthenticationStore.java index afdcea697f2..2835ed73fcc 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/AuthenticationStore.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/AuthenticationStore.java @@ -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); - public Authentication findAuthenticationResult(String uri); + /** + * 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); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java index 1d7be57c75d..23d963ca5c8 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Connection.java @@ -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)}. + * + * {@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. + * + * {@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 diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentDecoder.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentDecoder.java index 656f490e0aa..b8dc313e7a0 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentDecoder.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentDecoder.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.client.api; -public interface ContentDecoder +// TODO +interface ContentDecoder { } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java index 0be40d0c1fc..68d626e0056 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java @@ -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. + * + * Implementations should return a new "view" over the same content every time {@link #iterator()} is invoked. + * + * Applications should rely on utility classes such as {@link ByteBufferContentProvider} + * or {@link PathContentProvider}. + */ public interface ContentProvider extends Iterable { + /** + * @return the content length, if known, or -1 if the content length is unknown + */ long length(); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentResponse.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentResponse.java index 0be9edfe8e4..f960db2dfe6 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentResponse.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentResponse.java @@ -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(); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/CookieStore.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/CookieStore.java index 6aa77072c78..7ac798c58b6 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/CookieStore.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/CookieStore.java @@ -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 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 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(); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java index 2ec5a765e2d..3ed6524699c 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Destination.java @@ -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()}. + * + * {@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()}. + * + * {@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 newConnection(); - - void send(Request request, Response.Listener listener); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java index 6b0f5a300dc..9fbb1373883 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java @@ -22,88 +22,274 @@ import java.io.IOException; import java.nio.file.Path; import java.util.concurrent.Future; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.util.StreamingResponseListener; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.util.Fields; +/** + * {@link Request} represents a HTTP request, and offers a fluent interface to customize + * various attributes such as the path, the headers, the content, etc.
+ *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.
+ * + * @see Response + */ public interface Request { + /** + * @return the conversation id + */ long id(); + /** + * @return the scheme of this request, such as "http" or "https" + */ String scheme(); + /** + * @param scheme the scheme of this request, such as "http" or "https" + * @return this request object + */ Request scheme(String scheme); + /** + * @return the host of this request, such as "127.0.0.1" or "google.com" + */ String host(); + /** + * @return the port of this request such as 80 or 443 + */ int port(); + /** + * @return the method of this request, such as GET or POST + */ HttpMethod method(); + /** + * @param method the method of this request, such as GET or POST + * @return this request object + */ Request method(HttpMethod method); + /** + * @return the path of this request, such as "/" + */ String path(); + /** + * @param path the path of this request, such as "/" + * @return this request object + */ Request path(String path); + /** + * @return the full URI of this request such as "http://host:port/path" + */ String uri(); + /** + * @return the HTTP version of this request, such as "HTTP/1.1" + */ HttpVersion version(); + /** + * @param version the HTTP version of this request, such as "HTTP/1.1" + * @return this request object + */ Request version(HttpVersion version); + /** + * @return the query parameters of this request + */ Fields params(); + /** + * @param name the name of the query parameter + * @param value the value of the query parameter + * @return this request object + */ Request param(String name, String value); + /** + * @return the headers of this request + */ HttpFields headers(); + /** + * @param name the name of the header + * @param value the value of the header + * @return this request object + */ Request header(String name, String value); + /** + * @return the content provider of this request + */ ContentProvider content(); - Request content(ContentProvider buffer); + /** + * @param content the content provider of this request + * @return this request object + */ + Request content(ContentProvider content); + /** + * Shortcut method to specify a file as a content for this request, with the default content type of + * "application/octect-stream". + * + * @param file the file to upload + * @return this request object + * @throws IOException if the file does not exist or cannot be read + */ Request file(Path file) throws IOException; + /** + * Shortcut method to specify a file as a content for this request, with the given content type. + * + * @param file the file to upload + * @param contentType the content type of the file + * @return this request object + * @throws IOException if the file does not exist or cannot be read + */ Request file(Path file, String contentType) throws IOException; - Request decoder(ContentDecoder decoder); +// Request decoder(ContentDecoder decoder); + /** + * @return the user agent for this request + */ String agent(); - Request agent(String userAgent); + /** + * @param agent the user agent for this request + * @return this request object + */ + Request agent(String agent); + /** + * @return the idle timeout for this request + */ long idleTimeout(); + /** + * @param timeout the idle timeout for this request + * @return this request object + */ Request idleTimeout(long timeout); + /** + * @return whether this request follows redirects + */ + boolean followRedirects(); + + /** + * @param follow whether this request follows redirects + * @return this request object + */ Request followRedirects(boolean follow); + /** + * @return the listener for request events + */ Listener listener(); + /** + * @param listener the listener for request events + * @return this request object + */ Request listener(Listener listener); + /** + * Sends this request and returns a {@link Future} that can be used to wait for the + * request and the response to be completed (either with a success or a failure). + * + * This method should be used when a simple blocking semantic is needed, and when it is known + * that the response content can be buffered without exceeding memory constraints. + * For example, this method is not appropriate to download big files from a server; consider using + * {@link #send(Response.Listener)} instead, passing your own {@link Response.Listener} or a utility + * listener such as {@link StreamingResponseListener}. + * + * The future will return when {@link Response.Listener#onComplete(Result)} is invoked. + * + * @return a {@link Future} to wait on for request and response completion + * @see Response.Listener#onComplete(Result) + */ Futuresend(); + /** + * Sends this request and asynchronously notifies the given listener for response events. + * + * This method should be used when the application needs to be notified of the various response events + * as they happen, or when the application needs to efficiently manage the response content. + * + * @param listener the listener that receives response events + */ void send(Response.Listener listener); + /** + * Attempts to abort the send of this request. + * + * @see #aborted() + */ + void abort(); + + /** + * @return whether {@link #abort()} was called + */ + boolean aborted(); + + /** + * Listener for request events + */ public interface Listener { + /** + * Callback method invoked when the request is queued, waiting to be sent + * + * @param request the request being queued + */ public void onQueued(Request request); + /** + * Callback method invoked when the request begins being processed in order to be sent. + * This is the last opportunity to modify the request. + * + * @param request the request that begins being processed + */ public void onBegin(Request request); + /** + * Callback method invoked when the request headers (and perhaps small content) have been sent. + * The request is now committed, and in transit to the server, and further modifications to the + * request may have no effect. + * @param request the request that has been committed + */ public void onHeaders(Request request); - public void onFlush(Request request, int bytes); - + /** + * Callback method invoked when the request has been successfully sent. + * + * @param request the request sent + */ public void onSuccess(Request request); + /** + * Callback method invoked when the request has failed to be sent + * @param request the request that failed + * @param failure the failure + */ public void onFailure(Request request, Throwable failure); - public static class Adapter implements Listener + /** + * An empty implementation of {@link Listener} + */ + public static class Empty implements Listener { @Override public void onQueued(Request request) @@ -120,11 +306,6 @@ public interface Request { } - @Override - public void onFlush(Request request, int bytes) - { - } - @Override public void onSuccess(Request request) { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java index f4caa38f08f..79e09752b3d 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java @@ -20,38 +20,118 @@ 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; +/** + * {@link Response} represents a HTTP response and offers methods to retrieve status code, HTTP version + * and headers.
+ *{@link Response} objects are passed as parameters to {@link Response.Listener} callbacks, or as + * future result of {@link Request#send()}.
+ *{@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}.
+ */ 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. + * + * 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 and the response have been processed, + * either successfully or not. + * + * The {@code result} parameter contains the request, the response, and eventual failures. + * + * Requests may complete after response, for example in case of big uploads that are + * discarded or read asynchronously by the server. + * This method is always invoked after {@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); - public static class Adapter implements Listener + /** + * An empty implementation of {@link Listener} + */ + public static class Empty implements Listener { @Override public void onBegin(Response response) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Result.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Result.java index ff59d9e2cf0..8ad009354a3 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Result.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Result.java @@ -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; diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/package-info.java b/jetty-client/src/main/java/org/eclipse/jetty/client/package-info.java new file mode 100644 index 00000000000..3e4c1890950 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/package-info.java @@ -0,0 +1,50 @@ +// +// ======================================================================== +// 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. +// ======================================================================== +// + +/** + * This package provides APIs, utility classes and implementation class of an asynchronous HTTP client + * + * The core class is {@link HttpClient}, which acts as a central configuration object (for example + * for {@link HttpClient#setIdleTimeout(long) idle timeouts}, {@link HttpClient#setMaxConnectionsPerAddress(int) + * max connections per domain}, etc.) and as a factory for {@link Request} objects. + * + * The HTTP protocol is based on the request/response paradigm, a unit that in this implementation is called + * exchange and is represented by {@link HttpExchange}. + * An initial request may trigger a sequence of exchanges with one or more servers, called a conversation + * and represented by {@link HttpConversation}. A typical example of a conversation is a redirect, where + * upon a request for a resource URI, the server replies with a redirect (for example with the 303 status code) + * to another URI. This conversation is made of a first exchange made of the original request and its 303 response, + * and of a second exchange made of the request for the new URI and its 200 response. + * + * {@link HttpClient} holds a number of {@link HttpDestination destinations}, which in turn hold a number of + * pooled {@link HttpConnection connections}. + * + * When a request is sent, its exchange is associated to a connection, either taken from an idle queue or created + * anew, and when both the request and response are completed, the exchange is disassociated from the connection. + * Conversation may span multiple connections on different destinations, and therefore are maintained at the + * {@link HttpClient} level. + * + * Applications may decide to send the request and wait for the response in a blocking way, using + * {@link Request#send()}. + * Alternatively, application may ask to be notified of response events asynchronously, using + * {@link Request#send(Response.Listener)}. + */ +package org.eclipse.jetty.client; + +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.api.Response; diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java index 8d77d71f390..640b98f1c04 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BasicAuthentication.java @@ -22,8 +22,10 @@ import java.io.UnsupportedEncodingException; import java.nio.charset.UnsupportedCharsetException; import org.eclipse.jetty.client.api.Authentication; +import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.StringUtil; @@ -55,17 +57,48 @@ public class BasicAuthentication implements Authentication } @Override - public void authenticate(Request request) + public Result authenticate(Request request, ContentResponse response, String wwwAuthenticate, Attributes context) { String encoding = StringUtil.__ISO_8859_1; try { String value = "Basic " + B64Code.encode(user + ":" + password, encoding); - request.header(HttpHeader.AUTHORIZATION.asString(), value); + return new BasicResult(request.uri(), value); } catch (UnsupportedEncodingException x) { throw new UnsupportedCharsetException(encoding); } } + + private static class BasicResult implements Result + { + private final String uri; + private final String value; + + public BasicResult(String uri, String value) + { + this.uri = uri; + this.value = value; + } + + @Override + public String getURI() + { + return uri; + } + + @Override + public void apply(Request request) + { + if (request.uri().startsWith(uri)) + request.header(HttpHeader.AUTHORIZATION.asString(), value); + } + + @Override + public String toString() + { + return String.format("Basic authentication result for %s", uri); + } + } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BlockingResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BlockingResponseListener.java new file mode 100644 index 00000000000..39d619782af --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BlockingResponseListener.java @@ -0,0 +1,117 @@ +// +// ======================================================================== +// 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.util; + +import java.nio.ByteBuffer; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jetty.client.HttpContentResponse; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.api.Result; + +public class BlockingResponseListener extends BufferingResponseListener implements Future+{ + private final CountDownLatch latch = new CountDownLatch(1); + private ContentResponse response; + private Throwable failure; + private volatile boolean cancelled; + + @Override + public void onBegin(Response response) + { + super.onBegin(response); + if (cancelled) + response.abort(); + } + + @Override + public void onHeaders(Response response) + { + super.onHeaders(response); + if (cancelled) + response.abort(); + } + + @Override + public void onContent(Response response, ByteBuffer content) + { + super.onContent(response, content); + if (cancelled) + response.abort(); + } + + @Override + public void onComplete(Result result) + { + super.onComplete(result); + response = new HttpContentResponse(result.getResponse(), getContent()); + failure = result.getFailure(); + latch.countDown(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) + { + cancelled = true; + return latch.getCount() == 0; + } + + @Override + public boolean isCancelled() + { + return cancelled; + } + + @Override + public boolean isDone() + { + return latch.getCount() == 0 || isCancelled(); + } + + @Override + public ContentResponse get() throws InterruptedException, ExecutionException + { + latch.await(); + return result(); + } + + @Override + public ContentResponse get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException + { + boolean expired = !latch.await(timeout, unit); + if (expired) + throw new TimeoutException(); + return result(); + } + + private ContentResponse result() throws ExecutionException + { + if (isCancelled()) + throw (CancellationException)new CancellationException().initCause(failure); + if (failure != null) + throw new ExecutionException(failure); + return response; + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java index 0db2571ff23..04e426a2cb1 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java @@ -18,38 +18,32 @@ package org.eclipse.jetty.client.util; +import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.nio.charset.UnsupportedCharsetException; import org.eclipse.jetty.client.api.Response; -public class BufferingResponseListener extends Response.Listener.Adapter +public class BufferingResponseListener extends Response.Listener.Empty { - private final CountDownLatch latch = new CountDownLatch(1); - private final int maxCapacity; - private Response response; - private Throwable failure; - private byte[] buffer = new byte[0]; + private final int maxLength; + private volatile byte[] buffer = new byte[0]; public BufferingResponseListener() { - this(16 * 1024 * 1024); + this(2 * 1024 * 1024); } - public BufferingResponseListener(int maxCapacity) + public BufferingResponseListener(int maxLength) { - this.maxCapacity = maxCapacity; + this.maxLength = maxLength; } @Override public void onContent(Response response, ByteBuffer content) { long newLength = buffer.length + content.remaining(); - if (newLength > maxCapacity) + if (newLength > maxLength) throw new IllegalStateException("Buffering capacity exceeded"); byte[] newBuffer = new byte[(int)newLength]; @@ -58,38 +52,20 @@ public class BufferingResponseListener extends Response.Listener.Adapter buffer = newBuffer; } - @Override - public void onSuccess(Response response) - { - this.response = response; - latch.countDown(); - } - - @Override - public void onFailure(Response response, Throwable failure) - { - this.response = response; - this.failure = failure; - latch.countDown(); - } - - public Response await(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException - { - boolean expired = !latch.await(timeout, unit); - if (failure != null) - throw new ExecutionException(failure); - if (expired) - throw new TimeoutException(); - return response; - } - - public byte[] content() + public byte[] getContent() { return buffer; } - public String contentAsString(String encoding) + public String getContent(String encoding) { - return new String(content(), Charset.forName(encoding)); + try + { + return new String(getContent(), encoding); + } + catch (UnsupportedEncodingException x) + { + throw new UnsupportedCharsetException(encoding); + } } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java new file mode 100644 index 00000000000..b21d46213d8 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DigestAuthentication.java @@ -0,0 +1,271 @@ +// +// ======================================================================== +// 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.util; + +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jetty.client.api.Authentication; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.TypeUtil; + +public class DigestAuthentication implements Authentication +{ + private static final Pattern PARAM_PATTERN = Pattern.compile("([^=]+)=(.*)"); + + private final String uri; + private final String realm; + private final String user; + private final String password; + + public DigestAuthentication(String uri, String realm, String user, String password) + { + this.uri = uri; + this.realm = realm; + this.user = user; + this.password = password; + } + + @Override + public boolean matches(String type, String uri, String realm) + { + if (!"digest".equalsIgnoreCase(type)) + return false; + + if (!uri.startsWith(this.uri)) + return false; + + return this.realm.equals(realm); + } + + @Override + public Result authenticate(Request request, ContentResponse response, String wwwAuthenticate, Attributes context) + { + // Avoid case sensitivity problems on the 'D' character + String type = "igest"; + wwwAuthenticate = wwwAuthenticate.substring(wwwAuthenticate.indexOf(type) + type.length()); + + Map params = parseParams(wwwAuthenticate); + String nonce = params.get("nonce"); + if (nonce == null || nonce.length() == 0) + return null; + String opaque = params.get("opaque"); + String algorithm = params.get("algorithm"); + if (algorithm == null) + algorithm = "MD5"; + MessageDigest digester = getMessageDigest(algorithm); + if (digester == null) + return null; + String serverQOP = params.get("qop"); + String clientQOP = null; + if (serverQOP != null) + { + List serverQOPValues = Arrays.asList(serverQOP.split(",")); + if (serverQOPValues.contains("auth")) + clientQOP = "auth"; + else if (serverQOPValues.contains("auth-int")) + clientQOP = "auth-int"; + } + + return new DigestResult(request.uri(), response.content(), realm, user, password, algorithm, nonce, clientQOP, opaque); + } + + private Map parseParams(String wwwAuthenticate) + { + Map result = new HashMap<>(); + List parts = splitParams(wwwAuthenticate); + for (String part : parts) + { + Matcher matcher = PARAM_PATTERN.matcher(part); + if (matcher.matches()) + { + String name = matcher.group(1).trim().toLowerCase(); + String value = matcher.group(2).trim(); + if (value.startsWith("\"") && value.endsWith("\"")) + value = value.substring(1, value.length() - 1); + result.put(name, value); + } + } + return result; + } + + private List splitParams(String paramString) + { + List result = new ArrayList<>(); + int start = 0; + for (int i = 0; i < paramString.length(); ++i) + { + int quotes = 0; + char ch = paramString.charAt(i); + switch (ch) + { + case '\\': + ++i; + break; + case '"': + ++quotes; + break; + case ',': + if (quotes % 2 == 0) + { + result.add(paramString.substring(start, i).trim()); + start = i + 1; + } + break; + default: + break; + } + } + result.add(paramString.substring(start, paramString.length()).trim()); + return result; + } + + private MessageDigest getMessageDigest(String algorithm) + { + try + { + return MessageDigest.getInstance(algorithm); + } + catch (NoSuchAlgorithmException x) + { + return null; + } + } + + private class DigestResult implements Result + { + private final AtomicInteger nonceCount = new AtomicInteger(); + private final String uri; + private final byte[] content; + private final String realm; + private final String user; + private final String password; + private final String algorithm; + private final String nonce; + 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) + { + this.uri = uri; + this.content = content; + this.realm = realm; + this.user = user; + this.password = password; + this.algorithm = algorithm; + this.nonce = nonce; + this.qop = qop; + this.opaque = opaque; + } + + @Override + public String getURI() + { + return uri; + } + + @Override + public void apply(Request request) + { + if (!request.uri().startsWith(uri)) + return; + + MessageDigest digester = getMessageDigest(algorithm); + if (digester == null) + return; + + Charset charset = Charset.forName("ISO-8859-1"); + String A1 = user + ":" + realm + ":" + password; + String hashA1 = toHexString(digester.digest(A1.getBytes(charset))); + + String A2 = request.method().asString() + ":" + request.uri(); + if ("auth-int".equals(qop)) + A2 += ":" + toHexString(digester.digest(content)); + String hashA2 = toHexString(digester.digest(A2.getBytes(charset))); + + String nonceCount; + String clientNonce; + String A3; + if (qop != null) + { + nonceCount = nextNonceCount(); + clientNonce = newClientNonce(); + A3 = hashA1 + ":" + nonce + ":" + nonceCount + ":" + clientNonce + ":" + qop + ":" + hashA2; + } + else + { + nonceCount = null; + clientNonce = null; + A3 = hashA1 + ":" + nonce + ":" + hashA2; + } + String hashA3 = toHexString(digester.digest(A3.getBytes(charset))); + + StringBuilder value = new StringBuilder("Digest"); + value.append(" username=\"").append(user).append("\""); + value.append(", realm=\"").append(realm).append("\""); + value.append(", nonce=\"").append(nonce).append("\""); + if (opaque != null) + value.append(", opaque=\"").append(opaque).append("\""); + value.append(", algorithm=\"").append(algorithm).append("\""); + value.append(", uri=\"").append(request.uri()).append("\""); + if (qop != null) + { + value.append(", qop=\"").append(qop).append("\""); + value.append(", nc=\"").append(nonceCount).append("\""); + value.append(", cnonce=\"").append(clientNonce).append("\""); + } + value.append(", response=\"").append(hashA3).append("\""); + + request.header(HttpHeader.AUTHORIZATION.asString(), value.toString()); + } + + private String nextNonceCount() + { + String padding = "00000000"; + String next = Integer.toHexString(nonceCount.incrementAndGet()).toLowerCase(); + return padding.substring(0, padding.length() - next.length()) + next; + } + + private String newClientNonce() + { + Random random = new Random(); + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + return toHexString(bytes); + } + + private String toHexString(byte[] bytes) + { + return TypeUtil.toHexString(bytes).toLowerCase(); + } + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/StreamingResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/StreamingResponseListener.java index 1337c1d0e48..e0402b01284 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/StreamingResponseListener.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/StreamingResponseListener.java @@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.client.api.Response; -public class StreamingResponseListener extends Response.Listener.Adapter +public class StreamingResponseListener extends Response.Listener.Empty { public Response get(long timeout, TimeUnit seconds) { diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java index 4aeeabad4d6..48ce8e3efa7 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java @@ -49,7 +49,8 @@ public class AbstractHttpClientServerTest public void start(Handler handler) throws Exception { - server = new Server(); + if (server == null) + server = new Server(); connector = new SelectChannelConnector(server); server.addConnector(connector); server.setHandler(handler); @@ -62,11 +63,12 @@ public class AbstractHttpClientServerTest } @After - public void destroy() throws Exception + public void dispose() throws Exception { if (client != null) client.stop(); if (server != null) server.stop(); + server = null; } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/EmptyHandler.java b/jetty-client/src/test/java/org/eclipse/jetty/client/EmptyServerHandler.java similarity index 95% rename from jetty-client/src/test/java/org/eclipse/jetty/client/EmptyHandler.java rename to jetty-client/src/test/java/org/eclipse/jetty/client/EmptyServerHandler.java index bfedac1c5a7..d5af3dd9534 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/EmptyHandler.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/EmptyServerHandler.java @@ -26,7 +26,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; -public class EmptyHandler extends AbstractHandler +public class EmptyServerHandler extends AbstractHandler { @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java index 77ed14f2e35..7a06dca80c5 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java @@ -18,36 +18,90 @@ package org.eclipse.jetty.client; -import java.io.IOException; +import java.io.File; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.client.api.Authentication; import org.eclipse.jetty.client.api.AuthenticationStore; 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.server.handler.AbstractHandler; -import org.eclipse.jetty.util.B64Code; -import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.ConstraintMapping; +import org.eclipse.jetty.security.ConstraintSecurityHandler; +import org.eclipse.jetty.security.HashLoginService; +import org.eclipse.jetty.security.LoginService; +import org.eclipse.jetty.security.authentication.BasicAuthenticator; +import org.eclipse.jetty.security.authentication.DigestAuthenticator; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.security.Constraint; import org.junit.Assert; import org.junit.Test; public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest { - @Test - public void test_BasicAuthentication_WithChallenge() throws Exception - { - start(new BasicAuthenticationHandler()); + private String realm = "TestRealm"; + public void startBasic(Handler handler) throws Exception + { + start(new BasicAuthenticator(), handler); + } + + public void startDigest(Handler handler) throws Exception + { + start(new DigestAuthenticator(), handler); + } + + private void start(Authenticator authenticator, Handler handler) throws Exception + { + server = new Server(); + File realmFile = MavenTestingUtils.getTestResourceFile("realm.properties"); + LoginService loginService = new HashLoginService(realm, realmFile.getAbsolutePath()); + server.addBean(loginService); + + ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler(); + + Constraint constraint = new Constraint(); + constraint.setAuthenticate(true); + constraint.setRoles(new String[]{"*"}); + ConstraintMapping mapping = new ConstraintMapping(); + mapping.setPathSpec("/*"); + mapping.setConstraint(constraint); + + securityHandler.addConstraintMapping(mapping); + securityHandler.setAuthenticator(authenticator); + securityHandler.setLoginService(loginService); + securityHandler.setStrict(false); + + securityHandler.setHandler(handler); + start(securityHandler); + } + + @Test + public void test_BasicAuthentication() throws Exception + { + startBasic(new EmptyServerHandler()); + test_Authentication(new BasicAuthentication("http://localhost:" + connector.getLocalPort(), realm, "basic", "basic")); + } + + @Test + public void test_DigestAuthentication() throws Exception + { + startDigest(new EmptyServerHandler()); + test_Authentication(new DigestAuthentication("http://localhost:" + connector.getLocalPort(), realm, "digest", "digest")); + } + + private void test_Authentication(Authentication authentication) throws Exception + { AuthenticationStore authenticationStore = client.getAuthenticationStore(); - String realm = "test"; final AtomicInteger requests = new AtomicInteger(); - Request.Listener.Adapter requestListener = new Request.Listener.Adapter() + Request.Listener.Empty requestListener = new Request.Listener.Empty() { @Override public void onSuccess(Request request) @@ -58,10 +112,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest client.getRequestListeners().add(requestListener); // Request without Authentication causes a 401 - Request request = client.newRequest("localhost", connector.getLocalPort()) - .path("/test") - .param("type", "Basic") - .param("realm", realm); + Request request = client.newRequest("localhost", connector.getLocalPort()).path("/test"); ContentResponse response = request.send().get(5, TimeUnit.SECONDS); Assert.assertNotNull(response); Assert.assertEquals(401, response.status()); @@ -69,11 +120,9 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest client.getRequestListeners().remove(requestListener); requests.set(0); - String user = "jetty"; - String password = "rocks"; - authenticationStore.addAuthentication(new BasicAuthentication("http://localhost:" + connector.getLocalPort(), realm, user, password)); + authenticationStore.addAuthentication(authentication); - requestListener = new Request.Listener.Adapter() + requestListener = new Request.Listener.Empty() { @Override public void onSuccess(Request request) @@ -84,7 +133,6 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest client.getRequestListeners().add(requestListener); // Request with authentication causes a 401 (no previous successful authentication) + 200 - request.param("user", user).param("password", password); response = request.send().get(5, TimeUnit.SECONDS); Assert.assertNotNull(response); Assert.assertEquals(200, response.status()); @@ -92,7 +140,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest client.getRequestListeners().remove(requestListener); requests.set(0); - requestListener = new Request.Listener.Adapter() + requestListener = new Request.Listener.Empty() { @Override public void onSuccess(Request request) @@ -105,65 +153,11 @@ 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); - response = request.send().get(555, TimeUnit.SECONDS); + response = request.send().get(5, TimeUnit.SECONDS); Assert.assertNotNull(response); Assert.assertEquals(200, response.status()); Assert.assertEquals(1, requests.get()); client.getRequestListeners().remove(requestListener); requests.set(0); } - - private class BasicAuthenticationHandler extends AbstractHandler - { - @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - try - { - String type = request.getParameter("type"); - String authorization = request.getHeader(HttpHeader.AUTHORIZATION.asString()); - if (authorization == null) - { - String realm = request.getParameter("realm"); - response.setStatus(401); - switch (type) - { - case "Basic": - { - response.setHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\""); - break; - } - default: - { - throw new IllegalStateException(); - } - } - } - else - { - switch (type) - { - case "Basic": - { - String user = request.getParameter("user"); - String password = request.getParameter("password"); - String expected = "Basic " + B64Code.encode(user + ":" + password); - if (!expected.equals(authorization)) - throw new IOException(expected + " != " + authorization); - IO.copy(request.getInputStream(), response.getOutputStream()); - break; - } - default: - { - throw new IllegalStateException(); - } - } - } - } - finally - { - baseRequest.setHandled(true); - } - } - } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java index 8ea31a6c67a..6bc17b34895 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientStreamTest.java @@ -49,12 +49,12 @@ public class HttpClientStreamTest extends AbstractHttpClientServerTest output.write(kb); } - start(new EmptyHandler()); + start(new EmptyServerHandler()); final AtomicLong requestTime = new AtomicLong(); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .file(upload) - .listener(new Request.Listener.Adapter() + .listener(new Request.Listener.Empty() { @Override public void onSuccess(Request request) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java index 8eb976960cc..e9380ef0eb1 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java @@ -57,7 +57,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest @Test public void testStoppingClosesConnections() throws Exception { - start(new EmptyHandler()); + start(new EmptyServerHandler()); String scheme = "http"; String host = "localhost"; @@ -84,14 +84,14 @@ 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()); } @Test public void test_DestinationCount() throws Exception { - start(new EmptyHandler()); + start(new EmptyServerHandler()); String scheme = "http"; String host = "localhost"; @@ -111,7 +111,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest @Test public void test_GET_ResponseWithoutContent() throws Exception { - start(new EmptyHandler()); + start(new EmptyServerHandler()); Response response = client.GET("http://localhost:" + connector.getLocalPort()).get(5, TimeUnit.SECONDS); @@ -212,14 +212,14 @@ public class HttpClientTest extends AbstractHttpClientServerTest @Test public void test_QueuedRequest_IsSent_WhenPreviousRequestSucceeded() throws Exception { - start(new EmptyHandler()); + start(new EmptyServerHandler()); client.setMaxConnectionsPerAddress(1); final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch successLatch = new CountDownLatch(2); client.newRequest("http://localhost:" + connector.getLocalPort()) - .listener(new org.eclipse.jetty.client.api.Request.Listener.Adapter() + .listener(new org.eclipse.jetty.client.api.Request.Listener.Empty() { @Override public void onBegin(org.eclipse.jetty.client.api.Request request) @@ -234,7 +234,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest } } }) - .send(new Response.Listener.Adapter() + .send(new Response.Listener.Empty() { @Override public void onSuccess(Response response) @@ -245,7 +245,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest }); client.newRequest("http://localhost:" + connector.getLocalPort()) - .listener(new org.eclipse.jetty.client.api.Request.Listener.Adapter() + .listener(new org.eclipse.jetty.client.api.Request.Listener.Empty() { @Override public void onQueued(org.eclipse.jetty.client.api.Request request) @@ -253,7 +253,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest latch.countDown(); } }) - .send(new Response.Listener.Adapter() + .send(new Response.Listener.Empty() { @Override public void onSuccess(Response response) @@ -270,7 +270,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest @Test public void test_QueuedRequest_IsSent_WhenPreviousRequestClosedConnection() throws Exception { - start(new EmptyHandler()); + start(new EmptyServerHandler()); client.setMaxConnectionsPerAddress(1); final long idleTimeout = 1000; @@ -278,7 +278,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest final CountDownLatch latch = new CountDownLatch(3); client.newRequest("http://localhost:" + connector.getLocalPort()) - .listener(new org.eclipse.jetty.client.api.Request.Listener.Adapter() + .listener(new org.eclipse.jetty.client.api.Request.Listener.Empty() { @Override public void onBegin(org.eclipse.jetty.client.api.Request request) @@ -299,7 +299,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest latch.countDown(); } }) - .send(new Response.Listener.Adapter() + .send(new Response.Listener.Empty() { @Override public void onFailure(Response response, Throwable failure) @@ -309,7 +309,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest }); client.newRequest("http://localhost:" + connector.getLocalPort()) - .send(new Response.Listener.Adapter() + .send(new Response.Listener.Empty() { @Override public void onSuccess(Response response) @@ -326,7 +326,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest @Test public void test_ExchangeIsComplete_OnlyWhenBothRequestAndResponseAreComplete() throws Exception { - start(new EmptyHandler()); + start(new EmptyServerHandler()); // Prepare a big file to upload Path targetTestsDir = MavenTestingUtils.getTargetTestingDir().toPath(); @@ -345,7 +345,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest final AtomicLong responseTime = new AtomicLong(); client.newRequest("localhost", connector.getLocalPort()) .file(file) - .listener(new org.eclipse.jetty.client.api.Request.Listener.Adapter() + .listener(new org.eclipse.jetty.client.api.Request.Listener.Empty() { @Override public void onSuccess(org.eclipse.jetty.client.api.Request request) @@ -354,7 +354,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest latch.countDown(); } }) - .send(new Response.Listener.Adapter() + .send(new Response.Listener.Empty() { @Override public void onSuccess(Response response) @@ -414,7 +414,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest return Arrays.asList(ByteBuffer.allocate(chunkSize), null).iterator(); } }) - .send(new Response.Listener.Adapter() + .send(new Response.Listener.Empty() { @Override public void onComplete(Result result) @@ -429,13 +429,13 @@ public class HttpClientTest extends AbstractHttpClientServerTest @Test public void test_ExchangeIsComplete_WhenRequestFails_WithNoResponse() throws Exception { - start(new EmptyHandler()); + start(new EmptyServerHandler()); final CountDownLatch latch = new CountDownLatch(1); final String host = "localhost"; final int port = connector.getLocalPort(); client.newRequest(host, port) - .listener(new org.eclipse.jetty.client.api.Request.Listener.Adapter() + .listener(new org.eclipse.jetty.client.api.Request.Listener.Empty() { @Override public void onBegin(org.eclipse.jetty.client.api.Request request) @@ -444,7 +444,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest destination.getActiveConnections().peek().close(); } }) - .send(new Response.Listener.Adapter() + .send(new Response.Listener.Empty() { @Override public void onComplete(Result result) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java index 19f97bdbb03..5746d4a6d16 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java @@ -42,7 +42,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest @Test public void test_SuccessfulRequest_ReturnsConnection() throws Exception { - start(new EmptyHandler()); + start(new EmptyServerHandler()); String scheme = "http"; String host = "localhost"; @@ -58,7 +58,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest final CountDownLatch headersLatch = new CountDownLatch(1); final CountDownLatch successLatch = new CountDownLatch(3); client.newRequest(host, port) - .listener(new Request.Listener.Adapter() + .listener(new Request.Listener.Empty() { @Override public void onSuccess(Request request) @@ -66,7 +66,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest successLatch.countDown(); } }) - .send(new Response.Listener.Adapter() + .send(new Response.Listener.Empty() { @Override public void onHeaders(Response response) @@ -100,7 +100,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest @Test public void test_FailedRequest_RemovesConnection() throws Exception { - start(new EmptyHandler()); + start(new EmptyServerHandler()); String scheme = "http"; String host = "localhost"; @@ -115,7 +115,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest final CountDownLatch headersLatch = new CountDownLatch(1); final CountDownLatch failureLatch = new CountDownLatch(2); - client.newRequest(host, port).listener(new Request.Listener.Adapter() + client.newRequest(host, port).listener(new Request.Listener.Empty() { @Override public void onBegin(Request request) @@ -129,7 +129,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest { failureLatch.countDown(); } - }).send(new Response.Listener.Adapter() + }).send(new Response.Listener.Empty() { @Override public void onComplete(Result result) @@ -151,7 +151,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest @Test public void test_BadRequest_RemovesConnection() throws Exception { - start(new EmptyHandler()); + start(new EmptyServerHandler()); String scheme = "http"; String host = "localhost"; @@ -166,7 +166,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest final CountDownLatch successLatch = new CountDownLatch(3); client.newRequest(host, port) - .listener(new Request.Listener.Adapter() + .listener(new Request.Listener.Empty() { @Override public void onBegin(Request request) @@ -181,7 +181,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest successLatch.countDown(); } }) - .send(new Response.Listener.Adapter() + .send(new Response.Listener.Empty() { @Override public void onSuccess(Response response) @@ -209,7 +209,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest @Test public void test_ConnectionFailure_RemovesConnection() throws Exception { - start(new EmptyHandler()); + start(new EmptyServerHandler()); String scheme = "http"; String host = "localhost"; @@ -226,7 +226,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest final CountDownLatch failureLatch = new CountDownLatch(2); client.newRequest(host, port) - .listener(new Request.Listener.Adapter() + .listener(new Request.Listener.Empty() { @Override public void onFailure(Request request, Throwable failure) @@ -234,7 +234,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest failureLatch.countDown(); } }) - .send(new Response.Listener.Adapter() + .send(new Response.Listener.Empty() { @Override public void onComplete(Result result) @@ -276,7 +276,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest final CountDownLatch latch = new CountDownLatch(1); client.newRequest(host, port) - .send(new Response.Listener.Adapter() + .send(new Response.Listener.Empty() { @Override public void onComplete(Result result) @@ -321,7 +321,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest final CountDownLatch latch = new CountDownLatch(1); client.newRequest(host, port) .content(new ByteBufferContentProvider(ByteBuffer.allocate(16 * 1024 * 1024))) - .send(new Response.Listener.Adapter() + .send(new Response.Listener.Empty() { @Override public void onComplete(Result result) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieStoreTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieStoreTest.java index 1a8e54f9225..b616dd18449 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieStoreTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieStoreTest.java @@ -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 result = cookies.getCookies(destination, "/"); + List 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 result = cookies.getCookies(destination, "/"); + List 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 result = cookies.getCookies(destination, "/"); + List 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 result = cookies.getCookies(destination, "/path"); + List 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 result = cookies.getCookies(grandChildDestination, "/path"); + List 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 result = cookies.getCookies(destination, "/"); + List 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 result = cookies.getCookies(destination, "/"); + List 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()); } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java index c79054096cf..b1cbf7c6e1f 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java @@ -59,7 +59,7 @@ public class HttpCookieTest extends AbstractHttpClientServerTest Assert.assertEquals(200, response.status()); Destination destination = client.getDestination(scheme, host, port); - List cookies = client.getCookieStore().getCookies(destination, path); + List cookies = client.getCookieStore().findCookies(destination, path); Assert.assertNotNull(cookies); Assert.assertEquals(1, cookies.size()); HttpCookie cookie = cookies.get(0); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpDestinationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpDestinationTest.java index 2d57e34949c..9cebbf403bc 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpDestinationTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpDestinationTest.java @@ -32,7 +32,7 @@ public class HttpDestinationTest extends AbstractHttpClientServerTest @Before public void init() throws Exception { - start(new EmptyHandler()); + start(new EmptyServerHandler()); } @Test diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java index 07f300de06d..d8a8ef4b817 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpReceiverTest.java @@ -26,7 +26,7 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.util.BufferingResponseListener; +import org.eclipse.jetty.client.util.BlockingResponseListener; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpVersion; @@ -66,6 +66,7 @@ public class HttpReceiverTest HttpExchange exchange = new HttpExchange(conversation, connection, null, listener); conversation.exchanges().offer(exchange); connection.setExchange(exchange); + exchange.requestComplete(true); return exchange; } @@ -78,7 +79,7 @@ public class HttpReceiverTest "\r\n"); final AtomicReference responseRef = new AtomicReference<>(); final CountDownLatch latch = new CountDownLatch(1); - HttpExchange exchange = newExchange(new Response.Listener.Adapter() + HttpExchange exchange = newExchange(new Response.Listener.Empty() { @Override public void onSuccess(Response response) @@ -110,11 +111,11 @@ public class HttpReceiverTest "Content-length: " + content.length() + "\r\n" + "\r\n" + content); - BufferingResponseListener listener = new BufferingResponseListener(); + BlockingResponseListener listener = new BlockingResponseListener(); HttpExchange exchange = newExchange(listener); exchange.receive(); - Response response = listener.await(5, TimeUnit.SECONDS); + Response response = listener.get(5, TimeUnit.SECONDS); Assert.assertNotNull(response); Assert.assertEquals(200, response.status()); Assert.assertEquals("OK", response.reason()); @@ -123,7 +124,7 @@ public class HttpReceiverTest Assert.assertNotNull(headers); Assert.assertEquals(1, headers.size()); Assert.assertEquals(String.valueOf(content.length()), headers.get(HttpHeader.CONTENT_LENGTH)); - String received = listener.contentAsString("UTF-8"); + String received = listener.getContent("UTF-8"); Assert.assertEquals(content, received); } @@ -137,7 +138,7 @@ public class HttpReceiverTest "Content-length: " + (content1.length() + content2.length()) + "\r\n" + "\r\n" + content1); - BufferingResponseListener listener = new BufferingResponseListener(); + BlockingResponseListener listener = new BlockingResponseListener(); HttpExchange exchange = newExchange(listener); exchange.receive(); endPoint.setInputEOF(); @@ -145,7 +146,7 @@ public class HttpReceiverTest try { - listener.await(5, TimeUnit.SECONDS); + listener.get(5, TimeUnit.SECONDS); Assert.fail(); } catch (ExecutionException e) @@ -161,7 +162,7 @@ public class HttpReceiverTest "HTTP/1.1 200 OK\r\n" + "Content-length: 1\r\n" + "\r\n"); - BufferingResponseListener listener = new BufferingResponseListener(); + BlockingResponseListener listener = new BlockingResponseListener(); HttpExchange exchange = newExchange(listener); exchange.receive(); // Simulate an idle timeout @@ -169,7 +170,7 @@ public class HttpReceiverTest try { - listener.await(5, TimeUnit.SECONDS); + listener.get(5, TimeUnit.SECONDS); Assert.fail(); } catch (ExecutionException e) @@ -185,13 +186,13 @@ public class HttpReceiverTest "HTTP/1.1 200 OK\r\n" + "Content-length: A\r\n" + "\r\n"); - BufferingResponseListener listener = new BufferingResponseListener(); + BlockingResponseListener listener = new BlockingResponseListener(); HttpExchange exchange = newExchange(listener); exchange.receive(); try { - listener.await(5, TimeUnit.SECONDS); + listener.get(5, TimeUnit.SECONDS); Assert.fail(); } catch (ExecutionException e) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java new file mode 100644 index 00000000000..a6c15f85246 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java @@ -0,0 +1,188 @@ +// +// ======================================================================== +// 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.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.ByteBufferContentProvider; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.IO; +import org.junit.Assert; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class HttpRequestAbortTest extends AbstractHttpClientServerTest +{ + @Test + public void testAbortOnQueued() throws Exception + { + start(new EmptyServerHandler()); + + final AtomicBoolean begin = new AtomicBoolean(); + try + { + client.newRequest("localhost", connector.getLocalPort()) + .listener(new Request.Listener.Empty() + { + @Override + public void onQueued(Request request) + { + request.abort(); + } + + @Override + public void onBegin(Request request) + { + begin.set(true); + } + }) + .send().get(5, TimeUnit.SECONDS); + fail(); + } + catch (ExecutionException x) + { + HttpRequestException xx = (HttpRequestException)x.getCause(); + Request request = xx.getRequest(); + Assert.assertNotNull(request); + Assert.assertFalse(begin.get()); + } + } + + @Test + public void testAbortOnBegin() throws Exception + { + start(new EmptyServerHandler()); + + final AtomicBoolean headers = new AtomicBoolean(); + try + { + client.newRequest("localhost", connector.getLocalPort()) + .listener(new Request.Listener.Empty() + { + @Override + public void onBegin(Request request) + { + request.abort(); + } + + @Override + public void onHeaders(Request request) + { + headers.set(true); + } + }) + .send().get(5, TimeUnit.SECONDS); + fail(); + } + catch (ExecutionException x) + { + HttpRequestException xx = (HttpRequestException)x.getCause(); + Request request = xx.getRequest(); + Assert.assertNotNull(request); + Assert.assertFalse(headers.get()); + } + } + + @Test + public void testAbortOnHeaders() throws Exception + { + start(new EmptyServerHandler()); + + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .listener(new Request.Listener.Empty() + { + @Override + public void onHeaders(Request request) + { + // Too late to abort + request.abort(); + } + }) + .send().get(5, TimeUnit.SECONDS); + assertEquals(200, response.status()); + } + + @Test + public void testAbortOnHeadersWithContent() throws Exception + { + final AtomicReference failure = new AtomicReference<>(); + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + try + { + baseRequest.setHandled(true); + IO.copy(request.getInputStream(), response.getOutputStream()); + } + catch (IOException x) + { + failure.set(x); + throw x; + } + } + }); + + // Test can behave in 2 ways: + // A) if the request is failed before the request arrived, then we get an ExecutionException + // B) if the request is failed after the request arrived, then we get a 500 + try + { + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .listener(new Request.Listener.Empty() + { + @Override + public void onHeaders(Request request) + { + request.abort(); + } + }) + .content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1})) + { + @Override + public long length() + { + return -1; + } + }) + .send().get(5, TimeUnit.SECONDS); + Assert.assertNotNull(failure.get()); + assertEquals(500, response.status()); + } + catch (ExecutionException x) + { + HttpRequestException xx = (HttpRequestException)x.getCause(); + Request request = xx.getRequest(); + Assert.assertNotNull(request); + } + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java new file mode 100644 index 00000000000..30a5dbb5cca --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java @@ -0,0 +1,169 @@ +// +// ======================================================================== +// 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.IOException; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import javax.servlet.ServletException; +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.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.junit.Assert; +import org.junit.Test; + +public class HttpResponseAbortTest extends AbstractHttpClientServerTest +{ + @Test + public void testAbortOnBegin() throws Exception + { + start(new EmptyServerHandler()); + + final CountDownLatch latch = new CountDownLatch(1); + client.newRequest("localhost", connector.getLocalPort()) + .send(new Response.Listener.Empty() + { + @Override + public void onBegin(Response response) + { + response.abort(); + } + + @Override + public void onComplete(Result result) + { + Assert.assertTrue(result.isFailed()); + latch.countDown(); + } + }); + Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testAbortOnHeaders() throws Exception + { + start(new EmptyServerHandler()); + + final CountDownLatch latch = new CountDownLatch(1); + client.newRequest("localhost", connector.getLocalPort()) + .send(new Response.Listener.Empty() + { + @Override + public void onHeaders(Response response) + { + response.abort(); + } + + @Override + public void onComplete(Result result) + { + Assert.assertTrue(result.isFailed()); + latch.countDown(); + } + }); + Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testAbortOnContent() throws Exception + { + start(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + try + { + baseRequest.setHandled(true); + OutputStream output = response.getOutputStream(); + output.write(1); + output.flush(); + output.write(2); + output.flush(); + } + catch (IOException ignored) + { + // The client may have already closed, and we'll get an exception here, but it's expected + } + } + }); + + final CountDownLatch latch = new CountDownLatch(1); + client.newRequest("localhost", connector.getLocalPort()) + .send(new Response.Listener.Empty() + { + @Override + public void onContent(Response response, ByteBuffer content) + { + response.abort(); + } + + @Override + public void onComplete(Result result) + { + Assert.assertTrue(result.isFailed()); + latch.countDown(); + } + }); + Assert.assertTrue(latch.await(555, TimeUnit.SECONDS)); + } + + @Test(expected = CancellationException.class) + public void testCancelFuture() throws Exception + { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference > ref = new AtomicReference<>(); + start(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + try + { + latch.await(5, TimeUnit.SECONDS); + baseRequest.setHandled(true); + ref.get().cancel(true); + OutputStream output = response.getOutputStream(); + output.write(new byte[]{0, 1, 2, 3, 4, 5, 6, 7}); + } + catch (InterruptedException x) + { + throw new InterruptedIOException(); + } + } + }); + + Future future = client.newRequest("localhost", connector.getLocalPort()).send(); + ref.set(future); + latch.countDown(); + + future.get(5, TimeUnit.SECONDS); + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpSenderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpSenderTest.java index 7a25877e952..9a5c0ec891c 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpSenderTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpSenderTest.java @@ -60,7 +60,7 @@ public class HttpSenderTest Request request = client.newRequest(URI.create("http://localhost/")); final CountDownLatch headersLatch = new CountDownLatch(1); final CountDownLatch successLatch = new CountDownLatch(1); - request.listener(new Request.Listener.Adapter() + request.listener(new Request.Listener.Empty() { @Override public void onHeaders(Request request) @@ -121,7 +121,7 @@ public class HttpSenderTest HttpConnection connection = new HttpConnection(client, endPoint, destination); Request request = client.newRequest(URI.create("http://localhost/")); final CountDownLatch failureLatch = new CountDownLatch(2); - request.listener(new Request.Listener.Adapter() + request.listener(new Request.Listener.Empty() { @Override public void onFailure(Request request, Throwable x) @@ -129,7 +129,7 @@ public class HttpSenderTest failureLatch.countDown(); } }); - connection.send(request, new Response.Listener.Adapter() + connection.send(request, new Response.Listener.Empty() { @Override public void onComplete(Result result) @@ -150,7 +150,7 @@ public class HttpSenderTest HttpConnection connection = new HttpConnection(client, endPoint, destination); Request request = client.newRequest(URI.create("http://localhost/")); final CountDownLatch failureLatch = new CountDownLatch(2); - request.listener(new Request.Listener.Adapter() + request.listener(new Request.Listener.Empty() { @Override public void onFailure(Request request, Throwable x) @@ -158,7 +158,7 @@ public class HttpSenderTest failureLatch.countDown(); } }); - connection.send(request, new Response.Listener.Adapter() + connection.send(request, new Response.Listener.Empty() { @Override public void onComplete(Result result) @@ -188,7 +188,7 @@ public class HttpSenderTest request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content.getBytes("UTF-8")))); final CountDownLatch headersLatch = new CountDownLatch(1); final CountDownLatch successLatch = new CountDownLatch(1); - request.listener(new Request.Listener.Adapter() + request.listener(new Request.Listener.Empty() { @Override public void onHeaders(Request request) @@ -223,7 +223,7 @@ public class HttpSenderTest request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content1.getBytes("UTF-8")), ByteBuffer.wrap(content2.getBytes("UTF-8")))); final CountDownLatch headersLatch = new CountDownLatch(1); final CountDownLatch successLatch = new CountDownLatch(1); - request.listener(new Request.Listener.Adapter() + request.listener(new Request.Listener.Empty() { @Override public void onHeaders(Request request) @@ -265,7 +265,7 @@ public class HttpSenderTest }); final CountDownLatch headersLatch = new CountDownLatch(1); final CountDownLatch successLatch = new CountDownLatch(1); - request.listener(new Request.Listener.Adapter() + request.listener(new Request.Listener.Empty() { @Override public void onHeaders(Request request) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java b/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java index d3babdf8ea8..55dbec3e87c 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java @@ -27,7 +27,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.util.BufferingResponseListener; +import org.eclipse.jetty.client.util.BlockingResponseListener; import org.eclipse.jetty.client.util.PathContentProvider; import org.eclipse.jetty.client.util.StreamingResponseListener; import org.eclipse.jetty.http.HttpCookie; @@ -68,7 +68,7 @@ public class Usage .param("a", "b") .header("X-Header", "Y-value") .agent("Jetty HTTP Client") - .decoder(null) +// .decoder(null) .content(null) .idleTimeout(5000L); Future responseFuture = request.send(); @@ -82,7 +82,7 @@ public class Usage HttpClient client = new HttpClient(); final AtomicReference responseRef = new AtomicReference<>(); final CountDownLatch latch = new CountDownLatch(1); - client.newRequest("localhost", 8080).send(new Response.Listener.Adapter() + client.newRequest("localhost", 8080).send(new Response.Listener.Empty() { @Override public void onSuccess(Response response) @@ -102,7 +102,7 @@ public class Usage { HttpClient client = new HttpClient(); Response response = client.newRequest("localhost", 8080) - .listener(new Request.Listener.Adapter() + .listener(new Request.Listener.Empty() { @Override public void onSuccess(Request request) @@ -119,9 +119,9 @@ public class Usage try (Connection connection = client.getDestination("http", "localhost", 8080).newConnection().get(5, TimeUnit.SECONDS)) { Request request = client.newRequest("localhost", 8080); - BufferingResponseListener listener = new BufferingResponseListener(); + BlockingResponseListener listener = new BlockingResponseListener(); connection.send(request, listener); - Response response = listener.await(5, TimeUnit.SECONDS); + Response response = listener.get(5, TimeUnit.SECONDS); Assert.assertNotNull(response); Assert.assertEquals(200, response.status()); } diff --git a/jetty-client/src/test/resources/realm.properties b/jetty-client/src/test/resources/realm.properties new file mode 100644 index 00000000000..54ace472cb6 --- /dev/null +++ b/jetty-client/src/test/resources/realm.properties @@ -0,0 +1,3 @@ +# Format is : , +basic:basic +digest:digest diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml index b77c9dd653d..a348cfe27d0 100644 --- a/jetty-distribution/pom.xml +++ b/jetty-distribution/pom.xml @@ -349,6 +349,12 @@ ${project.version} ++ +org.eclipse.jetty +jetty-plus +${project.version} ++ org.eclipse.jetty jetty-annotations diff --git a/jetty-distribution/src/main/resources/start.ini b/jetty-distribution/src/main/resources/start.ini index b86d24d2c77..e9dfa9abd28 100644 --- a/jetty-distribution/src/main/resources/start.ini +++ b/jetty-distribution/src/main/resources/start.ini @@ -51,7 +51,7 @@ # for a full listing do # java -jar start.jar --list-options #----------------------------------------------------------- -OPTIONS=Server,jsp,resources,websocket,ext,plus +OPTIONS=Server,jsp,resources,websocket,ext #----------------------------------------------------------- @@ -64,7 +64,6 @@ etc/jetty.xml # etc/jetty-ssl.xml # etc/jetty-requestlog.xml etc/jetty-deploy.xml -#etc/jetty-overlay.xml etc/jetty-webapps.xml etc/jetty-contexts.xml etc/jetty-testrealm.xml diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java index 9c9862f8d52..3200d0edb36 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java @@ -47,6 +47,8 @@ import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; // TODO: Make this class inherit from oej.util.Fields +// TODO move this class to jetty-http? + /** * HTTP Fields. A collection of HTTP header and or Trailer fields. @@ -787,11 +789,11 @@ public class HttpFields implements Iterable// Format value and params StringBuilder buf = new StringBuilder(128); String name_value_params; - boolean quoted = QuotedStringTokenizer.quoteIfNeeded(buf, name, delim); + QuotedStringTokenizer.quoteIfNeeded(buf, name, delim); buf.append('='); String start=buf.toString(); if (value != null && value.length() > 0) - quoted|=QuotedStringTokenizer.quoteIfNeeded(buf, value, delim); + QuotedStringTokenizer.quoteIfNeeded(buf, value, delim); if (path != null && path.length() > 0) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index 2db27b62bc5..cc1d00be436 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -59,9 +59,9 @@ public class HttpParser CLOSED }; - private final HttpHandler _handler; - private final RequestHandler _requestHandler; - private final ResponseHandler _responseHandler; + private final HttpHandler _handler; + private final RequestHandler _requestHandler; + private final ResponseHandler _responseHandler; private final int _maxHeaderBytes; private HttpHeader _header; private String _headerString; @@ -91,19 +91,19 @@ public class HttpParser private final Utf8StringBuilder _utf8=new Utf8StringBuilder(); /* ------------------------------------------------------------------------------- */ - public HttpParser(RequestHandler handler) + public HttpParser(RequestHandler handler) { this(handler,-1); } /* ------------------------------------------------------------------------------- */ - public HttpParser(ResponseHandler handler) + public HttpParser(ResponseHandler handler) { this(handler,-1); } /* ------------------------------------------------------------------------------- */ - public HttpParser(RequestHandler handler,int maxHeaderBytes) + public HttpParser(RequestHandler handler,int maxHeaderBytes) { _handler=handler; _requestHandler=handler; @@ -112,7 +112,7 @@ public class HttpParser } /* ------------------------------------------------------------------------------- */ - public HttpParser(ResponseHandler handler,int maxHeaderBytes) + public HttpParser(ResponseHandler handler,int maxHeaderBytes) { _handler=handler; _requestHandler=null; @@ -931,12 +931,14 @@ public class HttpParser case CLOSED: if (BufferUtil.hasContent(buffer)) { - _headerBytes+=buffer.remaining(); + int len=buffer.remaining(); + _headerBytes+=len; if (_headerBytes>_maxHeaderBytes) { + Thread.sleep(100); String chars = BufferUtil.toDetailString(buffer); BufferUtil.clear(buffer); - throw new IllegalStateException(this+" data when CLOSED: "+chars); + throw new IllegalStateException(String.format("%s %d/%d data when CLOSED:%s",this,len,_headerBytes,chars)); } BufferUtil.clear(buffer); } @@ -1109,6 +1111,11 @@ public class HttpParser } break; } + case CLOSED: + { + BufferUtil.clear(buffer); + return false; + } } } @@ -1157,7 +1164,7 @@ public class HttpParser case CLOSED: break; - + default: setState(State.END); if (!_headResponse) @@ -1182,6 +1189,7 @@ public class HttpParser } setState(State.CLOSED); _endOfContent=EndOfContent.UNKNOWN_CONTENT; + _contentLength=-1; _contentPosition=0; _responseStatus=0; _headerBytes=0; @@ -1194,6 +1202,7 @@ public class HttpParser // reset state setState(State.START); _endOfContent=EndOfContent.UNKNOWN_CONTENT; + _contentLength=-1; _contentPosition=0; _responseStatus=0; _contentChunk=null; @@ -1204,8 +1213,6 @@ public class HttpParser /* ------------------------------------------------------------------------------- */ private void setState(State state) { - if (_state==State.CLOSED && state==State.END) - new Throwable().printStackTrace(); _state=state; } diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java index aeebc0c6140..89f5bd4c292 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java @@ -115,9 +115,9 @@ public class HttpFieldsTest header.put("name:2", "value:\r\n2"); ByteBuffer buffer = BufferUtil.allocate(1024); - buffer.clear(); + BufferUtil.flipToFill(buffer); header.putTo(buffer); - buffer.flip(); + BufferUtil.flipToFlush(buffer,0); String out = BufferUtil.toString(buffer); assertThat(out,containsString("name0: value??0")); assertThat(out,containsString("name??1: value1")); @@ -134,9 +134,9 @@ public class HttpFieldsTest header.put("CONTENT-ENCODING", "gZIP"); ByteBuffer buffer = BufferUtil.allocate(1024); - buffer.clear(); + BufferUtil.flipToFill(buffer); header.putTo(buffer); - buffer.flip(); + BufferUtil.flipToFlush(buffer,0); String out = BufferUtil.toString(buffer); Assert.assertThat(out,Matchers.containsString(HttpHeader.CONNECTION+": "+HttpHeaderValue.KEEP_ALIVE)); diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java index abe1e1f7c3f..cb62efdbcb3 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java @@ -45,6 +45,8 @@ public abstract class AbstractConnection implements Connection private enum State {IDLE,INTERESTED,FILLING,FILLING_INTERESTED}; private final AtomicReference _state = new AtomicReference<>(State.IDLE); + private int _inputBufferSize=8192; + public AbstractConnection(EndPoint endp, Executor executor) { this(endp, executor, true); @@ -117,6 +119,16 @@ public abstract class AbstractConnection implements Connection } }; } + + public int getInputBufferSize() + { + return _inputBufferSize; + } + + public void setInputBufferSize(int inputBufferSize) + { + _inputBufferSize = inputBufferSize; + } public Executor getExecutor() { @@ -146,7 +158,6 @@ public abstract class AbstractConnection implements Connection break; case FILLING: - if (_state.compareAndSet(State.FILLING,State.FILLING_INTERESTED)) break loop; break; diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java index 8abdc2287bc..44177a76975 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java @@ -159,12 +159,6 @@ public abstract class AbstractEndPoint implements EndPoint _writeFlusher.write(context, callback, buffers); } - @Override - public boolean isBufferingOutput() - { - return false; - } - protected abstract void onIncompleteFlush(); protected abstract boolean needsFill() throws IOException; diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java index e29895c93f0..6c94ac8c658 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java @@ -321,14 +321,15 @@ public class ByteArrayEndPoint extends AbstractEndPoint * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer) */ @Override - public int flush(ByteBuffer... buffers) throws IOException + public boolean flush(ByteBuffer... buffers) throws IOException { if (_closed) throw new IOException("CLOSED"); if (_oshut) throw new IOException("OSHUT"); - int flushed=0; + boolean flushed=true; + boolean idle=true; for (ByteBuffer b : buffers) { @@ -345,13 +346,17 @@ public class ByteArrayEndPoint extends AbstractEndPoint } } - flushed+=BufferUtil.flipPutFlip(b,_out); + if (BufferUtil.flipPutFlip(b,_out)>0) + idle=false; if (BufferUtil.hasContent(b)) + { + flushed=false; break; + } } } - if (flushed>0) + if (!idle) notIdle(); return flushed; } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java index dd90d35ef03..39ac51dcc0d 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ChannelEndPoint.java @@ -38,7 +38,7 @@ import org.eclipse.jetty.util.thread.Scheduler; * Channel End Point. * Holds the channel and socket for an NIO endpoint. */ -public class ChannelEndPoint extends AbstractEndPoint +public class ChannelEndPoint extends AbstractEndPoint implements SocketBased { private static final Logger LOG = Log.getLogger(ChannelEndPoint.class); @@ -154,7 +154,7 @@ public class ChannelEndPoint extends AbstractEndPoint } @Override - public int flush(ByteBuffer... buffers) throws IOException + public boolean flush(ByteBuffer... buffers) throws IOException { int flushed=0; try @@ -172,7 +172,7 @@ public class ChannelEndPoint extends AbstractEndPoint int l=_channel.write(b); if (l>0) flushed+=l; - else + if (b.hasRemaining()) break; } } @@ -183,16 +183,23 @@ public class ChannelEndPoint extends AbstractEndPoint { throw new EofException(e); } + + boolean all_flushed=true; if (flushed>0) { notIdle(); // clear empty buffers to prevent position creeping up the buffer for (ByteBuffer b : buffers) + { if (BufferUtil.isEmpty(b)) BufferUtil.clear(b); + else + all_flushed=false; + } } - return flushed; + + return all_flushed; } public ByteChannel getChannel() @@ -206,6 +213,7 @@ public class ChannelEndPoint extends AbstractEndPoint return _channel; } + @Override public Socket getSocket() { return _socket; diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java index 1809451e490..fb7213f6031 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/EndPoint.java @@ -170,11 +170,12 @@ public interface EndPoint extends Closeable * Flush data from the passed header/buffer to this endpoint. As many bytes as can be consumed * are taken from the header/buffer position up until the buffer limit. The header/buffers position * is updated to indicate how many bytes have been consumed. + * @return True IFF all the buffers have been consumed and the endpoint has flushed the data to its + * destination (ie is not buffering any data). * - * @return the number of bytes written * @throws EofException If the endpoint is closed or output is shutdown. */ - int flush(ByteBuffer... buffer) throws IOException; + boolean flush(ByteBuffer... buffer) throws IOException; /* ------------------------------------------------------------ */ /** @@ -242,9 +243,4 @@ public interface EndPoint extends Closeable void onClose(); - /** - * @return True if the endpoint is buffering output. - */ - boolean isBufferingOutput(); - } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java index 3451a077c58..00672e4d25b 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSelectChannelEndPoint.java @@ -49,23 +49,22 @@ public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint } @Override - public int flush(ByteBuffer... buffers) throws IOException + public boolean flush(ByteBuffer... buffers) throws IOException { - int written=0; + boolean flushed=true; for (ByteBuffer b : buffers) { if (b.hasRemaining()) { int position = b.position(); - int l = super.flush(b); + flushed|=super.flush(b); + int l=b.position()-position; notifyOutgoing(b, position, l); - if (l==0) + if (!flushed) break; - else - written+=l; } } - return written; + return flushed; } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectChannelEndPoint.java index fa0c8507641..75822c86dcd 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectChannelEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectChannelEndPoint.java @@ -109,7 +109,10 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements SelectorMa Connection old = getConnection(); super.setConnection(connection); if (old != null && old != connection) + { + LOG.debug("Upgrading connection {} -> {} on endPoint {}", old, connection, this); _selector.getSelectorManager().connectionUpgraded(this, old); + } } @Override diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SocketBased.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SocketBased.java new file mode 100644 index 00000000000..0ca139a851d --- /dev/null +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SocketBased.java @@ -0,0 +1,29 @@ +// +// ======================================================================== +// 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.io; + +import java.net.Socket; + +/** + * Interface for Socket based I/O + */ +public interface SocketBased +{ + public Socket getSocket(); +} diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java index 1def518c55b..a5a8db6b48a 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java @@ -297,12 +297,12 @@ abstract public class WriteFlusher try { - _endPoint.flush(buffers); + boolean flushed=_endPoint.flush(buffers); // Are we complete? for (ByteBuffer b : buffers) { - if (BufferUtil.hasContent(b)) + if (!flushed||BufferUtil.hasContent(b)) { PendingState> pending=new PendingState<>(buffers, context, callback); if (updateState(__WRITING,pending)) @@ -312,17 +312,6 @@ abstract public class WriteFlusher return; } } - - // Handle buffering endpoint - if (_endPoint.isBufferingOutput()) - { - PendingState> pending=new PendingState<>(buffers, context, callback); - if (updateState(__WRITING,pending)) - onIncompleteFlushed(); - else - fail(new PendingState<>(buffers, context, callback)); - return; - } // If updateState didn't succeed, we don't care as our buffers have been written if (!updateState(__WRITING,__IDLE)) @@ -368,12 +357,12 @@ abstract public class WriteFlusher { ByteBuffer[] buffers = pending.getBuffers(); - _endPoint.flush(buffers); + boolean flushed=_endPoint.flush(buffers); // Are we complete? for (ByteBuffer b : buffers) { - if (BufferUtil.hasContent(b)) + if (!flushed || BufferUtil.hasContent(b)) { if (updateState(__COMPLETING,pending)) onIncompleteFlushed(); @@ -383,16 +372,6 @@ abstract public class WriteFlusher } } - // Handle buffering endpoint - if (_endPoint.isBufferingOutput()) - { - if (updateState(__COMPLETING,pending)) - onIncompleteFlushed(); - else - fail(pending); - return; - } - // If updateState didn't succeed, we don't care as our buffers have been written if (!updateState(__COMPLETING,__IDLE)) ignoreFail(); diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java index 9efa74ddccc..2482a014a53 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java @@ -19,7 +19,9 @@ package org.eclipse.jetty.io.ssl; import java.io.IOException; +import java.net.SocketException; import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; import java.util.Arrays; import java.util.concurrent.Executor; import javax.net.ssl.SSLEngine; @@ -31,12 +33,14 @@ import javax.net.ssl.SSLException; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.AbstractEndPoint; import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.ChannelEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.FillInterest; import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.io.SelectChannelEndPoint; +import org.eclipse.jetty.io.SocketBased; import org.eclipse.jetty.io.WriteFlusher; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; @@ -111,6 +115,18 @@ public class SslConnection extends AbstractConnection this._bufferPool = byteBufferPool; this._sslEngine = sslEngine; this._decryptedEndPoint = new DecryptedEndPoint(); + + if (endPoint instanceof SocketBased) + { + try + { + ((SocketBased) endPoint).getSocket().setSoLinger(true,30000); + } + catch (SocketException e) + { + throw new RuntimeIOException(e); + } + } } public SSLEngine getSSLEngine() @@ -397,9 +413,18 @@ public class SslConnection extends AbstractConnection } @Override - public boolean isBufferingOutput() + public void setConnection(Connection connection) { - return BufferUtil.hasContent(_encryptedOutput); + if (connection instanceof AbstractConnection) + { + AbstractConnection a = (AbstractConnection)connection; + if (a.getInputBufferSize()<_sslEngine.getSession().getApplicationBufferSize()); + a.setInputBufferSize(_sslEngine.getSession().getApplicationBufferSize()); + } + + connection.onOpen(); + + super.setConnection(connection); } public SslConnection getSslConnection() @@ -613,7 +638,7 @@ public class SslConnection extends AbstractConnection } @Override - public synchronized int flush(ByteBuffer... appOuts) throws IOException + public synchronized boolean flush(ByteBuffer... appOuts) throws IOException { // TODO remove this when we are certain it is OK if (Thread.currentThread().getName().contains("selector")) @@ -631,7 +656,7 @@ public class SslConnection extends AbstractConnection try { if (_cannotAcceptMoreAppDataToFlush) - return 0; + return false; // We will need a network buffer if (_encryptedOutput == null) @@ -647,13 +672,16 @@ public class SslConnection extends AbstractConnection LOG.debug("{} wrap {}", SslConnection.this, wrapResult); BufferUtil.flipToFlush(_encryptedOutput, pos); if (wrapResult.bytesConsumed()>0) - { consumed+=wrapResult.bytesConsumed(); - // clear empty buffers to prevent position creeping up the buffer - for (ByteBuffer b : appOuts) - if (BufferUtil.isEmpty(b)) - BufferUtil.clear(b); + boolean all_consumed=true; + // clear empty buffers to prevent position creeping up the buffer + for (ByteBuffer b : appOuts) + { + if (BufferUtil.isEmpty(b)) + BufferUtil.clear(b); + else + all_consumed=false; } // and deal with the results returned from the sslEngineWrap @@ -669,15 +697,11 @@ public class SslConnection extends AbstractConnection // the write has progressed normally and let a subsequent call to flush (or WriteFlusher#onIncompleteFlushed) // to finish writing the close handshake. The caller will find out about the close on a subsequent flush or fill. if (BufferUtil.hasContent(_encryptedOutput)) - return consumed; + return false; } - - // If we were flushing because of a fill needing to wrap, return normally and it will handle the closed state. - if (appOuts[0]==__FILL_CALLED_FLUSH) - return consumed; - + // otherwise we have written, and the caller will close the underlying connection - return consumed; + return all_consumed; case BUFFER_UNDERFLOW: throw new IllegalStateException(); @@ -695,7 +719,7 @@ public class SslConnection extends AbstractConnection { case NOT_HANDSHAKING: // Return with the number of bytes consumed (which may be 0) - return consumed; + return all_consumed&&BufferUtil.isEmpty(_encryptedOutput); case NEED_TASK: // run the task and continue @@ -715,7 +739,7 @@ public class SslConnection extends AbstractConnection _flushRequiresFillToProgress = true; fill(__FLUSH_CALLED_FILL); } - return consumed; + return all_consumed&&BufferUtil.isEmpty(_encryptedOutput); case FINISHED: throw new IllegalStateException(); diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java index b772b1df92a..8f75429df6d 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java @@ -115,23 +115,20 @@ public class ByteArrayEndPointTest ByteArrayEndPoint endp = new ByteArrayEndPoint((byte[])null,15); endp.setGrowOutput(true); - assertEquals(11,endp.flush(BufferUtil.toBuffer("some output"))); + assertEquals(true,endp.flush(BufferUtil.toBuffer("some output"))); assertEquals("some output",endp.getOutputString()); - assertEquals(10,endp.flush(BufferUtil.toBuffer(" some more"))); + assertEquals(true,endp.flush(BufferUtil.toBuffer(" some more"))); assertEquals("some output some more",endp.getOutputString()); - assertEquals(0,endp.flush()); + assertEquals(true,endp.flush()); assertEquals("some output some more",endp.getOutputString()); - assertEquals(0,endp.flush(BufferUtil.EMPTY_BUFFER)); + assertEquals(true,endp.flush(BufferUtil.EMPTY_BUFFER)); assertEquals("some output some more",endp.getOutputString()); - assertEquals(9,endp.flush(BufferUtil.EMPTY_BUFFER,BufferUtil.toBuffer(" and"),BufferUtil.toBuffer(" more"))); + assertEquals(true,endp.flush(BufferUtil.EMPTY_BUFFER,BufferUtil.toBuffer(" and"),BufferUtil.toBuffer(" more"))); assertEquals("some output some more and more",endp.getOutputString()); - - - } @Test @@ -142,13 +139,13 @@ public class ByteArrayEndPointTest endp.setOutput(BufferUtil.allocate(10)); ByteBuffer data = BufferUtil.toBuffer("Some more data."); - assertEquals(10,endp.flush(data)); + assertEquals(false,endp.flush(data)); assertEquals("Some more ",endp.getOutputString()); assertEquals("data.",BufferUtil.toString(data)); assertEquals("Some more ",endp.takeOutputString()); - assertEquals(5,endp.flush(data)); + assertEquals(true,endp.flush(data)); assertEquals("data.",BufferUtil.toString(endp.takeOutput())); } diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointTest.java index 669f2ae3cbc..977622da394 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectChannelEndPointTest.java @@ -179,7 +179,6 @@ public class SelectChannelEndPointTest _endp.write(null, blockingWrite, out.asReadOnlyBuffer()); blockingWrite.get(); } - LOG.info("Finished writing {}", _writeCount); progress = true; } diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java index 4ad43309895..e153660128f 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java @@ -348,8 +348,10 @@ public class WriteFlusherTest } @Test - public void testConcurrentAccessToWriteAndOnFail() throws IOException, InterruptedException, ExecutionException + public void testConcurrentAccessToWriteAndOnFail() throws Exception { + // TODO review this test - It was changed for the boolean flush return, but not really well inspected + final CountDownLatch failedCalledLatch = new CountDownLatch(1); final CountDownLatch writeCalledLatch = new CountDownLatch(1); final CountDownLatch writeCompleteLatch = new CountDownLatch(1); @@ -375,14 +377,28 @@ public class WriteFlusherTest executor.submit(new Writer(writeFlusher, callback)); assertThat("Write has been called.", writeCalledLatch.await(5, TimeUnit.SECONDS), is(true)); executor.submit(new FailedCaller(writeFlusher, failedCalledLatch)).get(); + + // callback failed is NOT called because in WRITING state failed() doesn't know about the callback. However // either the write succeeds or we get an IOException which will call callback.failed() - assertThat("callback failed", callback.isFailed(), is(false)); assertThat("write complete", writeCompleteLatch.await(5, TimeUnit.SECONDS), is(true)); + + // in this testcase we more or less emulate that the write has successfully finished and we return from // EndPoint.flush() back to WriteFlusher.write(). Then someone calls failed. So the callback should have been // completed. - assertThat("callback completed", callback.isCompleted(), is(true)); + try + { + callback.get(5,TimeUnit.SECONDS); + assertThat("callback completed", callback.isCompleted(), is(true)); + assertThat("callback failed", callback.isFailed(), is(false)); + } + catch(ExecutionException e) + { + // ignored because failure is expected + assertThat("callback failed", callback.isFailed(), is(true)); + } + assertThat("callback completed", callback.isDone(), is(true)); } private class ExposingStateCallback extends FutureCallback @@ -437,7 +453,7 @@ public class WriteFlusherTest flushCalledLatch.countDown(); // make sure we stay here, so write is called twice at the same time Thread.sleep(5000); - return null; + return Boolean.TRUE; } }); @@ -528,7 +544,7 @@ public class WriteFlusherTest } @Override - public int flush(ByteBuffer... buffers) throws IOException + public boolean flush(ByteBuffer... buffers) throws IOException { writeCalledLatch.countDown(); ByteBuffer byteBuffer = buffers[0]; @@ -549,13 +565,16 @@ public class WriteFlusherTest else if (byteBuffer.remaining() == 3) { byteBuffer.position(1); // pretend writing one byte - return 1; } else { byteBuffer.position(byteBuffer.limit()); } - return byteBuffer.limit() - oldPos; + + for (ByteBuffer b: buffers) + if (BufferUtil.hasContent(b)) + return false; + return true; } } diff --git a/jetty-io/src/test/resources/jetty-logging.properties b/jetty-io/src/test/resources/jetty-logging.properties index 9456416380c..d4922ad1951 100644 --- a/jetty-io/src/test/resources/jetty-logging.properties +++ b/jetty-io/src/test/resources/jetty-logging.properties @@ -1,6 +1,2 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -org.eclipse.jetty.LEVEL=WARN -# Enable the next two to debug SSL write blocked issue -org.eclipse.jetty.io.LEVEL=INFO -org.eclipse.jetty.io.SelectorManager.LEVEL=DEBUG -org.eclipse.jetty.io.SelectChannelEndPointTest.LEVEL=DEBUG \ No newline at end of file +org.eclipse.jetty.LEVEL=INFO diff --git a/jetty-plus/src/main/config/start.d/100-jetty-plus.ini b/jetty-plus/src/main/config/start.d/100-jetty-plus.ini new file mode 100644 index 00000000000..7e23d701fd0 --- /dev/null +++ b/jetty-plus/src/main/config/start.d/100-jetty-plus.ini @@ -0,0 +1,2 @@ +OPTIONS=plus +etc/jetty-plus.xml \ No newline at end of file diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java index d323fce2a72..a9e688bbfbb 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java @@ -28,9 +28,10 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.PathMap; import org.eclipse.jetty.server.HttpChannel; -import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpChannelConfig; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.UserIdentity; @@ -87,12 +88,14 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr /** * @return Returns the constraintMappings. */ + @Override public List
getConstraintMappings() { return _constraintMappings; } /* ------------------------------------------------------------ */ + @Override public Set getRoles() { return _roles; @@ -134,6 +137,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr * The constraintMappings to set. * @param roles The known roles (or null to determine them from the mappings) */ + @Override public void setConstraintMappings(List constraintMappings, Set roles) { if (isStarted()) @@ -181,6 +185,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr /** * @see org.eclipse.jetty.security.ConstraintAware#addConstraintMapping(org.eclipse.jetty.security.ConstraintMapping) */ + @Override public void addConstraintMapping(ConstraintMapping mapping) { _constraintMappings.add(mapping); @@ -198,6 +203,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr /** * @see org.eclipse.jetty.security.ConstraintAware#addRole(java.lang.String) */ + @Override public void addRole(String role) { boolean modified = _roles.add(role); @@ -325,6 +331,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr } } + @Override protected RoleInfo prepareConstraintInfo(String pathInContext, Request request) { Map mappings = _constraintMap.match(pathInContext); @@ -354,34 +361,16 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr if (dataConstraint == null || dataConstraint == UserDataConstraint.None) return true; - HttpConfiguration httpConfiguration = HttpChannel.getCurrentHttpChannel().getHttpConfiguration(); + HttpChannelConfig httpConfig = HttpChannel.getCurrentHttpChannel().getHttpChannelConfig(); - if (dataConstraint == UserDataConstraint.Integral) + if (dataConstraint == UserDataConstraint.Confidential || dataConstraint == UserDataConstraint.Integral) { - if (httpConfiguration.isIntegral(request)) - return true; - if (httpConfiguration.getIntegralPort() > 0) - { - String url = httpConfiguration.getIntegralScheme() + "://" + request.getServerName() + ":" + httpConfiguration.getIntegralPort() + request.getRequestURI(); - if (request.getQueryString() != null) - url += "?" + request.getQueryString(); - response.setContentLength(0); - response.sendRedirect(url); - } - else - response.sendError(Response.SC_FORBIDDEN,"!Integral"); - - request.setHandled(true); - return false; - } - else if (dataConstraint == UserDataConstraint.Confidential) - { - if (httpConfiguration.isConfidential(request)) + if (request.isSecure()) return true; - if (httpConfiguration.getConfidentialPort() > 0) + if (httpConfig.getSecurePort() > 0) { - String url = httpConfiguration.getConfidentialScheme() + "://" + request.getServerName() + ":" + httpConfiguration.getConfidentialPort() + String url = httpConfig.getSecureScheme() + "://" + request.getServerName() + ":" + httpConfig.getSecurePort() + request.getRequestURI(); if (request.getQueryString() != null) url += "?" + request.getQueryString(); @@ -390,7 +379,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr response.sendRedirect(url); } else - response.sendError(Response.SC_FORBIDDEN,"!Confidential"); + response.sendError(HttpStatus.FORBIDDEN_403,"!Secure"); request.setHandled(true); return false; @@ -402,6 +391,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr } + @Override protected boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo) { return constraintInfo != null && ((RoleInfo)constraintInfo).isChecked(); diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/DataConstraintsTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/DataConstraintsTest.java index 321031f0611..65c8eacc95a 100644 --- a/jetty-security/src/test/java/org/eclipse/jetty/security/DataConstraintsTest.java +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/DataConstraintsTest.java @@ -28,8 +28,8 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.security.authentication.BasicAuthenticator; import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.server.HttpServerConnectionFactory; +import org.eclipse.jetty.server.HttpChannelConfig; +import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; @@ -60,38 +60,25 @@ public class DataConstraintsTest public void startServer() { _server = new Server(); - _connector = new LocalConnector(_server); + + HttpConnectionFactory http = new HttpConnectionFactory(); + http.getHttpChannelConfig().setSecurePort(9999); + http.getHttpChannelConfig().setSecureScheme("BWTP"); + _connector = new LocalConnector(_server,http); _connector.setIdleTimeout(300000); - HttpConfiguration httpConfiguration = new HttpConfiguration(null, false); - httpConfiguration.setIntegralPort(9998); - httpConfiguration.setIntegralScheme("FTP"); - httpConfiguration.setConfidentialPort(9999); - httpConfiguration.setConfidentialScheme("SPDY"); - _connector.setDefaultConnectionFactory(new HttpServerConnectionFactory(_connector, httpConfiguration)); - _connectorS = new LocalConnector(_server); - _connectorS.setDefaultConnectionFactory(new HttpServerConnectionFactory(_connectorS, new HttpConfiguration(null,false) + HttpConnectionFactory https = new HttpConnectionFactory(); + https.getHttpChannelConfig().addCustomizer(new HttpChannelConfig.Customizer() { @Override - public void customize(Request request) throws IOException + public void customize(Connector connector, HttpChannelConfig channelConfig, Request request) { request.setScheme(HttpScheme.HTTPS.asString()); - super.customize(request); + request.setSecure(true); } - - - @Override - public boolean isIntegral(Request request) - { - return true; - } - - @Override - public boolean isConfidential(Request request) - { - return true; - } - })); + }); + + _connectorS = new LocalConnector(_server,https); _server.setConnectors(new Connector[]{_connector,_connectorS}); ContextHandler _context = new ContextHandler(); @@ -106,6 +93,7 @@ public class DataConstraintsTest _security.setHandler(new AbstractHandler() { + @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); @@ -149,8 +137,8 @@ public class DataConstraintsTest response = _connector.getResponses("GET /ctx/integral/info HTTP/1.0\r\n\r\n"); assertThat(response, containsString("HTTP/1.1 302 Found")); - assertThat(response, containsString("Location: FTP://")); - assertThat(response, containsString(":9998")); + assertThat(response, containsString("Location: BWTP://")); + assertThat(response, containsString(":9999")); response = _connectorS.getResponses("GET /ctx/integral/info HTTP/1.0\r\n\r\n"); assertThat(response, containsString("HTTP/1.1 404 Not Found")); @@ -181,7 +169,7 @@ public class DataConstraintsTest response = _connector.getResponses("GET /ctx/confid/info HTTP/1.0\r\n\r\n"); assertThat(response, containsString("HTTP/1.1 302 Found")); - assertThat(response, containsString("Location: SPDY://")); + assertThat(response, containsString("Location: BWTP://")); assertThat(response, containsString(":9999")); response = _connectorS.getResponses("GET /ctx/confid/info HTTP/1.0\r\n\r\n"); @@ -444,11 +432,14 @@ public class DataConstraintsTest { this.identityService = identityService; } + + @Override public String getName() { return "name"; } + @Override public UserIdentity login(String username, Object credentials) { if("admin".equals(username) && "password".equals(credentials)) @@ -456,20 +447,24 @@ public class DataConstraintsTest return null; } + @Override public boolean validate(UserIdentity user) { return false; } + @Override public IdentityService getIdentityService() { return identityService; } + @Override public void setIdentityService(IdentityService service) { } + @Override public void logout(UserIdentity user) { } diff --git a/jetty-server/pom.xml b/jetty-server/pom.xml index e6ff775df97..f9b4596b7ed 100644 --- a/jetty-server/pom.xml +++ b/jetty-server/pom.xml @@ -100,6 +100,12 @@ jetty-http ${project.version} + org.eclipse.jetty +jetty-xml +${project.version} +test +org.eclipse.jetty jetty-jmx diff --git a/jetty-server/src/main/config/etc/jetty-ssl.xml b/jetty-server/src/main/config/etc/jetty-ssl.xml index 0a2d63061dd..e90f04a8f7f 100644 --- a/jetty-server/src/main/config/etc/jetty-ssl.xml +++ b/jetty-server/src/main/config/etc/jetty-ssl.xml @@ -1,5 +1,5 @@ - + @@ -11,25 +11,52 @@- - - diff --git a/jetty-server/src/main/config/etc/jetty.xml b/jetty-server/src/main/config/etc/jetty.xml index 6d5a7e79409..dec2d4da994 100644 --- a/jetty-server/src/main/config/etc/jetty.xml +++ b/jetty-server/src/main/config/etc/jetty.xml @@ -29,6 +29,21 @@- + + + + /etc/keystore + + + + + + +/etc/keystore OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 OBF:1u2u1wml1z7s1z7a1wnl1u2g -+ /etc/keystore /etc/keystore OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4 + + -+ + + + + - - +- 8443 -30000 -2 -100 -+ + + ++ +- +
++ +http/1.1 ++ - +
++ ++ + 8443 +30000 ++ + + + + + + @@ -36,9 +51,16 @@https +8443 +32768 +8192 +8192 ++ ++ @@ -68,7 +90,7 @@ + + + +- +
+ + - 300000 +30000 true true 1000 -false +true false diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnectionFactory.java new file mode 100644 index 00000000000..d11f33fe818 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnectionFactory.java @@ -0,0 +1,78 @@ +// +// ======================================================================== +// 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.server; + +import org.eclipse.jetty.util.ArrayUtil; +import org.eclipse.jetty.util.component.AggregateLifeCycle; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +public abstract class AbstractConnectionFactory extends AggregateLifeCycle implements ConnectionFactory +{ + final String _protocol; + int _inputbufferSize=8192; + + protected AbstractConnectionFactory(String protocol) + { + _protocol=protocol; + } + + @Override + public String getProtocol() + { + return _protocol; + } + + public int getInputBufferSize() + { + return _inputbufferSize; + } + + public void setInputBufferSize(int size) + { + _inputbufferSize=size; + } + + @Override + public String toString() + { + return String.format("%s@%x{%s}",this.getClass().getSimpleName(),hashCode(),getProtocol()); + } + + public static ConnectionFactory[] getFactories(SslContextFactory sslContextFactory, ConnectionFactory... factories) + { + factories=ArrayUtil.removeNulls(factories); + + if (sslContextFactory==null) + return factories; + + for (ConnectionFactory factory : factories) + { + if (factory instanceof HttpChannelConfig.ConnectionFactory) + { + HttpChannelConfig config = ((HttpChannelConfig.ConnectionFactory)factory).getHttpChannelConfig(); + if (config.getCustomizer(SecureRequestCustomizer.class)==null) + config.addCustomizer(new SecureRequestCustomizer()); + } + } + return ArrayUtil.prependToArray(new SslConnectionFactory(sslContextFactory,factories[0].getProtocol()),factories,ConnectionFactory.class); + + } + + +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java index 4afd4bdc0ee..f5b83019ddb 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java @@ -20,7 +20,10 @@ package org.eclipse.jetty.server; import java.io.IOException; import java.net.Socket; +import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; @@ -37,51 +40,63 @@ import org.eclipse.jetty.util.component.AggregateLifeCycle; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.util.thread.TimerScheduler; /** *Partial implementation of {@link Connector}
+ * + *+ * The connector keeps a collection of {@link ConnectionFactory} instances, each of which are known by their + * protocol name. The protocol name may be a real protocol (eg http/1.1 or spdy/3) or it may be a private name + * that represents a special connection factory. For example, the name "SSL-http/1.1" is used for + * an {@link SslConnectionFactory} that has been instantiated with the {@link HttpConnectionFactory} as it's + * next protocol. + *
+ * If NPN is used to select the real protocol used by an SSL connection, then the name "SSL-NPN" is used, + * which represents a {@link SslConnectionFactory} with a NPNConnectionFactory as the next protocol. Once + * the NPN connection is established, it will get the next protocol from the NPN extension and then call + * {@link #getConnectionFactory(String)} to get the next connection factory. + * */ @ManagedObject("Abstract implementation of the Connector Interface") public abstract class AbstractConnector extends AggregateLifeCycle implements Connector, Dumpable { protected final Logger LOG = Log.getLogger(getClass()); // Order is important on server side, so we use a LinkedHashMap - private final Map
factories = new LinkedHashMap<>(); + private final Map _factories = new LinkedHashMap<>(); private final Statistics _stats = new ConnectorStatistics(); private final Server _server; - private final SslContextFactory _sslContextFactory; private final Executor _executor; private final Scheduler _scheduler; private final ByteBufferPool _byteBufferPool; private final Thread[] _acceptors; private volatile CountDownLatch _stopping; - private volatile long _idleTimeout = 200000; - private volatile ConnectionFactory defaultConnectionFactory; + private long _idleTimeout = 200000; + private String _defaultProtocol; + private ConnectionFactory _defaultConnectionFactory; /** * @param server The server this connector will be added to. Must not be null. + * @param factory TODO + * @param sslContextFactory the SSL context factory to make this connector SSL enabled, or null * @param executor An executor for this connector or null to use the servers executor * @param scheduler A scheduler for this connector or null to use the servers scheduler * @param pool A buffer pool for this connector or null to use a default {@link ByteBufferPool} - * @param sslContextFactory the SSL context factory to make this connector SSL enabled, or null * @param acceptors the number of acceptor threads to use, or 0 for a default value. */ public AbstractConnector( Server server, Executor executor, Scheduler scheduler, - ByteBufferPool pool, - SslContextFactory sslContextFactory, - int acceptors) + ByteBufferPool pool, + int acceptors, + ConnectionFactory... factories) { _server=server; _executor=executor!=null?executor:_server.getThreadPool(); _scheduler=scheduler!=null?scheduler:new TimerScheduler(); _byteBufferPool = pool!=null?pool:new ArrayByteBufferPool(); - _sslContextFactory = sslContextFactory; addBean(_server,false); addBean(_executor); @@ -89,9 +104,11 @@ public abstract class AbstractConnector extends AggregateLifeCycle implements Co unmanage(_executor); // inherited from server addBean(_scheduler); addBean(_byteBufferPool); - addBean(_sslContextFactory); addBean(_stats,true); + for (ConnectionFactory factory:factories) + addConnectionFactory(factory); + if (acceptors<=0) acceptors=Math.max(1,(Runtime.getRuntime().availableProcessors()) / 4); if (acceptors > 2 * Runtime.getRuntime().availableProcessors()) @@ -123,12 +140,6 @@ public abstract class AbstractConnector extends AggregateLifeCycle implements Co return _byteBufferPool; } - @Override - public SslContextFactory getSslContextFactory() - { - return _sslContextFactory; - } - @Override public long getIdleTimeout() { @@ -162,10 +173,13 @@ public abstract class AbstractConnector extends AggregateLifeCycle implements Co return _acceptors.length; } - @Override protected void doStart() throws Exception { + _defaultConnectionFactory = getConnectionFactory(_defaultProtocol); + if(_defaultConnectionFactory==null) + throw new IllegalStateException("No protocol factory for default protocol: "+_defaultProtocol); + super.doStart(); _stopping=new CountDownLatch(_acceptors.length); @@ -232,56 +246,97 @@ public abstract class AbstractConnector extends AggregateLifeCycle implements Co return isRunning(); } + @Override public ConnectionFactory getConnectionFactory(String protocol) { - synchronized (factories) + synchronized (_factories) { - return factories.get(protocol); + return _factories.get(protocol.toLowerCase()); } } - public ConnectionFactory putConnectionFactory(String protocol, ConnectionFactory factory) + @Override + public T getConnectionFactory(Class factoryType) { - synchronized (factories) + synchronized (_factories) { - return factories.put(protocol, factory); + for (ConnectionFactory f : _factories.values()) + if (factoryType.isAssignableFrom(f.getClass())) + return (T)f; + return null; + } + } + + public void addConnectionFactory(ConnectionFactory factory) + { + synchronized (_factories) + { + ConnectionFactory old=_factories.remove(factory.getProtocol()); + if (old!=null) + removeBean(old); + _factories.put(factory.getProtocol().toLowerCase(), factory); + addBean(factory); + if (_defaultProtocol==null) + _defaultProtocol=factory.getProtocol(); } } public ConnectionFactory removeConnectionFactory(String protocol) { - synchronized (factories) + synchronized (_factories) { - return factories.remove(protocol); + ConnectionFactory factory= _factories.remove(protocol.toLowerCase()); + removeBean(factory); + return factory; } } - public Map getConnectionFactories() + @Override + public Collection getConnectionFactories() { - synchronized (factories) + synchronized (_factories) { - return new LinkedHashMap<>(factories); + return _factories.values(); + } + } + + @Override + public List getProtocols() + { + synchronized (_factories) + { + return new ArrayList<>(_factories.keySet()); } } public void clearConnectionFactories() { - synchronized (factories) + synchronized (_factories) { - factories.clear(); + _factories.clear(); } } + public String getDefaultProtocol() + { + return _defaultProtocol; + } + + public void setDefaultProtocol(String defaultProtocol) + { + _defaultProtocol = defaultProtocol.toLowerCase(); + if (isRunning()) + _defaultConnectionFactory=getConnectionFactory(_defaultProtocol); + } + + @Override public ConnectionFactory getDefaultConnectionFactory() { - return defaultConnectionFactory; + if (isStarted()) + return _defaultConnectionFactory; + return getConnectionFactory(_defaultProtocol); } - - public void setDefaultConnectionFactory(ConnectionFactory defaultConnectionFactory) - { - this.defaultConnectionFactory = defaultConnectionFactory; - } - + private class Acceptor implements Runnable { private final int _acceptor; @@ -358,4 +413,13 @@ public abstract class AbstractConnector extends AggregateLifeCycle implements Co { return _scheduler; } + + @Override + public String toString() + { + return String.format("%s@%x{%s}", + getClass().getSimpleName(), + hashCode(), + getDefaultProtocol()); + } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNetworkConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNetworkConnector.java index 21fafc7a50c..59070233d33 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNetworkConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNetworkConnector.java @@ -23,7 +23,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.Future; import org.eclipse.jetty.io.ByteBufferPool; -import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.Scheduler; /** @@ -34,9 +33,9 @@ public abstract class AbstractNetworkConnector extends AbstractConnector impleme private volatile String _host; private volatile int _port = 0; - public AbstractNetworkConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, SslContextFactory sslContextFactory, int acceptors) + public AbstractNetworkConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, ConnectionFactory... factories) { - super(server, executor, scheduler, pool, sslContextFactory, acceptors); + super(server,executor,scheduler,pool,acceptors,factories); } public void setHost(String host) @@ -110,8 +109,8 @@ public abstract class AbstractNetworkConnector extends AbstractConnector impleme @Override public String toString() { - return String.format("%s@%s:%d", - getClass().getSimpleName(), + return String.format("%s{%s:%d}", + super.toString(), getHost() == null ? "0.0.0.0" : getHost(), getLocalPort() <= 0 ? getPort() : getLocalPort()); } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ByteBufferHttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ByteBufferHttpInput.java index 680deb511f6..8d81c83c7b5 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ByteBufferHttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ByteBufferHttpInput.java @@ -42,6 +42,5 @@ public class ByteBufferHttpInput extends HttpInput @Override protected void onContentConsumed(ByteBuffer item) { - item.clear(); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectionFactory.java index daad1fa5c9c..b129baf62f9 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectionFactory.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ConnectionFactory.java @@ -18,7 +18,6 @@ package org.eclipse.jetty.server; -import java.nio.channels.SocketChannel; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; @@ -28,12 +27,14 @@ import org.eclipse.jetty.io.EndPoint; */ public interface ConnectionFactory { + public String getProtocol(); + /** * Creates a new {@link Connection} with the given parameters
- * @param channel the {@link SocketChannel} associated with the connection + * @param connector The {@link Connector} creating this connection * @param endPoint the {@link EndPoint} associated with the connection - * @param attachment the attachment associated with the connection * @return a new {@link Connection} */ - public Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment); + public Connection newConnection(Connector connector, EndPoint endPoint); + } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java index 908abd0db07..c37f3fd5463 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Connector.java @@ -18,6 +18,8 @@ package org.eclipse.jetty.server; +import java.util.Collection; +import java.util.List; import java.util.concurrent.Executor; import org.eclipse.jetty.io.ByteBufferPool; @@ -25,7 +27,6 @@ import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.Graceful; import org.eclipse.jetty.util.component.LifeCycle; -import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.Scheduler; /** @@ -57,10 +58,22 @@ public interface Connector extends LifeCycle, Graceful public ByteBufferPool getByteBufferPool(); /** - * @return the {@link SslContextFactory} associated with this {@link Connector} + * @return the {@link ConnectionFactory} associated with the protocol name */ - public SslContextFactory getSslContextFactory(); + public ConnectionFactory getConnectionFactory(String nextProtocol); + + publicT getConnectionFactory(Class factoryType); + + /** + * @return the default {@link ConnectionFactory} associated with the default protocol name + */ + public ConnectionFactory getDefaultConnectionFactory(); + + public Collection getConnectionFactories(); + + public List getProtocols(); + /** * @return the dle timeout for connections in milliseconds */ @@ -199,4 +212,6 @@ public interface Connector extends LifeCycle, Graceful */ public void connectionClosed(long duration, int messagesIn, int messagesOut); } + + } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java index 90954666c6f..9919323211b 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; + import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java new file mode 100644 index 00000000000..8f9f5825d76 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java @@ -0,0 +1,280 @@ +// +// ======================================================================== +// 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.server; + +import java.net.InetSocketAddress; + +import javax.servlet.ServletRequest; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.server.HttpChannelConfig.Customizer; + +public class ForwardedRequestCustomizer implements Customizer +{ + private String _hostHeader; + private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString(); + private String _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString(); + private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString(); + private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString(); + private String _forwardedCipherSuiteHeader; + private String _forwardedSslSessionIdHeader; + + + /* ------------------------------------------------------------ */ + public String getHostHeader() + { + return _hostHeader; + } + + /* ------------------------------------------------------------ */ + /** + * Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}. + * This value is only used if {@link #isForwarded()} is true. + * + * @param hostHeader + * The value of the host header to force. + */ + public void setHostHeader(String hostHeader) + { + _hostHeader = hostHeader; + } + + /* ------------------------------------------------------------ */ + /* + * + * @see #setForwarded(boolean) + */ + public String getForwardedHostHeader() + { + return _forwardedHostHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @param forwardedHostHeader + * The header name for forwarded hosts (default x-forwarded-host) + * @see #setForwarded(boolean) + */ + public void setForwardedHostHeader(String forwardedHostHeader) + { + _forwardedHostHeader = forwardedHostHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @return the header name for forwarded server. + * @see #setForwarded(boolean) + */ + public String getForwardedServerHeader() + { + return _forwardedServerHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @param forwardedServerHeader + * The header name for forwarded server (default x-forwarded-server) + * @see #setForwarded(boolean) + */ + public void setForwardedServerHeader(String forwardedServerHeader) + { + _forwardedServerHeader = forwardedServerHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @return the forwarded for header + * @see #setForwarded(boolean) + */ + public String getForwardedForHeader() + { + return _forwardedForHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @param forwardedRemoteAddressHeader + * The header name for forwarded for (default x-forwarded-for) + * @see #setForwarded(boolean) + */ + public void setForwardedForHeader(String forwardedRemoteAddressHeader) + { + _forwardedForHeader = forwardedRemoteAddressHeader; + } + + /* ------------------------------------------------------------ */ + /** + * Get the forwardedProtoHeader. + * + * @return the forwardedProtoHeader (default X-Forwarded-For) + * @see #setForwarded(boolean) + */ + public String getForwardedProtoHeader() + { + return _forwardedProtoHeader; + } + + /* ------------------------------------------------------------ */ + /** + * Set the forwardedProtoHeader. + * + * @param forwardedProtoHeader + * the forwardedProtoHeader to set (default X-Forwarded-For) + * @see #setForwarded(boolean) + */ + public void setForwardedProtoHeader(String forwardedProtoHeader) + { + _forwardedProtoHeader = forwardedProtoHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @return The header name holding a forwarded cipher suite (default null) + */ + public String getForwardedCipherSuiteHeader() + { + return _forwardedCipherSuiteHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @param forwardedCipherSuite + * The header name holding a forwarded cipher suite (default null) + */ + public void setForwardedCipherSuiteHeader(String forwardedCipherSuite) + { + _forwardedCipherSuiteHeader = forwardedCipherSuite; + } + + /* ------------------------------------------------------------ */ + /** + * @return The header name holding a forwarded SSL Session ID (default null) + */ + public String getForwardedSslSessionIdHeader() + { + return _forwardedSslSessionIdHeader; + } + + /* ------------------------------------------------------------ */ + /** + * @param forwardedSslSessionId + * The header name holding a forwarded SSL Session ID (default null) + */ + public void setForwardedSslSessionIdHeader(String forwardedSslSessionId) + { + _forwardedSslSessionIdHeader = forwardedSslSessionId; + } + + /* ------------------------------------------------------------ */ + @Override + public void customize(Connector connector, HttpChannelConfig config, Request request) + { + HttpFields httpFields = request.getHttpFields(); + + // Do SSL first + if (getForwardedCipherSuiteHeader()!=null) + { + String cipher_suite=httpFields.getStringField(getForwardedCipherSuiteHeader()); + if (cipher_suite!=null) + request.setAttribute("javax.servlet.request.cipher_suite",cipher_suite); + } + if (getForwardedSslSessionIdHeader()!=null) + { + String ssl_session_id=httpFields.getStringField(getForwardedSslSessionIdHeader()); + if(ssl_session_id!=null) + { + request.setAttribute("javax.servlet.request.ssl_session_id", ssl_session_id); + request.setScheme(HttpScheme.HTTPS.asString()); + } + } + + // Retrieving headers from the request + String forwardedHost = getLeftMostFieldValue(httpFields,getForwardedHostHeader()); + String forwardedServer = getLeftMostFieldValue(httpFields,getForwardedServerHeader()); + String forwardedFor = getLeftMostFieldValue(httpFields,getForwardedForHeader()); + String forwardedProto = getLeftMostFieldValue(httpFields,getForwardedProtoHeader()); + + if (_hostHeader != null) + { + // Update host header + httpFields.put(HttpHeader.HOST.toString(),_hostHeader); + request.setServerName(null); + request.setServerPort(-1); + request.getServerName(); + } + else if (forwardedHost != null) + { + // Update host header + httpFields.put(HttpHeader.HOST.toString(),forwardedHost); + request.setServerName(null); + request.setServerPort(-1); + request.getServerName(); + } + else if (forwardedServer != null) + { + // Use provided server name + request.setServerName(forwardedServer); + } + + if (forwardedFor != null) + { + request.setRemoteAddr(new InetSocketAddress(forwardedFor,request.getRemotePort())); + } + + if (forwardedProto != null) + { + request.setScheme(forwardedProto); + if (forwardedProto.equals(config.getSecureScheme())) + request.setSecure(true); + } + } + + /* ------------------------------------------------------------ */ + protected String getLeftMostFieldValue(HttpFields fields, String header) + { + if (header == null) + return null; + + String headerValue = fields.getStringField(header); + + if (headerValue == null) + return null; + + int commaIndex = headerValue.indexOf(','); + + if (commaIndex == -1) + { + // Single value + return headerValue; + } + + // The left-most value is the farthest downstream client + return headerValue.substring(0,commaIndex); + } + + + /* ------------------------------------------------------------ */ + @Override + public String toString() + { + return String.format("%s@%x",this.getClass().getSimpleName(),hashCode()); + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java index b2715a9c634..1a6205f59ef 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.server; import java.io.IOException; + import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index 29fac7f48a2..24536935237 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -23,6 +23,7 @@ import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; + import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; @@ -63,14 +64,14 @@ import org.eclipse.jetty.util.thread.Scheduler; public class HttpChannel implements HttpParser.RequestHandler , Runnable { private static final Logger LOG = Log.getLogger(HttpChannel.class); - private static final ThreadLocal __currentChannel = new ThreadLocal<>(); + private static final ThreadLocal > __currentChannel = new ThreadLocal<>(); - public static HttpChannel getCurrentHttpChannel() + public static HttpChannel> getCurrentHttpChannel() { return __currentChannel.get(); } - protected static void setCurrentHttpChannel(HttpChannel channel) + protected static void setCurrentHttpChannel(HttpChannel> channel) { __currentChannel.set(channel); } @@ -78,7 +79,7 @@ public class HttpChannel implements HttpParser.RequestHandler , Runnable private final AtomicBoolean _committed = new AtomicBoolean(); private final AtomicInteger _requests = new AtomicInteger(); private final Connector _connector; - private final HttpConfiguration _configuration; + private final HttpChannelConfig _configuration; private final EndPoint _endPoint; private final HttpTransport _transport; private final HttpURI _uri; @@ -90,7 +91,7 @@ public class HttpChannel implements HttpParser.RequestHandler , Runnable private boolean _expect100Continue = false; private boolean _expect102Processing = false; - public HttpChannel(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput input) + public HttpChannel(Connector connector, HttpChannelConfig configuration, EndPoint endPoint, HttpTransport transport, HttpInput input) { _connector = connector; _configuration = configuration; @@ -131,7 +132,7 @@ public class HttpChannel implements HttpParser.RequestHandler , Runnable return _connector.getByteBufferPool(); } - public HttpConfiguration getHttpConfiguration() + public HttpChannelConfig getHttpChannelConfig() { return _configuration; } @@ -237,8 +238,11 @@ public class HttpChannel implements HttpParser.RequestHandler , Runnable if (_state.isInitial()) { + _request.setTimeStamp(System.currentTimeMillis()); _request.setDispatcherType(DispatcherType.REQUEST); - getHttpConfiguration().customize(_request); + + for (HttpChannelConfig.Customizer customizer : _configuration.getCustomizers()) + customizer.customize(getConnector(),_configuration,_request); getServer().handle(this); } else @@ -321,7 +325,7 @@ public class HttpChannel implements HttpParser.RequestHandler , Runnable if (_state.isSuspended()) { HttpFields fields = new HttpFields(); - ResponseInfo info = new ResponseInfo(_request.getHttpVersion(), fields, 0, Response.SC_INTERNAL_SERVER_ERROR, null, _request.isHead()); + ResponseInfo info = new ResponseInfo(_request.getHttpVersion(), fields, 0, HttpStatus.INTERNAL_SERVER_ERROR_500, null, _request.isHead()); boolean committed = commitResponse(info, null, true); if (!committed) LOG.warn("Could not send response error 500: "+x); @@ -515,7 +519,7 @@ public class HttpChannel implements HttpParser.RequestHandler , Runnable if (LOG.isDebugEnabled()) LOG.debug("{} content {}", this, item); @SuppressWarnings("unchecked") - HttpInput input = _request.getHttpInput(); + HttpInput input = (HttpInput )_request.getHttpInput(); input.content(item); return true; } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelConfig.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelConfig.java new file mode 100644 index 00000000000..2ef9016b82c --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelConfig.java @@ -0,0 +1,124 @@ +// +// ======================================================================== +// 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.server; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.jetty.http.HttpScheme; + +public class HttpChannelConfig +{ + private List _customizers=new CopyOnWriteArrayList<>(); + private int _outputBufferSize=32*1024; + private int _requestHeaderSize=8*1024; + private int _responseHeaderSize=8*1024; + private int _securePort; + private String _secureScheme = HttpScheme.HTTPS.asString(); + + public interface Customizer + { + public void customize(Connector connector, HttpChannelConfig channelConfig, Request request); + } + + public interface ConnectionFactory + { + HttpChannelConfig getHttpChannelConfig(); + } + + public void addCustomizer(Customizer customizer) + { + _customizers.add(customizer); + } + + public List getCustomizers() + { + return _customizers; + } + + public T getCustomizer(Class type) + { + for (Customizer c : _customizers) + if (type.isAssignableFrom(c.getClass())) + return (T)c; + return null; + } + + public int getOutputBufferSize() + { + return _outputBufferSize; + } + + public int getRequestHeaderSize() + { + return _requestHeaderSize; + } + + public int getResponseHeaderSize() + { + return _responseHeaderSize; + } + + public int getSecurePort() + { + return _securePort; + } + + public String getSecureScheme() + { + return _secureScheme; + } + + public void setCustomizers(List customizers) + { + _customizers.clear(); + _customizers.addAll(customizers); + } + + public void setOutputBufferSize(int responseBufferSize) + { + _outputBufferSize = responseBufferSize; + } + + public void setRequestHeaderSize(int requestHeaderSize) + { + _requestHeaderSize = requestHeaderSize; + } + + public void setResponseHeaderSize(int responseHeaderSize) + { + _responseHeaderSize = responseHeaderSize; + } + + public void setSecurePort(int confidentialPort) + { + _securePort = confidentialPort; + } + + public void setSecureScheme(String confidentialScheme) + { + _secureScheme = confidentialScheme; + } + + @Override + public String toString() + { + return String.format("%s@%x{%d,%d/%d,%s://:%d,%s}",this.getClass().getSimpleName(),hashCode(),_outputBufferSize,_requestHeaderSize,_responseHeaderSize,_secureScheme,_securePort,_customizers); + } +} diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java index 83e7f0ee079..04c3b7008b1 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.server; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; + import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java deleted file mode 100644 index 266b22f3e8b..00000000000 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java +++ /dev/null @@ -1,556 +0,0 @@ -// -// ======================================================================== -// 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.server; - -import java.io.IOException; -import java.net.InetSocketAddress; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLSession; -import javax.servlet.ServletRequest; - -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpScheme; -import org.eclipse.jetty.io.ssl.SslConnection; -import org.eclipse.jetty.server.ssl.SslCertificates; -import org.eclipse.jetty.util.component.AggregateLifeCycle; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.util.ssl.SslContextFactory; - - -public class HttpConfiguration extends AggregateLifeCycle -{ - static final Logger LOG = Log.getLogger(HttpConfiguration.class); - - private final SslContextFactory _sslContextFactory; - private final boolean _ssl; - - private String _integralScheme = HttpScheme.HTTPS.asString(); - private int _integralPort = 0; - private String _confidentialScheme = HttpScheme.HTTPS.asString(); - private int _confidentialPort = 0; - private boolean _forwarded; - private String _hostHeader; - private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString(); - private String _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString(); - private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString(); - private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString(); - private String _forwardedCipherSuiteHeader; - private String _forwardedSslSessionIdHeader; - private int _requestHeaderSize=8*1024; - private int _requestBufferSize=16*1024; - private int _responseHeaderSize=8*1024; - private int _responseBufferSize=32*1024; - - public HttpConfiguration(SslContextFactory sslContextFactory,boolean ssl) - { - _sslContextFactory=sslContextFactory!=null?sslContextFactory:ssl?new SslContextFactory(SslContextFactory.DEFAULT_KEYSTORE_PATH):null; - _ssl=ssl; - if (_sslContextFactory!=null) - addBean(_sslContextFactory,sslContextFactory==null); - } - - public SslContextFactory getSslContextFactory() - { - return _sslContextFactory; - } - - public boolean isSecure() - { - return _ssl; - } - - public int getRequestHeaderSize() - { - return _requestHeaderSize; - } - - public void setRequestHeaderSize(int requestHeaderSize) - { - _requestHeaderSize = requestHeaderSize; - } - - public int getRequestBufferSize() - { - return _requestBufferSize; - } - - public void setRequestBufferSize(int requestBufferSize) - { - _requestBufferSize = requestBufferSize; - } - - public int getResponseHeaderSize() - { - return _responseHeaderSize; - } - - public void setResponseHeaderSize(int responseHeaderSize) - { - _responseHeaderSize = responseHeaderSize; - } - - public int getResponseBufferSize() - { - return _responseBufferSize; - } - - public void setResponseBufferSize(int responseBufferSize) - { - _responseBufferSize = responseBufferSize; - } - - /* ------------------------------------------------------------ */ - /** - * Allow the Listener a chance to customise the request. before the server - * does its stuff.
- * This allows the required attributes to be set for SSL requests.
- * The requirements of the Servlet specs are: - *- *
- */ - public void customize(Request request) throws IOException - { - if (isSecure()) - { - request.setScheme(HttpScheme.HTTPS.asString()); - SslConnection.DecryptedEndPoint ssl_endp = (SslConnection.DecryptedEndPoint)request.getHttpChannel().getEndPoint(); - SslConnection sslConnection = ssl_endp.getSslConnection(); - SSLEngine sslEngine=sslConnection.getSSLEngine(); - SslCertificates.customize(sslEngine,request); - } - - request.setTimeStamp(System.currentTimeMillis()); - if (isForwarded()) - checkForwardedHeaders(request); - } - - /* ------------------------------------------------------------ */ - protected void checkForwardedHeaders(Request request) throws IOException - { - HttpFields httpFields = request.getHttpFields(); - - // Do SSL first - if (getForwardedCipherSuiteHeader()!=null) - { - String cipher_suite=httpFields.getStringField(getForwardedCipherSuiteHeader()); - if (cipher_suite!=null) - request.setAttribute("javax.servlet.request.cipher_suite",cipher_suite); - } - if (getForwardedSslSessionIdHeader()!=null) - { - String ssl_session_id=httpFields.getStringField(getForwardedSslSessionIdHeader()); - if(ssl_session_id!=null) - { - request.setAttribute("javax.servlet.request.ssl_session_id", ssl_session_id); - request.setScheme(HttpScheme.HTTPS.asString()); - } - } - - // Retrieving headers from the request - String forwardedHost = getLeftMostFieldValue(httpFields,getForwardedHostHeader()); - String forwardedServer = getLeftMostFieldValue(httpFields,getForwardedServerHeader()); - String forwardedFor = getLeftMostFieldValue(httpFields,getForwardedForHeader()); - String forwardedProto = getLeftMostFieldValue(httpFields,getForwardedProtoHeader()); - - if (_hostHeader != null) - { - // Update host header - httpFields.put(HttpHeader.HOST.toString(),_hostHeader); - request.setServerName(null); - request.setServerPort(-1); - request.getServerName(); - } - else if (forwardedHost != null) - { - // Update host header - httpFields.put(HttpHeader.HOST.toString(),forwardedHost); - request.setServerName(null); - request.setServerPort(-1); - request.getServerName(); - } - else if (forwardedServer != null) - { - // Use provided server name - request.setServerName(forwardedServer); - } - - if (forwardedFor != null) - { - request.setRemoteAddr(new InetSocketAddress(forwardedFor,request.getRemotePort())); - } - - if (forwardedProto != null) - { - request.setScheme(forwardedProto); - } - } - - /* ------------------------------------------------------------ */ - protected String getLeftMostFieldValue(HttpFields fields, String header) - { - if (header == null) - return null; - - String headerValue = fields.getStringField(header); - - if (headerValue == null) - return null; - - int commaIndex = headerValue.indexOf(','); - - if (commaIndex == -1) - { - // Single value - return headerValue; - } - - // The left-most value is the farthest downstream client - return headerValue.substring(0,commaIndex); - } - - /* ------------------------------------------------------------ */ - /* - * @see org.eclipse.jetty.server.Connector#getConfidentialPort() - */ - public int getConfidentialPort() - { - return _confidentialPort; - } - - /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ - /* - * @see org.eclipse.jetty.server.Connector#getConfidentialScheme() - */ - public String getConfidentialScheme() - { - return _confidentialScheme; - } - - /* ------------------------------------------------------------ */ - /** - * The request is integral IFF it is secure AND the server port - * matches any configured {@link #getIntegralPort()}. - * This allows separation of listeners providing INTEGRAL versus - * CONFIDENTIAL constraints, such as one SSL listener configured to require - * client certs providing CONFIDENTIAL, whereas another SSL listener not - * requiring client certs providing mere INTEGRAL constraints. - *- an attribute named "javax.servlet.request.ssl_session_id" of type - * String (since Servlet Spec 3.0).
- *- an attribute named "javax.servlet.request.cipher_suite" of type - * String.
- *- an attribute named "javax.servlet.request.key_size" of type Integer.
- *- an attribute named "javax.servlet.request.X509Certificate" of type - * java.security.cert.X509Certificate[]. This is an array of objects of type - * X509Certificate, the order of this array is defined as being in ascending - * order of trust. The first certificate in the chain is the one set by the - * client, the next is the one used to authenticate the first, and so on. - *
- *- * The request is secure if it is SSL or it {@link #isForwarded()} is true - * and the scheme matches {@link #getIntegralScheme()()} - */ - public boolean isIntegral(Request request) - { - boolean https = isSecure() || _forwarded && _integralScheme.equalsIgnoreCase(request.getScheme()); - int iPort=getIntegralPort(); - boolean port = iPort<=0||iPort==request.getServerPort(); - return https && port; - } - - /* ------------------------------------------------------------ */ - /* - * @see org.eclipse.jetty.server.Connector#getConfidentialPort() - */ - public int getIntegralPort() - { - return _integralPort; - } - - /* ------------------------------------------------------------ */ - /* - * @see org.eclipse.jetty.server.Connector#getIntegralScheme() - */ - public String getIntegralScheme() - { - return _integralScheme; - } - - /* ------------------------------------------------------------ */ - /** - * The request is confidential IFF it is secure AND the server port - * matches any configured {@link #getConfidentialPort()}. - * This allows separation of listeners providing INTEGRAL versus - * CONFIDENTIAL constraints, such as one SSL listener configured to require - * client certs providing CONFIDENTIAL, whereas another SSL listener not - * requiring client certs providing mere INTEGRAL constraints. - *
- * The request is secure if it is SSL or it {@link #isForwarded()} is true - * and the scheme matches {@link #getConfidentialScheme()} - */ - public boolean isConfidential(Request request) - { - boolean https = isSecure() || _forwarded && _confidentialScheme.equalsIgnoreCase(request.getScheme()); - int confidentialPort=getConfidentialPort(); - boolean port = confidentialPort<=0||confidentialPort==request.getServerPort(); - return https && port; - } - - /* ------------------------------------------------------------ */ - /** - * @param confidentialPort - * The confidentialPort to set. - */ - public void setConfidentialPort(int confidentialPort) - { - _confidentialPort = confidentialPort; - } - - /* ------------------------------------------------------------ */ - /** - * @param confidentialScheme - * The confidentialScheme to set. - */ - public void setConfidentialScheme(String confidentialScheme) - { - _confidentialScheme = confidentialScheme; - } - - /* ------------------------------------------------------------ */ - /** - * @param integralPort - * The integralPort to set. - */ - public void setIntegralPort(int integralPort) - { - _integralPort = integralPort; - } - - /* ------------------------------------------------------------ */ - /** - * @param integralScheme - * The integralScheme to set. - */ - public void setIntegralScheme(String integralScheme) - { - _integralScheme = integralScheme; - } - - /* ------------------------------------------------------------ */ - /** - * Is reverse proxy handling on? - * - * @return true if this connector is checking the x-forwarded-for/host/server headers - */ - public boolean isForwarded() - { - return _forwarded; - } - - /* ------------------------------------------------------------ */ - /** - * Set reverse proxy handling. If set to true, then the X-Forwarded headers (or the headers set in their place) are looked for to set the request protocol, - * host, server and client ip. - * - * @param check true if this connector is checking the x-forwarded-for/host/server headers - * @see #setForwardedForHeader(String) - * @see #setForwardedHostHeader(String) - * @see #setForwardedProtoHeader(String) - * @see #setForwardedServerHeader(String) - */ - public void setForwarded(boolean check) - { - if (check) - LOG.debug("{} is forwarded",this); - _forwarded = check; - } - - /* ------------------------------------------------------------ */ - public String getHostHeader() - { - return _hostHeader; - } - - /* ------------------------------------------------------------ */ - /** - * Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}. - * This value is only used if {@link #isForwarded()} is true. - * - * @param hostHeader - * The value of the host header to force. - */ - public void setHostHeader(String hostHeader) - { - _hostHeader = hostHeader; - } - - /* ------------------------------------------------------------ */ - /* - * - * @see #setForwarded(boolean) - */ - public String getForwardedHostHeader() - { - return _forwardedHostHeader; - } - - /* ------------------------------------------------------------ */ - /** - * @param forwardedHostHeader - * The header name for forwarded hosts (default x-forwarded-host) - * @see #setForwarded(boolean) - */ - public void setForwardedHostHeader(String forwardedHostHeader) - { - _forwardedHostHeader = forwardedHostHeader; - } - - /* ------------------------------------------------------------ */ - /** - * @return the header name for forwarded server. - * @see #setForwarded(boolean) - */ - public String getForwardedServerHeader() - { - return _forwardedServerHeader; - } - - /* ------------------------------------------------------------ */ - /** - * @param forwardedServerHeader - * The header name for forwarded server (default x-forwarded-server) - * @see #setForwarded(boolean) - */ - public void setForwardedServerHeader(String forwardedServerHeader) - { - _forwardedServerHeader = forwardedServerHeader; - } - - /* ------------------------------------------------------------ */ - /** - * @return the forwarded for header - * @see #setForwarded(boolean) - */ - public String getForwardedForHeader() - { - return _forwardedForHeader; - } - - /* ------------------------------------------------------------ */ - /** - * @param forwardedRemoteAddressHeader - * The header name for forwarded for (default x-forwarded-for) - * @see #setForwarded(boolean) - */ - public void setForwardedForHeader(String forwardedRemoteAddressHeader) - { - _forwardedForHeader = forwardedRemoteAddressHeader; - } - - /* ------------------------------------------------------------ */ - /** - * Get the forwardedProtoHeader. - * - * @return the forwardedProtoHeader (default X-Forwarded-For) - * @see #setForwarded(boolean) - */ - public String getForwardedProtoHeader() - { - return _forwardedProtoHeader; - } - - /* ------------------------------------------------------------ */ - /** - * Set the forwardedProtoHeader. - * - * @param forwardedProtoHeader - * the forwardedProtoHeader to set (default X-Forwarded-For) - * @see #setForwarded(boolean) - */ - public void setForwardedProtoHeader(String forwardedProtoHeader) - { - _forwardedProtoHeader = forwardedProtoHeader; - } - - /* ------------------------------------------------------------ */ - /** - * @return The header name holding a forwarded cipher suite (default null) - */ - public String getForwardedCipherSuiteHeader() - { - return _forwardedCipherSuiteHeader; - } - - /* ------------------------------------------------------------ */ - /** - * @param forwardedCipherSuite - * The header name holding a forwarded cipher suite (default null) - */ - public void setForwardedCipherSuiteHeader(String forwardedCipherSuite) - { - _forwardedCipherSuiteHeader = forwardedCipherSuite; - } - - /* ------------------------------------------------------------ */ - /** - * @return The header name holding a forwarded SSL Session ID (default null) - */ - public String getForwardedSslSessionIdHeader() - { - return _forwardedSslSessionIdHeader; - } - - /* ------------------------------------------------------------ */ - /** - * @param forwardedSslSessionId - * The header name holding a forwarded SSL Session ID (default null) - */ - public void setForwardedSslSessionIdHeader(String forwardedSslSessionId) - { - _forwardedSslSessionIdHeader = forwardedSslSessionId; - } - - - /* ------------------------------------------------------------ */ - @Override - protected void doStart() throws Exception - { - if (_sslContextFactory!=null) - { - _sslContextFactory.checkKeyStore(); - - super.doStart(); - - SSLEngine sslEngine = _sslContextFactory.newSSLEngine(); - - sslEngine.setUseClientMode(false); - - SSLSession sslSession = sslEngine.getSession(); - - if (getRequestHeaderSize()
__currentConnection = new ThreadLocal<>(); - private final HttpConfiguration _configuration; + private final HttpChannelConfig _config; private final Connector _connector; - private final ByteBufferPool _bufferPool; // TODO: remove field, use a _connector.getByteBufferPool() + private final ByteBufferPool _bufferPool; private final HttpGenerator _generator; private final HttpChannelOverHttp _channel; private final HttpParser _parser; - private ByteBuffer _requestBuffer = null; - private ByteBuffer _chunk = null; + private volatile ByteBuffer _requestBuffer = null; + private volatile ByteBuffer _chunk = null; public static HttpConnection getCurrentConnection() { @@ -67,12 +67,17 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http { __currentConnection.set(connection); } + + public HttpChannelConfig getHttpChannelConfig() + { + return _config; + } - public HttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint) + public HttpConnection(HttpChannelConfig config, Connector connector, EndPoint endPoint) { super(endPoint, connector.getExecutor()); - _configuration = config; + _config = config; _connector = connector; _bufferPool = _connector.getByteBufferPool(); _generator = new HttpGenerator(); // TODO: consider moving the generator to the transport, where it belongs @@ -98,7 +103,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http return _connector; } - public HttpChannel getHttpChannel() + public HttpChannel> getHttpChannel() { return _channel; } @@ -145,8 +150,9 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http { if (_requestBuffer != null && !_requestBuffer.hasRemaining()) { - _bufferPool.release(_requestBuffer); - _requestBuffer = null; + ByteBuffer buffer=_requestBuffer; + _requestBuffer=null; + _bufferPool.release(buffer); } } @@ -175,7 +181,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http if (!event && BufferUtil.isEmpty(_requestBuffer)) { if (_requestBuffer == null) - _requestBuffer = _bufferPool.acquire(_configuration.getRequestHeaderSize(), false); + _requestBuffer = _bufferPool.acquire(getInputBufferSize(), false); int filled = getEndPoint().fill(_requestBuffer); if (filled==0) // Do a retry on fill 0 (optimisation for SSL connections) @@ -315,7 +321,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http { case NEED_HEADER: { - header = _bufferPool.acquire(_configuration.getResponseHeaderSize(), false); + header = _bufferPool.acquire(_config.getResponseHeaderSize(), false); continue; } case NEED_CHUNK: @@ -533,7 +539,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http // We will need a buffer to read into if (_requestBuffer==null) - _requestBuffer=_bufferPool.acquire(_configuration.getRequestBufferSize(),false); + _requestBuffer=_bufferPool.acquire(getInputBufferSize(),false); // read some data int filled=getEndPoint().fill(_requestBuffer); @@ -590,9 +596,9 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http private class HttpChannelOverHttp extends HttpChannel { - public HttpChannelOverHttp(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput input) + public HttpChannelOverHttp(Connector connector, HttpChannelConfig config, EndPoint endPoint, HttpTransport transport, HttpInput input) { - super(connector,configuration,endPoint,transport,input); + super(connector,config,endPoint,transport,input); } @Override diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpServerConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java similarity index 51% rename from jetty-server/src/main/java/org/eclipse/jetty/server/HttpServerConnectionFactory.java rename to jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java index 09784704d9a..05986d89aa2 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpServerConnectionFactory.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java @@ -19,39 +19,41 @@ package org.eclipse.jetty.server; -import java.nio.channels.SocketChannel; +import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.annotation.Name; -public class HttpServerConnectionFactory implements ConnectionFactory +public class HttpConnectionFactory extends AbstractConnectionFactory implements HttpChannelConfig.ConnectionFactory { - private final Connector connector; - private final HttpConfiguration configuration; + private final HttpChannelConfig _config; - public HttpServerConnectionFactory(Connector connector) + public HttpConnectionFactory() { - this(connector, new HttpConfiguration(connector.getSslContextFactory(), connector.getSslContextFactory() != null)); + this(new HttpChannelConfig()); + setInputBufferSize(16384); } - public HttpServerConnectionFactory(Connector connector, HttpConfiguration configuration) + + public HttpConnectionFactory(@Name("config") HttpChannelConfig config) { - this.connector = connector; - this.configuration = configuration; + super(HttpVersion.HTTP_1_1.toString()); + _config=config; + addBean(_config); } - - public Connector getConnector() + + @Override + public HttpChannelConfig getHttpChannelConfig() { - return connector; - } - - public HttpConfiguration getHttpConfiguration() - { - return configuration; + return _config; } @Override - public Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) + public Connection newConnection(Connector connector, EndPoint endPoint) { - return new HttpConnection(getHttpConfiguration(), getConnector(), endPoint); + HttpConnection connection = new HttpConnection(_config, connector, endPoint); + connection.setInputBufferSize(getInputBufferSize()); // TODO constructor injection + return connection; } + } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java index 3fad5007748..98a86b8a017 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.server; import java.io.IOException; import java.io.InterruptedIOException; + import javax.servlet.ServletInputStream; import org.eclipse.jetty.io.EofException; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index 1a0b845f58e..631a09ddbd5 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -22,6 +22,7 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; + import javax.servlet.RequestDispatcher; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; @@ -54,7 +55,7 @@ public class HttpOutput extends ServletOutputStream public HttpOutput(HttpChannel channel) { _channel = channel; - _bufferSize = _channel.getHttpConfiguration().getResponseBufferSize(); + _bufferSize = _channel.getHttpChannelConfig().getOutputBufferSize(); } public boolean isWritten() @@ -209,7 +210,7 @@ public class HttpOutput extends ServletOutputStream HttpContent httpContent = (HttpContent)content; Response response = _channel.getResponse(); String contentType = httpContent.getContentType(); - if (contentType != null) + if (contentType != null && !response.getHttpFields().containsKey(HttpHeader.CONTENT_TYPE.asString())) response.getHttpFields().put(HttpHeader.CONTENT_TYPE, contentType); if (httpContent.getContentLength() > 0) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpServerConnector.java index 19db4db28e8..c7f67174379 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpServerConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpServerConnector.java @@ -27,19 +27,44 @@ import org.eclipse.jetty.util.thread.Scheduler; public class HttpServerConnector extends SelectChannelConnector { - public HttpServerConnector(Server server) + public HttpServerConnector( + @Name("server") Server server) { - this(server, null); + this(server,null,null,null,null,null,0,0); } - public HttpServerConnector(Server server, SslContextFactory sslContextFactory) + public HttpServerConnector( + @Name("server") Server server, + @Name("sslContextFactory") SslContextFactory sslContextFactory) { - this(server, null, null, null, sslContextFactory, 0, 0); + this(server,null,sslContextFactory, null, null, null, 0, 0); } - public HttpServerConnector(@Name("server") Server server, @Name("executor") Executor executor, @Name("scheduler") Scheduler scheduler, @Name("bufferPool") ByteBufferPool pool, @Name("sslContextFactory") SslContextFactory sslContextFactory, @Name("acceptors") int acceptors, @Name("selectors") int selectors) + public HttpServerConnector( + @Name("server") Server server, + @Name("connectionFactory") HttpConnectionFactory connectionFactory) { - super(server, executor, scheduler, pool, sslContextFactory, acceptors, selectors); - setDefaultConnectionFactory(new HttpServerConnectionFactory(this)); + this(server,connectionFactory,null, null, null, null, 0, 0); + } + + public HttpServerConnector( + @Name("server") Server server, + @Name("connectionFactory") HttpConnectionFactory connectionFactory, + @Name("sslContextFactory") SslContextFactory sslContextFactory) + { + this(server,connectionFactory,sslContextFactory, null, null, null, 0, 0); + } + + public HttpServerConnector( + @Name("server") Server server, + @Name("connectionFactory") HttpConnectionFactory connectionFactory, + @Name("sslContextFactory") SslContextFactory sslContextFactory, + @Name("executor") Executor executor, + @Name("scheduler") Scheduler scheduler, + @Name("bufferPool") ByteBufferPool pool, + @Name("acceptors") int acceptors, + @Name("selectors") int selectors) + { + super(server,executor,scheduler,pool,acceptors,selectors,AbstractConnectionFactory.getFactories(sslContextFactory,connectionFactory==null?new HttpConnectionFactory():connectionFactory)); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java index 9764dd32a84..0cfec6445b4 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java @@ -25,13 +25,10 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; -import javax.net.ssl.SSLEngine; import org.eclipse.jetty.io.ByteArrayEndPoint; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.Connection; -import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.io.ssl.SslConnection; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -41,22 +38,31 @@ public class LocalConnector extends AbstractConnector { private final BlockingQueue _connects = new LinkedBlockingQueue<>(); + + public LocalConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, ConnectionFactory... factories) + { + super(server,executor,scheduler,pool,acceptors,factories); + setIdleTimeout(30000); + } + public LocalConnector(Server server) { - this(server,null); + this(server, null, null, null, 0, new HttpConnectionFactory()); } public LocalConnector(Server server, SslContextFactory sslContextFactory) { - this(server, null, null, null, sslContextFactory, 0); + this(server, null, null, null, 0,AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory())); } - public LocalConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, - SslContextFactory sslContextFactory, int acceptors) + public LocalConnector(Server server, ConnectionFactory connectionFactory) { - super(server,executor,scheduler,pool, sslContextFactory, acceptors); - setIdleTimeout(30000); - setDefaultConnectionFactory(new HttpServerConnectionFactory(this)); + this(server, null, null, null, 0, connectionFactory); + } + + public LocalConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory) + { + this(server, null, null, null, 0,AbstractConnectionFactory.getFactories(sslContextFactory,connectionFactory)); } @Override @@ -159,29 +165,10 @@ public class LocalConnector extends AbstractConnector LocalEndPoint endPoint = _connects.take(); endPoint.onOpen(); - SslContextFactory sslContextFactory = getSslContextFactory(); - if (sslContextFactory != null) - { - SSLEngine engine = sslContextFactory.newSSLEngine(endPoint.getRemoteAddress()); - engine.setUseClientMode(false); - - SslConnection sslConnection = new SslConnection(getByteBufferPool(), getExecutor(), endPoint, engine); - endPoint.setConnection(sslConnection); - connectionOpened(sslConnection); - sslConnection.onOpen(); - - EndPoint appEndPoint = sslConnection.getDecryptedEndPoint(); - Connection connection = getDefaultConnectionFactory().newConnection(null, appEndPoint, null); - appEndPoint.setConnection(connection); - connection.onOpen(); - } - else - { - Connection connection = getDefaultConnectionFactory().newConnection(null, endPoint, null); - endPoint.setConnection(connection); - connectionOpened(connection); - connection.onOpen(); - } + Connection connection = getDefaultConnectionFactory().newConnection(this, endPoint); + endPoint.setConnection(connection); + connectionOpened(connection); + connection.onOpen(); } public class LocalEndPoint extends ByteArrayEndPoint diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java index dadf023485c..02594402957 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NCSARequestLog.java @@ -24,6 +24,7 @@ import java.io.OutputStreamWriter; import java.io.Writer; import java.util.Locale; import java.util.TimeZone; + import javax.servlet.http.Cookie; import org.eclipse.jetty.http.HttpHeader; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index d38187f278f..4975a2c6008 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -37,6 +37,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; + import javax.servlet.AsyncContext; import javax.servlet.AsyncListener; import javax.servlet.DispatcherType; @@ -118,28 +119,30 @@ public class Request implements HttpServletRequest private static final Collection __defaultLocale = Collections.singleton(Locale.getDefault()); private static final int __NONE = 0, _STREAM = 1, __READER = 2; - private final HttpChannel _channel; + private final HttpChannel> _channel; private final HttpFields _fields=new HttpFields(); private final List _requestAttributeListeners=new ArrayList<>(); - private final HttpInput _input; + private final HttpInput> _input; + private boolean _secure; private boolean _asyncSupported = true; + private boolean _newContext; + private boolean _cookiesExtracted = false; + private boolean _handled = false; + private boolean _paramsExtracted; + private boolean _requestedSessionIdFromCookie = false; private volatile Attributes _attributes; private Authentication _authentication; private MultiMap _baseParameters; private String _characterEncoding; private ContextHandler.Context _context; - private boolean _newContext; private String _contextPath; private CookieCutter _cookies; - private boolean _cookiesExtracted = false; private DispatcherType _dispatcherType; - private boolean _handled = false; private int _inputState = __NONE; private HttpMethod _httpMethod; private String _httpMethodString; private MultiMap _parameters; - private boolean _paramsExtracted; private String _pathInfo; private int _port; private HttpVersion _httpVersion = HttpVersion.HTTP_1_1; @@ -149,7 +152,6 @@ public class Request implements HttpServletRequest private String _readerEncoding; private InetSocketAddress _remote; private String _requestedSessionId; - private boolean _requestedSessionIdFromCookie = false; private String _requestURI; private Map