From cce5a28e23bd6b4b8e1466d969af0cb526756988 Mon Sep 17 00:00:00 2001 From: Ryan Lubke Date: Fri, 27 Sep 2013 11:25:13 -0700 Subject: [PATCH 0001/2254] Refactoring in preparation for supporting spdy multiplexing. --- .../providers/grizzly/EventHandler.java | 33 +++--- .../grizzly/FeedableBodyGenerator.java | 54 +++++---- .../grizzly/GrizzlyAsyncHttpProvider.java | 99 ++++------------ .../providers/grizzly/HttpTxContext.java | 61 ++++++---- .../providers/grizzly/RequestInfoHolder.java | 66 +++++++++++ .../providers/grizzly/Utils.java | 12 ++ .../grizzly/bodyhandler/FileBodyHandler.java | 2 +- .../filters/AsyncHttpClientEventFilter.java | 10 +- .../filters/AsyncHttpClientFilter.java | 108 ++++++++++++++---- .../AsyncHttpClientTransportFilter.java | 77 ------------- .../grizzly/filters/ProxyFilter.java | 2 +- .../filters/events/SSLSwitchingEvent.java | 1 - .../statushandler/AuthorizationHandler.java | 5 +- .../ProxyAuthorizationHandler.java | 18 +-- .../statushandler/RedirectHandler.java | 5 +- 15 files changed, 292 insertions(+), 261 deletions(-) create mode 100644 providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/RequestInfoHolder.java delete mode 100644 providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientTransportFilter.java diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java index d1b94d5bc3..0b5ff84d7d 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java @@ -91,7 +91,7 @@ public final class EventHandler { public void exceptionOccurred(FilterChainContext ctx, Throwable error) { - HttpTxContext.get(ctx.getConnection()).abort(error); + HttpTxContext.get(ctx).abort(error); } @@ -100,7 +100,7 @@ public void onHttpContentParsed(HttpContent content, FilterChainContext ctx) { final HttpTxContext context = - HttpTxContext.get(ctx.getConnection()); + HttpTxContext.get(ctx); final AsyncHandler handler = context.getHandler(); if (handler != null && context.getCurrentState() != ABORT) { try { @@ -119,7 +119,7 @@ public void onHttpContentParsed(HttpContent content, @SuppressWarnings("UnusedParameters") public void onHttpHeadersEncoded(HttpHeader httpHeader, FilterChainContext ctx) { final HttpTxContext context = - HttpTxContext.get(ctx.getConnection()); + HttpTxContext.get(ctx); final AsyncHandler handler = context.getHandler(); if (handler instanceof TransferCompletionHandler) { ((TransferCompletionHandler) handler).onHeaderWriteCompleted(); @@ -128,7 +128,7 @@ public void onHttpHeadersEncoded(HttpHeader httpHeader, FilterChainContext ctx) public void onHttpContentEncoded(HttpContent content, FilterChainContext ctx) { final HttpTxContext context = - HttpTxContext.get(ctx.getConnection()); + HttpTxContext.get(ctx); final AsyncHandler handler = context.getHandler(); if (handler instanceof TransferCompletionHandler) { final int written = content.getContent().remaining(); @@ -145,9 +145,7 @@ public void onInitialLineParsed(HttpHeader httpHeader, if (httpHeader.isSkipRemainder()) { return; } - final Connection connection = ctx.getConnection(); - final HttpTxContext context = - HttpTxContext.get(connection); + final HttpTxContext context = HttpTxContext.get(ctx); final int status = ((HttpResponsePacket) httpHeader).getStatus(); if (HttpStatus.CONINTUE_100.statusMatches(status)) { ctx.notifyUpstream(new ContinueEvent(context)); @@ -221,8 +219,7 @@ public void onHttpHeaderError(final HttpHeader httpHeader, t.printStackTrace(); httpHeader.setSkipRemainder(true); - final HttpTxContext context = - HttpTxContext.get(ctx.getConnection()); + final HttpTxContext context = HttpTxContext.get(ctx); context.abort(t); } @@ -233,7 +230,7 @@ public void onHttpHeadersParsed(HttpHeader httpHeader, //super.onHttpHeadersParsed(httpHeader, ctx); GrizzlyAsyncHttpProvider.LOGGER.debug("RESPONSE: {}", httpHeader); processKeepAlive(ctx.getConnection(), httpHeader); - final HttpTxContext context = HttpTxContext.get(ctx.getConnection()); + final HttpTxContext context = HttpTxContext.get(ctx); if (httpHeader.isSkipRemainder()) { return; @@ -271,12 +268,13 @@ public void onHttpHeadersParsed(HttpHeader httpHeader, context.getFuture()); final HttpTxContext newContext = context.copy(); + newContext.setRequest(newRequest); context.setFuture(null); - HttpTxContext.set(c, newContext); context.getProvider().execute(c, newRequest, newHandler, - context.getFuture()); + context.getFuture(), + newContext); } catch (Exception e) { context.abort(e); } @@ -366,10 +364,7 @@ public boolean onHttpPacketParsed(HttpHeader httpHeader, FilterChainContext ctx) return false; } - final HttpResponsePacket response = - (HttpResponsePacket) httpHeader; - final HttpTxContext context = HttpTxContext.get( - ctx.getConnection()); + final HttpTxContext context = HttpTxContext.get(ctx); cleanup(ctx); final AsyncHandler handler = context.getHandler(); if (handler != null) { @@ -431,9 +426,9 @@ private static HttpTxContext cleanup(final FilterChainContext ctx) { final Connection c = ctx.getConnection(); final HttpTxContext context = - HttpTxContext.get(c); - HttpTxContext.set(c, null); - if (!Utils.isIgnored(ctx.getConnection())) { + HttpTxContext.get(ctx); + HttpTxContext.remove(ctx, context); + if (!Utils.isIgnored(c)) { final ConnectionManager manager = context.getProvider().getConnectionManager(); //if (!manager.canReturnConnection(c)) { diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/FeedableBodyGenerator.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/FeedableBodyGenerator.java index f338897e6f..e359626b0f 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/FeedableBodyGenerator.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/FeedableBodyGenerator.java @@ -21,10 +21,12 @@ import org.glassfish.grizzly.Buffer; import org.glassfish.grizzly.CompletionHandler; import org.glassfish.grizzly.Connection; +import org.glassfish.grizzly.OutputSink; import org.glassfish.grizzly.WriteHandler; import org.glassfish.grizzly.WriteResult; import org.glassfish.grizzly.filterchain.FilterChainContext; import org.glassfish.grizzly.http.HttpContent; +import org.glassfish.grizzly.http.HttpContext; import org.glassfish.grizzly.http.HttpRequestPacket; import org.glassfish.grizzly.impl.FutureImpl; import org.glassfish.grizzly.nio.NIOConnection; @@ -174,7 +176,7 @@ public void run() { try { feeder.flush(); } catch (IOException ioe) { - HttpTxContext ctx = HttpTxContext.get(c); + HttpTxContext ctx = HttpTxContext.get(context); ctx.abort(ioe); } } @@ -304,7 +306,7 @@ public final synchronized void feed(final Buffer buffer, final boolean last) throw new IllegalStateException( "Asynchronous transfer has not been initiated."); } - blockUntilQueueFree(feedableBodyGenerator.context.getConnection()); + blockUntilQueueFree(feedableBodyGenerator.context); final HttpContent content = feedableBodyGenerator.contentBuilder .content(buffer) @@ -321,11 +323,13 @@ public final synchronized void feed(final Buffer buffer, final boolean last) * will block is dependent on the write timeout of the transport * associated with the specified connection. */ - private static void blockUntilQueueFree(final Connection c) { - if (!c.canWrite()) { + private static void blockUntilQueueFree(final FilterChainContext ctx) { + HttpContext httpContext = HttpContext.get(ctx); + final OutputSink outputSink = httpContext.getOutputSink(); + if (!outputSink.canWrite()) { final FutureImpl future = Futures.createSafeFuture(); - c.notifyCanWrite(new WriteHandler() { + outputSink.notifyCanWrite(new WriteHandler() { @Override public void onWritePossible() throws Exception { @@ -338,26 +342,26 @@ public void onError(Throwable t) { } }); - block(c, future); + block(ctx, future); } } - private static void block(final Connection c, + private static void block(final FilterChainContext ctx, final FutureImpl future) { try { final long writeTimeout = - c.getTransport().getWriteTimeout(MILLISECONDS); + ctx.getConnection().getTransport().getWriteTimeout(MILLISECONDS); if (writeTimeout != -1) { future.get(writeTimeout, MILLISECONDS); } else { future.get(); } } catch (ExecutionException e) { - HttpTxContext ctx = HttpTxContext.get(c); - ctx.abort(e.getCause()); + HttpTxContext httpTxContext = HttpTxContext.get(ctx); + httpTxContext.abort(e.getCause()); } catch (Exception e) { - HttpTxContext ctx = HttpTxContext.get(c); - ctx.abort(e); + HttpTxContext httpTxContext = HttpTxContext.get(ctx); + httpTxContext.abort(e); } } @@ -493,17 +497,19 @@ public NonBlockingFeeder(final FeedableBodyGenerator feedableBodyGenerator) { */ @Override public synchronized void flush() { - final Connection c = feedableBodyGenerator.context.getConnection(); + final HttpContext httpContext = + HttpContext.get(feedableBodyGenerator.context); + final OutputSink outputSink = httpContext.getOutputSink(); if (isReady()) { - writeUntilFullOrDone(c); + writeUntilFullOrDone(outputSink); if (!isDone()) { if (!isReady()) { notifyReadyToFeed(new ReadyToFeedListenerImpl()); } - if (!c.canWrite()) { + if (!outputSink.canWrite()) { // write queue is full, leverage WriteListener to let us know // when it is safe to write again. - c.notifyCanWrite(new WriteHandlerImpl()); + outputSink.notifyCanWrite(new WriteHandlerImpl()); } } } else { @@ -515,8 +521,8 @@ public synchronized void flush() { // ----------------------------------------------------- Private Methods - private void writeUntilFullOrDone(final Connection c) { - while (c.canWrite()) { + private void writeUntilFullOrDone(final OutputSink outputSink) { + while (outputSink.canWrite()) { if (isReady()) { canFeed(); } @@ -548,6 +554,7 @@ private final class WriteHandlerImpl implements WriteHandler { private final Connection c; + private final FilterChainContext ctx; // -------------------------------------------------------- Constructors @@ -555,6 +562,7 @@ private final class WriteHandlerImpl implements WriteHandler { private WriteHandlerImpl() { this.c = feedableBodyGenerator.context.getConnection(); + this.ctx = feedableBodyGenerator.context; } @@ -577,10 +585,12 @@ public void onWritePossible() throws Exception { @Override public void onError(Throwable t) { - c.setMaxAsyncWriteQueueSize( - feedableBodyGenerator.origMaxPendingBytes); - HttpTxContext ctx = HttpTxContext.get(c); - ctx.abort(t); + if (!Utils.isSpdyConnection(c)) { + c.setMaxAsyncWriteQueueSize( + feedableBodyGenerator.origMaxPendingBytes); + } + HttpTxContext httpTxContext = HttpTxContext.get(ctx); + httpTxContext.abort(t); } } // END WriteHandlerImpl diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java index 12995bb4f6..f03decb5a9 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java @@ -24,12 +24,8 @@ import org.asynchttpclient.Request; import org.asynchttpclient.Response; import org.asynchttpclient.ntlm.NTLMEngine; -import org.asynchttpclient.providers.grizzly.bodyhandler.BodyHandler; -import org.asynchttpclient.providers.grizzly.bodyhandler.BodyHandlerFactory; -import org.asynchttpclient.providers.grizzly.bodyhandler.ExpectHandler; import org.asynchttpclient.providers.grizzly.filters.AsyncHttpClientEventFilter; import org.asynchttpclient.providers.grizzly.filters.AsyncHttpClientFilter; -import org.asynchttpclient.providers.grizzly.filters.AsyncHttpClientTransportFilter; import org.asynchttpclient.providers.grizzly.filters.AsyncSpdyClientEventFilter; import org.asynchttpclient.providers.grizzly.filters.ClientEncodingFilter; import org.asynchttpclient.providers.grizzly.filters.SwitchingSSLFilter; @@ -44,12 +40,11 @@ import org.glassfish.grizzly.filterchain.FilterChain; import org.glassfish.grizzly.filterchain.FilterChainBuilder; import org.glassfish.grizzly.filterchain.FilterChainContext; +import org.glassfish.grizzly.filterchain.TransportFilter; import org.glassfish.grizzly.http.ContentEncoding; import org.glassfish.grizzly.http.GZipContentEncoding; import org.glassfish.grizzly.http.HttpClientFilter; -import org.glassfish.grizzly.http.HttpContent; -import org.glassfish.grizzly.http.HttpRequestPacket; -import org.glassfish.grizzly.http.Method; +import org.glassfish.grizzly.http.HttpContext; import org.glassfish.grizzly.npn.ClientSideNegotiator; import org.glassfish.grizzly.spdy.NextProtoNegSupport; import org.glassfish.grizzly.spdy.SpdyFramingFilter; @@ -59,7 +54,6 @@ import org.glassfish.grizzly.ssl.SSLBaseFilter; import org.glassfish.grizzly.ssl.SSLConnectionContext; import org.glassfish.grizzly.ssl.SSLUtils; -import org.glassfish.grizzly.http.util.Header; import org.glassfish.grizzly.impl.SafeFutureImpl; import org.glassfish.grizzly.nio.transport.TCPNIOTransport; import org.glassfish.grizzly.nio.transport.TCPNIOTransportBuilder; @@ -74,7 +68,6 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; -import java.io.File; import java.io.IOException; import java.util.LinkedHashSet; import java.util.List; @@ -99,7 +92,6 @@ public class GrizzlyAsyncHttpProvider implements AsyncHttpProvider { public static final Logger LOGGER = LoggerFactory.getLogger(GrizzlyAsyncHttpProvider.class); public final static NTLMEngine NTLM_ENGINE = new NTLMEngine(); - private final BodyHandlerFactory bodyHandlerFactory; private final AsyncHttpClientConfig clientConfig; private ConnectionManager connectionManager; @@ -123,8 +115,6 @@ public GrizzlyAsyncHttpProvider(final AsyncHttpClientConfig clientConfig) { } catch (IOException ioe) { throw new RuntimeException(ioe); } - bodyHandlerFactory = new BodyHandlerFactory(this); - } @@ -158,7 +148,7 @@ public void failed(final Throwable throwable) { public void completed(final Connection c) { try { touchConnection(c, request); - execute(c, request, handler, future); + execute(c, request, handler, future, null); } catch (Exception e) { failed(e); } @@ -239,12 +229,12 @@ public DelayedExecutor.Resolver getResolver() { public ListenableFuture execute(final Connection c, final Request request, final AsyncHandler handler, - final GrizzlyResponseFuture future) { + final GrizzlyResponseFuture future, + final HttpTxContext httpTxContext) { Utils.addRequestInFlight(c); - if (HttpTxContext.get(c) == null) { - HttpTxContext.create(this, future, request, handler, c); - } - c.write(request, createWriteCompletionHandler(future)); + final RequestInfoHolder requestInfoHolder = + new RequestInfoHolder(this, request, handler, future, httpTxContext); + c.write(requestInfoHolder, createWriteCompletionHandler(future)); return future; } @@ -253,7 +243,7 @@ public ListenableFuture execute(final Connection c, void initializeTransport(final AsyncHttpClientConfig clientConfig) { final FilterChainBuilder secure = FilterChainBuilder.stateless(); - secure.add(new AsyncHttpClientTransportFilter()); + secure.add(new TransportFilter()); final int timeout = clientConfig.getRequestTimeoutInMs(); if (timeout > 0) { @@ -271,8 +261,7 @@ void initializeTransport(final AsyncHttpClientConfig clientConfig) { new IdleTimeoutFilter.TimeoutResolver() { @Override public long getTimeout(FilterChainContext ctx) { - final HttpTxContext context = - HttpTxContext.get(ctx.getConnection()); + final HttpTxContext context = HttpTxContext.get(ctx); if (context != null) { if (context.isWSRequest()) { return clientConfig.getWebSocketIdleTimeoutInMs(); @@ -495,70 +484,26 @@ public void updated(WriteResult result) { void timeout(final Connection c) { - final HttpTxContext context = HttpTxContext.get(c); - if (context != null) { - HttpTxContext.set(c, null); - context.abort(new TimeoutException("Timeout exceeded")); - } - - } - - - @SuppressWarnings({"unchecked"}) - public boolean sendRequest(final FilterChainContext ctx, - final Request request, - final HttpRequestPacket requestPacket) - throws IOException { - - boolean isWriteComplete = true; - - if (requestHasEntityBody(request)) { - final HttpTxContext context = HttpTxContext.get(ctx.getConnection()); - BodyHandler handler = bodyHandlerFactory.getBodyHandler(request); - if (requestPacket.getHeaders().contains(Header.Expect) - && requestPacket.getHeaders().getValue(1).equalsIgnoreCase("100-Continue")) { - // We have to set the content-length now as the headers will be flushed - // before the FileBodyHandler is invoked. If we don't do it here, and - // the user didn't explicitly set the length, then the transfer-encoding - // will be chunked and zero-copy file transfer will not occur. - final File f = request.getFile(); - if (f != null) { - requestPacket.setContentLengthLong(f.length()); - } - handler = new ExpectHandler(handler); + final String key = HttpTxContext.class.getName(); + HttpTxContext ctx = null; + if (!Utils.isSpdyConnection(c)) { + ctx = (HttpTxContext) c.getAttributes().getAttribute(key); + if (ctx != null) { + c.getAttributes().removeAttribute(key); + ctx.abort(new TimeoutException("Timeout exceeded")); } - context.setBodyHandler(handler); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("REQUEST: {}", requestPacket); - } - isWriteComplete = handler.doHandle(ctx, request, requestPacket); } else { - HttpContent content = HttpContent.builder(requestPacket).last(true).build(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("REQUEST: {}", requestPacket); - } - ctx.write(content, ctx.getTransportContext().getCompletionHandler()); + throw new IllegalStateException(); } +// if (context != null) { +// HttpTxContext.set(c, null); +// context.abort(new TimeoutException("Timeout exceeded")); +// } - return isWriteComplete; } - public static boolean requestHasEntityBody(final Request request) { - - final String method = request.getMethod(); - return (Method.POST.matchesMethod(method) - || Method.PUT.matchesMethod(method) - || Method.PATCH.matchesMethod(method) - || Method.DELETE.matchesMethod(method)); - - } - - - // ----------------------------------------------------------- Inner Classes - - // ---------------------------------------------------------- Nested Classes diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/HttpTxContext.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/HttpTxContext.java index 344128f75a..3ea419bf67 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/HttpTxContext.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/HttpTxContext.java @@ -18,12 +18,17 @@ import org.asynchttpclient.providers.grizzly.bodyhandler.BodyHandler; import org.asynchttpclient.providers.grizzly.statushandler.StatusHandler; import org.asynchttpclient.websocket.WebSocket; +import org.glassfish.grizzly.CloseListener; +import org.glassfish.grizzly.CloseType; +import org.glassfish.grizzly.Closeable; import org.glassfish.grizzly.Grizzly; import org.glassfish.grizzly.attributes.Attribute; -import org.glassfish.grizzly.attributes.AttributeStorage; +import org.glassfish.grizzly.filterchain.FilterChainContext; +import org.glassfish.grizzly.http.HttpContext; import org.glassfish.grizzly.websockets.HandShake; import org.glassfish.grizzly.websockets.ProtocolHandler; +import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -57,6 +62,15 @@ public final class HttpTxContext { private HandShake handshake; private ProtocolHandler protocolHandler; private WebSocket webSocket; + private CloseListener listener = new CloseListener< Closeable,CloseType>() { + @Override + public void onClosed(Closeable closeable, CloseType type) + throws IOException { + if (CloseType.REMOTELY.equals(type)) { + abort(new IOException("Remotely Closed")); + } + } + }; // -------------------------------------------------------- Constructors @@ -67,7 +81,6 @@ private HttpTxContext(final GrizzlyAsyncHttpProvider provider, final Request request, final AsyncHandler handler) { this.provider = provider; - this.future = future; this.request = request; this.handler = handler; @@ -81,35 +94,33 @@ private HttpTxContext(final GrizzlyAsyncHttpProvider provider, // ---------------------------------------------------------- Public Methods - public static void set(final AttributeStorage storage, - final HttpTxContext httpTransactionState) { - - if (httpTransactionState == null) { - REQUEST_STATE_ATTR.remove(storage); - } else { - REQUEST_STATE_ATTR.set(storage, httpTransactionState); - } - + public static void set(final FilterChainContext ctx, + final HttpTxContext httpTxContext) { + HttpContext httpContext = HttpContext.get(ctx); + httpContext.getCloseable().addCloseListener(httpTxContext.listener); + REQUEST_STATE_ATTR.set(httpContext, httpTxContext); } - public static HttpTxContext get(final AttributeStorage storage) { - - return REQUEST_STATE_ATTR.get(storage); - + public static void remove(final FilterChainContext ctx, + final HttpTxContext httpTxContext) { + HttpContext httpContext = HttpContext.get(ctx); + httpContext.getCloseable().removeCloseListener(httpTxContext.listener); + REQUEST_STATE_ATTR.remove(ctx); } - - public static HttpTxContext create(final GrizzlyAsyncHttpProvider provider, - final GrizzlyResponseFuture future, - final Request request, - final AsyncHandler handler, - final AttributeStorage storage) { - final HttpTxContext context = - new HttpTxContext(provider, future, request, handler); - set(storage, context); - return context; + public static HttpTxContext get(FilterChainContext ctx) { + HttpContext httpContext = HttpContext.get(ctx); + return ((httpContext != null) + ? REQUEST_STATE_ATTR.get(httpContext) + : null); } + public static HttpTxContext create(final RequestInfoHolder requestInfoHolder) { + return new HttpTxContext(requestInfoHolder.getProvider(), + requestInfoHolder.getFuture(), + requestInfoHolder.getRequest(), + requestInfoHolder.getHandler()); + } public void abort(final Throwable t) { if (future != null) { diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/RequestInfoHolder.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/RequestInfoHolder.java new file mode 100644 index 0000000000..7cf16c8ecd --- /dev/null +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/RequestInfoHolder.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package org.asynchttpclient.providers.grizzly; + +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.Request; + +public class RequestInfoHolder { + + private final GrizzlyAsyncHttpProvider provider; + private final Request request; + private final AsyncHandler handler; + private final GrizzlyResponseFuture future; + private final HttpTxContext httpTxContext; + + + // ------------------------------------------------------------ Constructors + + + public RequestInfoHolder(final GrizzlyAsyncHttpProvider provider, + final Request request, + final AsyncHandler handler, + final GrizzlyResponseFuture future, + final HttpTxContext httpTxContext) { + this.provider = provider; + this.request = request; + this.handler = handler; + this.future = future; + this.httpTxContext = httpTxContext; + } + + + // ---------------------------------------------------------- Public Methods + + + public GrizzlyAsyncHttpProvider getProvider() { + return provider; + } + + public Request getRequest() { + return request; + } + + public AsyncHandler getHandler() { + return handler; + } + + public GrizzlyResponseFuture getFuture() { + return future; + } + + public HttpTxContext getHttpTxContext() { + return httpTxContext; + } +} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/Utils.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/Utils.java index 3426376ee7..f54f828e1b 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/Utils.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/Utils.java @@ -13,10 +13,12 @@ package org.asynchttpclient.providers.grizzly; +import org.asynchttpclient.Request; import org.glassfish.grizzly.Connection; import org.glassfish.grizzly.Grizzly; import org.glassfish.grizzly.attributes.Attribute; import org.glassfish.grizzly.attributes.AttributeStorage; +import org.glassfish.grizzly.http.Method; import java.net.URI; import java.util.concurrent.atomic.AtomicInteger; @@ -88,4 +90,14 @@ public static boolean isSpdyConnection(final Connection c) { Boolean result = SPDY.get(c); return (result != null ? result : false); } + + public static boolean requestHasEntityBody(final Request request) { + + final String method = request.getMethod(); + return (Method.POST.matchesMethod(method) + || Method.PUT.matchesMethod(method) + || Method.PATCH.matchesMethod(method) + || Method.DELETE.matchesMethod(method)); + + } } diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/FileBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/FileBodyHandler.java index 368a3a2c09..be3248d776 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/FileBodyHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/FileBodyHandler.java @@ -55,7 +55,7 @@ public boolean doHandle(final FilterChainContext ctx, final File f = request.getFile(); requestPacket.setContentLengthLong(f.length()); - final HttpTxContext context = HttpTxContext.get(ctx.getConnection()); + final HttpTxContext context = HttpTxContext.get(ctx); if (!SEND_FILE_SUPPORT || requestPacket.isSecure()) { final FileInputStream fis = new FileInputStream(request.getFile()); final MemoryManager mm = ctx.getMemoryManager(); diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientEventFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientEventFilter.java index 8b2aa0783d..eb15b9a9aa 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientEventFilter.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientEventFilter.java @@ -15,11 +15,13 @@ import org.asynchttpclient.providers.grizzly.EventHandler; import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; +import org.glassfish.grizzly.Connection; import org.glassfish.grizzly.filterchain.FilterChainContext; +import org.glassfish.grizzly.filterchain.NextAction; import org.glassfish.grizzly.http.HttpClientFilter; import org.glassfish.grizzly.http.HttpContent; +import org.glassfish.grizzly.http.HttpContext; import org.glassfish.grizzly.http.HttpHeader; -import org.glassfish.grizzly.http.HttpResponsePacket; import java.io.IOException; @@ -51,6 +53,12 @@ public AsyncHttpClientEventFilter(final EventHandler eventHandler, this.eventHandler = eventHandler; } + @Override + public NextAction handleRead(FilterChainContext ctx) throws IOException { + final Connection c = ctx.getConnection(); + HttpContext.newInstance(ctx, c, c, c); + return super.handleRead(ctx); + } @Override public void exceptionOccurred(FilterChainContext ctx, Throwable error) { diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java index 52527f597c..cda70498c6 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java @@ -29,7 +29,10 @@ import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; import org.asynchttpclient.providers.grizzly.GrizzlyResponseFuture; import org.asynchttpclient.providers.grizzly.HttpTxContext; +import org.asynchttpclient.providers.grizzly.RequestInfoHolder; import org.asynchttpclient.providers.grizzly.Utils; +import org.asynchttpclient.providers.grizzly.bodyhandler.BodyHandler; +import org.asynchttpclient.providers.grizzly.bodyhandler.BodyHandlerFactory; import org.asynchttpclient.providers.grizzly.bodyhandler.ExpectHandler; import org.asynchttpclient.providers.grizzly.filters.events.ContinueEvent; import org.asynchttpclient.providers.grizzly.filters.events.SSLSwitchingEvent; @@ -42,6 +45,7 @@ import org.glassfish.grizzly.filterchain.FilterChainContext; import org.glassfish.grizzly.filterchain.FilterChainEvent; import org.glassfish.grizzly.filterchain.NextAction; +import org.glassfish.grizzly.http.HttpContext; import org.glassfish.grizzly.http.HttpRequestPacket; import org.glassfish.grizzly.http.Method; import org.glassfish.grizzly.http.ProcessingState; @@ -54,6 +58,7 @@ import org.glassfish.grizzly.ssl.SSLUtils; import org.glassfish.grizzly.websockets.Version; +import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; @@ -63,9 +68,11 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; + import org.glassfish.grizzly.Connection; import org.glassfish.grizzly.http.HttpContent; import org.glassfish.grizzly.http.HttpResponsePacket; +import org.slf4j.Logger; import static org.asynchttpclient.providers.grizzly.filters.SwitchingSSLFilter.getHandshakeError; import static org.asynchttpclient.util.AsyncHttpProviderUtils.getAuthority; @@ -83,9 +90,11 @@ public final class AsyncHttpClientFilter extends BaseFilter { private ConcurrentLinkedQueue requestCache = new ConcurrentLinkedQueue(); + private final Logger logger; private final AsyncHttpClientConfig config; private final GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider; + private final BodyHandlerFactory bodyHandlerFactory; private static final Attribute PROXY_AUTH_FAILURE = Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(AsyncHttpClientFilter.class.getName() + "-PROXY-AUTH_FAILURE"); @@ -95,9 +104,9 @@ public final class AsyncHttpClientFilter extends BaseFilter { public AsyncHttpClientFilter(GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider, final AsyncHttpClientConfig config) { this.grizzlyAsyncHttpProvider = grizzlyAsyncHttpProvider; - this.config = config; - + bodyHandlerFactory = new BodyHandlerFactory(grizzlyAsyncHttpProvider); + logger = GrizzlyAsyncHttpProvider.LOGGER; } @@ -125,9 +134,9 @@ public NextAction handleWrite(final FilterChainContext ctx) throws IOException { Object message = ctx.getMessage(); - if (message instanceof Request) { + if (message instanceof RequestInfoHolder) { ctx.setMessage(null); - if (!sendAsGrizzlyRequest((Request) message, ctx)) { + if (!sendAsGrizzlyRequest((RequestInfoHolder) message, ctx)) { return ctx.getSuspendAction(); } } else if (message instanceof Buffer) { @@ -182,7 +191,8 @@ public Object onCompleted(Response response) throws Exception { grizzlyAsyncHttpProvider.execute(ctx.getConnection(), request, handler, - future); + future, + HttpTxContext.get(ctx)); return ctx.getSuspendAction(); } @@ -203,32 +213,36 @@ private static void recycleRequestResponsePackets(final Connection c, } } - private boolean sendAsGrizzlyRequest(final Request request, + private boolean sendAsGrizzlyRequest(final RequestInfoHolder requestInfoHolder, final FilterChainContext ctx) throws IOException { - final HttpTxContext httpCtx = HttpTxContext.get(ctx.getConnection()); + HttpTxContext httpTxContext = requestInfoHolder.getHttpTxContext(); + if (httpTxContext == null) { + httpTxContext = HttpTxContext.create(requestInfoHolder); + } - if (checkProxyAuthFailure(ctx, httpCtx)) { + if (checkProxyAuthFailure(ctx, httpTxContext)) { return true; } - final URI uri = httpCtx.getRequest().getURI(); + final URI uri = httpTxContext.getRequest().getURI(); boolean secure = Utils.isSecure(uri); // If the request is secure, check to see if an error occurred during // the handshake. We have to do this here, as the error would occur // out of the scope of a HttpTxContext so there would be // no good way to communicate the problem to the caller. - if (secure && checkHandshakeError(ctx, httpCtx)) { + if (secure && checkHandshakeError(ctx, httpTxContext)) { return true; } - if (isUpgradeRequest(httpCtx.getHandler()) && isWSRequest(httpCtx.getRequestUrl())) { - httpCtx.setWSRequest(true); - convertToUpgradeRequest(httpCtx); + if (isUpgradeRequest(httpTxContext.getHandler()) && isWSRequest(httpTxContext.getRequestUrl())) { + httpTxContext.setWSRequest(true); + convertToUpgradeRequest(httpTxContext); } + final Request request = httpTxContext.getRequest(); HttpRequestPacket requestPacket = requestCache.poll(); if (requestPacket == null) { requestPacket = new HttpRequestPacketImpl(); @@ -244,7 +258,7 @@ private boolean sendAsGrizzlyRequest(final Request request, requestPacket.setRequestURI(uri.getPath()); } - if (GrizzlyAsyncHttpProvider.requestHasEntityBody(request)) { + if (Utils.requestHasEntityBody(request)) { final long contentLength = request.getContentLength(); if (contentLength > 0) { requestPacket.setContentLengthLong(contentLength); @@ -254,16 +268,16 @@ private boolean sendAsGrizzlyRequest(final Request request, } } - if (httpCtx.isWSRequest()) { + if (httpTxContext.isWSRequest()) { try { - final URI wsURI = new URI(httpCtx.getWsRequestURI()); - httpCtx.setProtocolHandler(Version.RFC6455.createHandler(true)); - httpCtx.setHandshake( - httpCtx.getProtocolHandler().createHandShake(wsURI)); + final URI wsURI = new URI(httpTxContext.getWsRequestURI()); + httpTxContext.setProtocolHandler(Version.RFC6455.createHandler(true)); + httpTxContext.setHandshake( + httpTxContext.getProtocolHandler().createHandShake(wsURI)); requestPacket = (HttpRequestPacket) - httpCtx.getHandshake().composeHeaders().getHttpHeader(); + httpTxContext.getHandshake().composeHeaders().getHttpHeader(); } catch (URISyntaxException e) { - throw new IllegalArgumentException("Invalid WS URI: " + httpCtx.getWsRequestURI()); + throw new IllegalArgumentException("Invalid WS URI: " + httpTxContext.getWsRequestURI()); } } @@ -273,7 +287,7 @@ private boolean sendAsGrizzlyRequest(final Request request, addGeneralHeaders(request, requestPacket); addCookies(request, requestPacket); - initTransferCompletionHandler(request, httpCtx.getHandler()); + initTransferCompletionHandler(request, httpTxContext.getHandler()); final HttpRequestPacket requestPacketLocal = requestPacket; FilterChainContext sendingCtx = ctx; @@ -284,11 +298,55 @@ private boolean sendAsGrizzlyRequest(final Request request, // use a different FilterChainContext when invoking sendRequest(). sendingCtx = checkAndHandleFilterChainUpdate(ctx, sendingCtx); } + final Connection c = ctx.getConnection(); + HttpContext.newInstance(ctx, c, c, c); + HttpTxContext.set(ctx, httpTxContext); + return sendRequest(sendingCtx, request, requestPacketLocal); + + } + + @SuppressWarnings("unchecked") + public boolean sendRequest(final FilterChainContext ctx, + final Request request, + final HttpRequestPacket requestPacket) + throws IOException { + + boolean isWriteComplete = true; + + if (Utils.requestHasEntityBody(request)) { + final HttpTxContext context = + HttpTxContext.get(ctx); + BodyHandler handler = bodyHandlerFactory.getBodyHandler(request); + if (requestPacket.getHeaders().contains(Header.Expect) + && requestPacket.getHeaders() + .getValue(1) + .equalsIgnoreCase("100-Continue")) { + // We have to set the content-length now as the headers will be flushed + // before the FileBodyHandler is invoked. If we don't do it here, and + // the user didn't explicitly set the length, then the transfer-encoding + // will be chunked and zero-copy file transfer will not occur. + final File f = request.getFile(); + if (f != null) { + requestPacket.setContentLengthLong(f.length()); + } + handler = new ExpectHandler(handler); + } + context.setBodyHandler(handler); + if (logger.isDebugEnabled()) { + logger.debug("REQUEST: {}", requestPacket); + } + isWriteComplete = handler.doHandle(ctx, request, requestPacket); + } else { + HttpContent content = + HttpContent.builder(requestPacket).last(true).build(); + if (logger.isDebugEnabled()) { + logger.debug("REQUEST: {}", requestPacket); + } + ctx.write(content, ctx.getTransportContext().getCompletionHandler()); + } - return grizzlyAsyncHttpProvider.sendRequest(sendingCtx, - request, - requestPacketLocal); + return isWriteComplete; } private static FilterChainContext checkAndHandleFilterChainUpdate(final FilterChainContext ctx, diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientTransportFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientTransportFilter.java deleted file mode 100644 index 39982d6c5c..0000000000 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientTransportFilter.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ - -package org.asynchttpclient.providers.grizzly.filters; - -import org.asynchttpclient.providers.grizzly.HttpTxContext; -import org.glassfish.grizzly.CompletionHandler; -import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.filterchain.NextAction; -import org.glassfish.grizzly.filterchain.TransportFilter; - -import java.io.EOFException; -import java.io.IOException; - -/** - * Custom {@link TransportFilter} implementation to capture and handle low-level - * exceptions. - * - * @since 1.7 - * @author The Grizzly Team - */ -public final class AsyncHttpClientTransportFilter extends TransportFilter { - - - // ----------------------------------------------------- Methods from Filter - - - @Override - public NextAction handleRead(FilterChainContext ctx) throws IOException { - final HttpTxContext context = - HttpTxContext.get(ctx.getConnection()); - if (context == null) { - return super.handleRead(ctx); - } - ctx.getTransportContext().setCompletionHandler(new CompletionHandler() { - @Override - public void cancelled() { - - } - - @Override - public void failed(Throwable throwable) { - if (throwable instanceof EOFException) { - context.abort(new IOException("Remotely Closed")); - } - } - - @Override - public void completed(Object result) { - } - - @Override - public void updated(Object result) { - } - }); - return super.handleRead(ctx); - } - - @Override - public void exceptionOccurred(FilterChainContext ctx, Throwable error) { - final HttpTxContext context = HttpTxContext.get(ctx.getConnection()); - if (context != null) { - context.abort(error.getCause()); - } - } - -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/ProxyFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/ProxyFilter.java index 0a2be3030b..6786a95550 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/ProxyFilter.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/ProxyFilter.java @@ -65,7 +65,7 @@ public NextAction handleWrite(FilterChainContext ctx) throws IOException { org.glassfish.grizzly.http.HttpContent content = ctx.getMessage(); HttpRequestPacket request = (HttpRequestPacket) content.getHttpHeader(); - HttpTxContext context = HttpTxContext.get(ctx.getConnection()); + HttpTxContext context = HttpTxContext.get(ctx); assert(context != null); Request req = context.getRequest(); if (!secure) { diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/SSLSwitchingEvent.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/SSLSwitchingEvent.java index 84ae75f26c..17612dc953 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/SSLSwitchingEvent.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/SSLSwitchingEvent.java @@ -16,7 +16,6 @@ import org.glassfish.grizzly.Connection; import org.glassfish.grizzly.filterchain.FilterChainEvent; -import java.util.concurrent.Callable; /** * {@link FilterChainEvent} to dynamically enable/disable the SSLFilter on diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/AuthorizationHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/AuthorizationHandler.java index 2ee3ef7b08..d9bf914507 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/AuthorizationHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/AuthorizationHandler.java @@ -103,12 +103,13 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpTxContext newContext = httpTransactionContext.copy(); httpTransactionContext.setFuture(null); - HttpTxContext.set(c, newContext); + HttpTxContext.set(ctx, newContext); newContext.setInvocationStatus(STOP); httpTransactionContext.getProvider().execute(c, req, httpTransactionContext.getHandler(), - httpTransactionContext.getFuture()); + httpTransactionContext.getFuture(), + newContext); return false; } catch (Exception e) { httpTransactionContext.abort(e); diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java index dbe8ff5d9a..4720763ca3 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java @@ -23,6 +23,7 @@ import org.asynchttpclient.util.Base64; import org.glassfish.grizzly.Connection; import org.glassfish.grizzly.filterchain.FilterChainContext; +import org.glassfish.grizzly.http.HttpContext; import org.glassfish.grizzly.http.HttpResponsePacket; import org.glassfish.grizzly.http.util.Header; import org.glassfish.grizzly.http.util.HttpStatus; @@ -149,7 +150,7 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpTxContext newContext = httpTransactionContext.copy(); httpTransactionContext.setFuture(null); - HttpTxContext.set(c, newContext); + HttpTxContext.set(ctx, newContext); newContext.setInvocationStatus(tempInvocationStatus); @@ -163,18 +164,18 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, "Negotiate " + challengeHeader); - return executeRequest(httpTransactionContext, req, c); + return executeRequest(httpTransactionContext, req, c, newContext); } else if (isNTLMSecondHandShake(proxyAuth)) { final Connection c = ctx.getConnection(); final HttpTxContext newContext = httpTransactionContext.copy(); httpTransactionContext.setFuture(null); - HttpTxContext.set(c, newContext); + HttpTxContext.set(ctx, newContext); newContext.setInvocationStatus(tempInvocationStatus); - return executeRequest(httpTransactionContext, req, c); + return executeRequest(httpTransactionContext, req, c, newContext); } else { final Connection c = getConnectionForNextRequest(ctx, @@ -184,12 +185,12 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpTxContext newContext = httpTransactionContext.copy(); httpTransactionContext.setFuture(null); - HttpTxContext.set(c, newContext); + HttpTxContext.set(ctx, newContext); newContext.setInvocationStatus(tempInvocationStatus); //NTLM needs the same connection to be used for exchange of tokens - return executeRequest(httpTransactionContext, req, c); + return executeRequest(httpTransactionContext, req, c, newContext); } } catch (Exception e) { httpTransactionContext.abort(e); @@ -200,11 +201,12 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, private boolean executeRequest( final HttpTxContext httpTransactionContext, - final Request req, final Connection c) { + final Request req, final Connection c, final HttpTxContext httpTxContext) { httpTransactionContext.getProvider().execute(c, req, httpTransactionContext.getHandler(), - httpTransactionContext.getFuture()); + httpTransactionContext.getFuture(), + httpTxContext); return false; } diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/RedirectHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/RedirectHandler.java index 448d004c16..4a17602a3f 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/RedirectHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/RedirectHandler.java @@ -89,11 +89,12 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, newContext.setInvocationStatus(CONTINUE); newContext.setRequest(requestToSend); newContext.setRequestUrl(requestToSend.getUrl()); - HttpTxContext.set(c, newContext); + HttpTxContext.set(ctx, newContext); httpTransactionContext.getProvider().execute(c, requestToSend, newContext.getHandler(), - newContext.getFuture()); + newContext.getFuture(), + newContext); return false; } catch (Exception e) { httpTransactionContext.abort(e); From 078d86af833dbfbda824fd686befbf40c79464c5 Mon Sep 17 00:00:00 2001 From: Ryan Lubke Date: Fri, 27 Sep 2013 13:09:18 -0700 Subject: [PATCH 0002/2254] Uptake grizzly 2.3.7-SNAPSHOT. This release includes change necessary to support SPDY multiplexing with AHC. Simple test of 20 concurrent requests over a single connection to https://www.google.com confirm multiplexing is working. --- providers/grizzly/pom.xml | 2 +- .../providers/grizzly/EventHandler.java | 2 +- .../grizzly/GrizzlyAsyncHttpProvider.java | 3 +-- .../filters/AsyncHttpClientEventFilter.java | 7 ------ .../filters/AsyncHttpClientFilter.java | 22 ++++++++++++++++++- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/providers/grizzly/pom.xml b/providers/grizzly/pom.xml index 775b199f1e..ab9d594d5b 100644 --- a/providers/grizzly/pom.xml +++ b/providers/grizzly/pom.xml @@ -14,7 +14,7 @@ - 2.3.6 + 2.3.7-SNAPSHOT 1.0 diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java index 0b5ff84d7d..c0e28d05c1 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java @@ -428,7 +428,7 @@ private static HttpTxContext cleanup(final FilterChainContext ctx) { final HttpTxContext context = HttpTxContext.get(ctx); HttpTxContext.remove(ctx, context); - if (!Utils.isIgnored(c)) { + if (!Utils.isSpdyConnection(c) && !Utils.isIgnored(c)) { final ConnectionManager manager = context.getProvider().getConnectionManager(); //if (!manager.canReturnConnection(c)) { diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java index f03decb5a9..a375d815de 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java @@ -44,7 +44,6 @@ import org.glassfish.grizzly.http.ContentEncoding; import org.glassfish.grizzly.http.GZipContentEncoding; import org.glassfish.grizzly.http.HttpClientFilter; -import org.glassfish.grizzly.http.HttpContext; import org.glassfish.grizzly.npn.ClientSideNegotiator; import org.glassfish.grizzly.spdy.NextProtoNegSupport; import org.glassfish.grizzly.spdy.SpdyFramingFilter; @@ -485,7 +484,7 @@ public void updated(WriteResult result) { void timeout(final Connection c) { final String key = HttpTxContext.class.getName(); - HttpTxContext ctx = null; + HttpTxContext ctx; if (!Utils.isSpdyConnection(c)) { ctx = (HttpTxContext) c.getAttributes().getAttribute(key); if (ctx != null) { diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientEventFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientEventFilter.java index eb15b9a9aa..747d9982ad 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientEventFilter.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientEventFilter.java @@ -53,13 +53,6 @@ public AsyncHttpClientEventFilter(final EventHandler eventHandler, this.eventHandler = eventHandler; } - @Override - public NextAction handleRead(FilterChainContext ctx) throws IOException { - final Connection c = ctx.getConnection(); - HttpContext.newInstance(ctx, c, c, c); - return super.handleRead(ctx); - } - @Override public void exceptionOccurred(FilterChainContext ctx, Throwable error) { eventHandler.exceptionOccurred(ctx, error); diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java index cda70498c6..d2724f5925 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java @@ -54,6 +54,8 @@ import org.glassfish.grizzly.http.util.Header; import org.glassfish.grizzly.http.util.MimeHeaders; import org.glassfish.grizzly.impl.SafeFutureImpl; +import org.glassfish.grizzly.spdy.SpdySession; +import org.glassfish.grizzly.spdy.SpdyStream; import org.glassfish.grizzly.ssl.SSLConnectionContext; import org.glassfish.grizzly.ssl.SSLUtils; import org.glassfish.grizzly.websockets.Version; @@ -68,6 +70,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.locks.Lock; import org.glassfish.grizzly.Connection; import org.glassfish.grizzly.http.HttpContent; @@ -299,7 +302,24 @@ private boolean sendAsGrizzlyRequest(final RequestInfoHolder requestInfoHolder, sendingCtx = checkAndHandleFilterChainUpdate(ctx, sendingCtx); } final Connection c = ctx.getConnection(); - HttpContext.newInstance(ctx, c, c, c); + System.out.println("*** CONNECTION: " + c); + if (!Utils.isSpdyConnection(c)) { + HttpContext.newInstance(ctx, c, c, c); + } else { + SpdySession session = SpdySession.get(c); + final Lock lock = session.getNewClientStreamLock(); + try { + lock.lock(); + SpdyStream stream = session.openStream( + requestPacketLocal, + session.getNextLocalStreamId(), + 0, 0, 0, false, !requestPacketLocal.isExpectContent()); + HttpContext.newInstance(ctx, stream, stream, stream); + } finally { + lock.unlock(); + } + + } HttpTxContext.set(ctx, httpTxContext); return sendRequest(sendingCtx, request, requestPacketLocal); From dd2cc874df827332798818003f8bfc56d67377ae Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Oct 2013 11:28:04 +0200 Subject: [PATCH 0003/2254] Drop Netty 3 support, move Spnego into api, close #316 --- .../filter/ResponseFilter.java | 2 +- .../listener/TransferCompletionHandler.java | 62 +- .../asynchttpclient}/spnego/SpnegoEngine.java | 45 +- .../spnego/SpnegoTokenGenerator.java | 2 +- .../async/SimpleAsyncHttpClientTest.java | 85 +- .../filters/AsyncHttpClientFilter.java | 4 +- providers/netty/pom.xml | 41 +- .../providers/netty/BodyChunkedInput.java | 76 - .../providers/netty/BodyFileRegion.java | 57 - .../providers/netty}/Callback.java | 4 +- .../providers/netty}/Constants.java | 2 +- .../providers/netty}/DiscardEvent.java | 2 +- .../netty/FeedableBodyGenerator.java | 119 - .../netty/NettyAsyncHttpProvider.java | 2329 +---------------- .../netty/NettyAsyncHttpProviderConfig.java | 79 +- .../providers/netty/NettyConnectListener.java | 153 -- .../providers/netty/NettyConnectionsPool.java | 294 --- .../providers/netty/NettyResponse.java | 121 - .../providers/netty/NettyResponseFuture.java | 515 ---- .../providers/netty/NettyWebSocket.java | 236 -- .../providers/netty/Protocol.java | 27 - .../providers/netty/ResponseBodyPart.java | 134 - .../providers/netty/ResponseHeaders.java | 74 - .../providers/netty/ResponseStatus.java | 75 - .../providers/netty/WebSocketUtil.java | 72 - .../providers/netty/channel}/Channels.java | 176 +- .../netty/channel}/NettyConnectionsPool.java | 4 +- .../netty/channel/NonConnectionsPool.java} | 35 +- .../providers/netty/future}/FutureReaper.java | 9 +- .../netty/future}/NettyResponseFuture.java | 6 +- .../netty/future}/NettyResponseFutures.java | 2 +- .../providers/netty/handler/HttpProtocol.java | 450 ++++ .../netty/handler/NettyChannelHandler.java | 199 ++ .../providers/netty/handler/Protocol.java | 137 + .../netty/handler/WebSocketProtocol.java | 221 ++ .../netty/request}/BodyChunkedInput.java | 2 +- .../netty/request}/BodyFileRegion.java | 2 +- .../netty/request}/FeedableBodyGenerator.java | 2 +- .../netty/request}/NettyConnectListener.java | 23 +- .../netty/request}/NettyRequestSender.java | 298 ++- .../netty/request}/NettyRequests.java | 37 +- .../netty/request}/ProgressListener.java | 36 +- .../netty/response}/NettyResponse.java | 2 +- .../netty/response}/ResponseBodyPart.java | 4 +- .../netty/response}/ResponseHeaders.java | 2 +- .../netty/response}/ResponseStatus.java | 2 +- .../providers/netty/spnego/SpnegoEngine.java | 172 -- .../providers/netty}/util/ByteBufUtil.java | 2 +- .../netty/util/ChannelBufferUtil.java | 35 - .../netty/util/CleanupChannelGroup.java | 34 +- .../providers/netty}/util/HttpUtil.java | 2 +- .../providers/netty/ws}/NettyWebSocket.java | 130 +- .../providers/netty/ws}/WebSocketUtil.java | 2 +- .../netty/NettyAsyncHttpProviderTest.java | 23 - .../netty/NettyAsyncProviderBasicTest.java | 8 +- .../netty/NettyAsyncProviderPipelineTest.java | 58 +- .../netty/NettyAsyncResponseTest.java | 21 +- .../providers/netty/NettyAuthTimeoutTest.java | 1 - .../providers/netty/NettyBasicAuthTest.java | 7 - .../netty/NettyConnectionPoolTest.java | 7 +- .../netty/NettyMultipartUploadTest.java | 2 - .../netty/NettyPerRequestTimeoutTest.java | 2 +- .../providers/netty/NettyProviderUtil.java | 11 +- .../NettyRequestThrottleTimeoutTest.java | 23 +- .../netty/RetryNonBlockingIssue.java | 11 +- .../netty/websocket/NettyRedirectTest.java | 2 - .../netty/websocket/NettyTextMessageTest.java | 2 - providers/netty4/pom.xml | 51 - .../netty4/NettyAsyncHttpProvider.java | 94 - .../netty4/NettyAsyncHttpProviderConfig.java | 227 -- .../providers/netty4/NettyChannelHandler.java | 873 ------ .../providers/netty4/OptimizedFileRegion.java | 68 - .../providers/netty4/Protocol.java | 24 - .../providers/netty4/ThreadLocalBoolean.java | 19 - .../netty4/spnego/SpnegoTokenGenerator.java | 55 - .../netty4/util/CleanupChannelGroup.java | 110 - .../netty4/NettyAsyncHttpProviderTest.java | 25 - .../netty4/NettyAsyncProviderBasicTest.java | 36 - .../NettyAsyncProviderPipelineTest.java | 89 - .../netty4/NettyAsyncResponseTest.java | 94 - .../netty4/NettyAsyncStreamHandlerTest.java | 25 - .../netty4/NettyAsyncStreamLifecycleTest.java | 24 - .../netty4/NettyAuthTimeoutTest.java | 27 - .../providers/netty4/NettyBasicAuthTest.java | 31 - .../providers/netty4/NettyBasicHttpsTest.java | 25 - .../providers/netty4/NettyBodyChunkTest.java | 25 - .../NettyBodyDeferringAsyncHandlerTest.java | 26 - .../netty4/NettyByteBufferCapacityTest.java | 25 - .../providers/netty4/NettyChunkingTest.java | 12 - .../netty4/NettyComplexClientTest.java | 25 - .../netty4/NettyConnectionPoolTest.java | 115 - .../providers/netty4/NettyDigestAuthTest.java | 24 - .../providers/netty4/NettyEmptyBodyTest.java | 25 - .../netty4/NettyErrorResponseTest.java | 25 - .../netty4/NettyExpect100ContinueTest.java | 25 - .../netty4/NettyFilePartLargeFileTest.java | 24 - .../providers/netty4/NettyFilterTest.java | 24 - .../netty4/NettyFollowingThreadTest.java | 25 - .../providers/netty4/NettyHead302Test.java | 25 - .../netty4/NettyHostnameVerifierTest.java | 25 - .../netty4/NettyHttpToHttpsRedirectTest.java | 24 - .../netty4/NettyIdleStateHandlerTest.java | 24 - .../netty4/NettyInputStreamTest.java | 24 - .../netty4/NettyListenableFutureTest.java | 26 - .../netty4/NettyMaxConnectionsInThreads.java | 23 - .../netty4/NettyMaxTotalConnectionTest.java | 24 - .../netty4/NettyMultipartUploadTest.java | 29 - .../netty4/NettyMultipleHeaderTest.java | 24 - .../netty4/NettyNoNullResponseTest.java | 24 - .../NettyNonAsciiContentLengthTest.java | 25 - .../netty4/NettyParamEncodingTest.java | 24 - .../NettyPerRequestRelative302Test.java | 24 - .../netty4/NettyPerRequestTimeoutTest.java | 33 - .../netty4/NettyPostRedirectGetTest.java | 27 - .../providers/netty4/NettyPostWithQSTest.java | 24 - .../providers/netty4/NettyProxyTest.java | 25 - .../netty4/NettyProxyTunnellingTest.java | 29 - .../netty4/NettyPutLargeFileTest.java | 24 - .../netty4/NettyQueryParametersTest.java | 24 - .../providers/netty4/NettyRC10KTest.java | 24 - .../NettyRedirectConnectionUsageTest.java | 24 - .../netty4/NettyRelative302Test.java | 25 - .../providers/netty4/NettyRemoteSiteTest.java | 28 - .../NettyRequestThrottleTimeoutTest.java | 135 - .../netty4/NettyRetryRequestTest.java | 28 - .../NettySimpleAsyncHttpClientTest.java | 35 - .../netty4/NettyTransferListenerTest.java | 24 - .../netty4/NettyWebDavBasicTest.java | 24 - .../netty4/NettyZeroCopyFileTest.java | 24 - .../netty4/RetryNonBlockingIssue.java | 266 -- .../websocket/NettyByteMessageTest.java | 25 - .../NettyCloseCodeReasonMsgTest.java | 27 - .../netty4/websocket/NettyRedirectTest.java | 26 - .../websocket/NettyTextMessageTest.java | 25 - providers/netty4/src/test/resources/300k.png | Bin 265495 -> 0 bytes .../src/test/resources/SimpleTextFile.txt | 1 - .../netty4/src/test/resources/client.keystore | Bin 1277 -> 0 bytes .../netty4/src/test/resources/gzip.txt.gz | Bin 47 -> 0 bytes .../src/test/resources/logback-test.xml | 13 - .../src/test/resources/realm.properties | 1 - .../src/test/resources/ssltest-cacerts.jks | Bin 29888 -> 0 bytes .../src/test/resources/ssltest-keystore.jks | Bin 1445 -> 0 bytes .../netty4/src/test/resources/textfile.txt | 1 - .../netty4/src/test/resources/textfile2.txt | 1 - providers/pom.xml | 1 - 145 files changed, 1727 insertions(+), 8673 deletions(-) rename {providers/netty4/src/main/java/org/asynchttpclient/providers/netty4 => api/src/main/java/org/asynchttpclient}/spnego/SpnegoEngine.java (84%) rename {providers/netty/src/main/java/org/asynchttpclient/providers/netty => api/src/main/java/org/asynchttpclient}/spnego/SpnegoTokenGenerator.java (97%) delete mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/BodyChunkedInput.java delete mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/BodyFileRegion.java rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty}/Callback.java (72%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty}/Constants.java (86%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty}/DiscardEvent.java (66%) delete mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/FeedableBodyGenerator.java delete mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyConnectListener.java delete mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyConnectionsPool.java delete mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyResponse.java delete mode 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyResponseFuture.java delete mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyWebSocket.java delete mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/Protocol.java delete mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/ResponseBodyPart.java delete mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/ResponseHeaders.java delete mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/ResponseStatus.java delete mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/WebSocketUtil.java rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/channel}/Channels.java (84%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/channel}/NettyConnectionsPool.java (98%) rename providers/{netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyProviderUtil.java => netty/src/main/java/org/asynchttpclient/providers/netty/channel/NonConnectionsPool.java} (50%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/future}/FutureReaper.java (91%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/future}/NettyResponseFuture.java (98%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/future}/NettyResponseFutures.java (98%) create mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java create mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/NettyChannelHandler.java create mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Protocol.java create mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/request}/BodyChunkedInput.java (97%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/request}/BodyFileRegion.java (97%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/request}/FeedableBodyGenerator.java (98%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/request}/NettyConnectListener.java (87%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/request}/NettyRequestSender.java (64%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/request}/NettyRequests.java (92%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/request}/ProgressListener.java (70%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/response}/NettyResponse.java (98%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/response}/ResponseBodyPart.java (95%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/response}/ResponseHeaders.java (97%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/response}/ResponseStatus.java (98%) delete mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/spnego/SpnegoEngine.java rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty}/util/ByteBufUtil.java (95%) delete mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ChannelBufferUtil.java rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty}/util/HttpUtil.java (94%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/ws}/NettyWebSocket.java (71%) rename providers/{netty4/src/main/java/org/asynchttpclient/providers/netty4 => netty/src/main/java/org/asynchttpclient/providers/netty/ws}/WebSocketUtil.java (98%) delete mode 100644 providers/netty4/pom.xml delete mode 100644 providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyAsyncHttpProvider.java delete mode 100644 providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyAsyncHttpProviderConfig.java delete mode 100644 providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyChannelHandler.java delete mode 100644 providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/OptimizedFileRegion.java delete mode 100644 providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/Protocol.java delete mode 100644 providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/ThreadLocalBoolean.java delete mode 100644 providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/spnego/SpnegoTokenGenerator.java delete mode 100644 providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/util/CleanupChannelGroup.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncHttpProviderTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncProviderBasicTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncProviderPipelineTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncResponseTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncStreamHandlerTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncStreamLifecycleTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAuthTimeoutTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyBasicAuthTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyBasicHttpsTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyBodyChunkTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyBodyDeferringAsyncHandlerTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyByteBufferCapacityTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyChunkingTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyComplexClientTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyConnectionPoolTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyDigestAuthTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyEmptyBodyTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyErrorResponseTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyExpect100ContinueTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyFilePartLargeFileTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyFilterTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyFollowingThreadTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyHead302Test.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyHostnameVerifierTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyHttpToHttpsRedirectTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyIdleStateHandlerTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyInputStreamTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyListenableFutureTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyMaxConnectionsInThreads.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyMaxTotalConnectionTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyMultipartUploadTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyMultipleHeaderTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyNoNullResponseTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyNonAsciiContentLengthTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyParamEncodingTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPerRequestRelative302Test.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPerRequestTimeoutTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPostRedirectGetTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPostWithQSTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyProxyTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyProxyTunnellingTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPutLargeFileTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyQueryParametersTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRC10KTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRedirectConnectionUsageTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRelative302Test.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRemoteSiteTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRequestThrottleTimeoutTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRetryRequestTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettySimpleAsyncHttpClientTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyTransferListenerTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyWebDavBasicTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyZeroCopyFileTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/RetryNonBlockingIssue.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/websocket/NettyByteMessageTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/websocket/NettyCloseCodeReasonMsgTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/websocket/NettyRedirectTest.java delete mode 100644 providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/websocket/NettyTextMessageTest.java delete mode 100644 providers/netty4/src/test/resources/300k.png delete mode 100644 providers/netty4/src/test/resources/SimpleTextFile.txt delete mode 100644 providers/netty4/src/test/resources/client.keystore delete mode 100644 providers/netty4/src/test/resources/gzip.txt.gz delete mode 100644 providers/netty4/src/test/resources/logback-test.xml delete mode 100644 providers/netty4/src/test/resources/realm.properties delete mode 100644 providers/netty4/src/test/resources/ssltest-cacerts.jks delete mode 100644 providers/netty4/src/test/resources/ssltest-keystore.jks delete mode 100644 providers/netty4/src/test/resources/textfile.txt delete mode 100644 providers/netty4/src/test/resources/textfile2.txt diff --git a/api/src/main/java/org/asynchttpclient/filter/ResponseFilter.java b/api/src/main/java/org/asynchttpclient/filter/ResponseFilter.java index 8c26bcca9a..cdad3ce23c 100644 --- a/api/src/main/java/org/asynchttpclient/filter/ResponseFilter.java +++ b/api/src/main/java/org/asynchttpclient/filter/ResponseFilter.java @@ -15,7 +15,7 @@ /** * A Filter interface that gets invoked before making the processing of the response bytes. {@link ResponseFilter} are invoked * before the actual response's status code get processed. That means authorization, proxy authentication and redirects - * processing hasn't occured when {@link ResponseFilter} gets invoked. + * processing hasn't occurred when {@link ResponseFilter} gets invoked. */ public interface ResponseFilter { diff --git a/api/src/main/java/org/asynchttpclient/listener/TransferCompletionHandler.java b/api/src/main/java/org/asynchttpclient/listener/TransferCompletionHandler.java index ff7417be3b..94a5888af5 100644 --- a/api/src/main/java/org/asynchttpclient/listener/TransferCompletionHandler.java +++ b/api/src/main/java/org/asynchttpclient/listener/TransferCompletionHandler.java @@ -13,7 +13,6 @@ package org.asynchttpclient.listener; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicLong; import org.asynchttpclient.AsyncCompletionHandlerBase; import org.asynchttpclient.FluentCaseInsensitiveStringsMap; @@ -42,7 +41,7 @@ * public void onBytesReceived(ByteBuffer buffer) { * } *

- * public void onBytesSent(ByteBuffer buffer) { + * public void onBytesSent(long amount, long current, long total) { * } *

* public void onRequestResponseCompleted() { @@ -61,9 +60,7 @@ public class TransferCompletionHandler extends AsyncCompletionHandlerBase { private final static Logger logger = LoggerFactory.getLogger(TransferCompletionHandler.class); private final ConcurrentLinkedQueue listeners = new ConcurrentLinkedQueue(); private final boolean accumulateResponseBytes; - private TransferAdapter transferAdapter; - private AtomicLong bytesTransferred = new AtomicLong(0); - private AtomicLong totalBytesToTransfer = new AtomicLong(-1); + private FluentCaseInsensitiveStringsMap headers; /** * Create a TransferCompletionHandler that will not accumulate bytes. The resulting {@link org.asynchttpclient.Response#getResponseBody()}, @@ -109,13 +106,13 @@ public TransferCompletionHandler removeTransferListener(TransferListener t) { } /** - * Associate a {@link TransferCompletionHandler.TransferAdapter} with this listener. + * Set headers to this listener. * - * @param transferAdapter - * {@link TransferAdapter} + * @param headers + * {@link FluentCaseInsensitiveStringsMap} */ - public void transferAdapter(TransferAdapter transferAdapter) { - this.transferAdapter = transferAdapter; + public void headers(FluentCaseInsensitiveStringsMap headers) { + this.headers = headers; } @Override @@ -136,49 +133,20 @@ public STATE onBodyPartReceived(final HttpResponseBodyPart content) throws Excep @Override public Response onCompleted(Response response) throws Exception { - if (bytesTransferred.get() > 0L) { - // onContentWriteCompleted hasn't been notified, it would have been set to -1L (async race) - onContentWriteCompleted(); - } fireOnEnd(); return response; } @Override public STATE onHeaderWriteCompleted() { - if (transferAdapter != null) { - fireOnHeadersSent(transferAdapter.getHeaders()); - } - return STATE.CONTINUE; - } - - @Override - public STATE onContentWriteCompleted() { - // onContentWriteProgress might not have been called on last write - long transferred = bytesTransferred.getAndSet(-1L); - long expected = totalBytesToTransfer.get(); - - if (expected <= 0L && transferAdapter != null) { - FluentCaseInsensitiveStringsMap headers = transferAdapter.getHeaders(); - String contentLengthString = headers.getFirstValue("Content-Length"); - if (contentLengthString != null) - expected = Long.valueOf(contentLengthString); - } - - if (expected > 0L && transferred != expected) { - fireOnBytesSent(expected - transferred, expected, expected); + if (headers != null) { + fireOnHeadersSent(headers); } - return STATE.CONTINUE; } @Override public STATE onContentWriteProgress(long amount, long current, long total) { - bytesTransferred.addAndGet(amount); - - if (total > 0L) - totalBytesToTransfer.set(total); - fireOnBytesSent(amount, current, total); return STATE.CONTINUE; } @@ -247,16 +215,4 @@ private void fireOnThrowable(Throwable t) { } } } - - public static class TransferAdapter { - private final FluentCaseInsensitiveStringsMap headers; - - public TransferAdapter(FluentCaseInsensitiveStringsMap headers) { - this.headers = headers; - } - - public FluentCaseInsensitiveStringsMap getHeaders() { - return headers; - } - } } diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/spnego/SpnegoEngine.java b/api/src/main/java/org/asynchttpclient/spnego/SpnegoEngine.java similarity index 84% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/spnego/SpnegoEngine.java rename to api/src/main/java/org/asynchttpclient/spnego/SpnegoEngine.java index eeeb83365f..aa42c011aa 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/spnego/SpnegoEngine.java +++ b/api/src/main/java/org/asynchttpclient/spnego/SpnegoEngine.java @@ -35,7 +35,7 @@ * . */ -package org.asynchttpclient.providers.netty4.spnego; +package org.asynchttpclient.spnego; import org.asynchttpclient.util.Base64; import org.ietf.jgss.GSSContext; @@ -49,9 +49,8 @@ import java.io.IOException; /** - * SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication - * scheme. - * + * SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication scheme. + * * @since 4.1 */ public class SpnegoEngine { @@ -70,8 +69,9 @@ public SpnegoEngine(final SpnegoTokenGenerator spnegoGenerator) { public SpnegoEngine() { this(null); } - + private static SpnegoEngine instance; + public static SpnegoEngine instance() { if (instance == null) instance = new SpnegoEngine(); @@ -85,17 +85,16 @@ public String generateToken(String server) throws Throwable { try { log.debug("init {}", server); - /* Using the SPNEGO OID is the correct method. - * Kerberos v5 works for IIS but not JBoss. Unwrapping - * the initial token when using SPNEGO OID looks like what is - * described here... - * + /* + * Using the SPNEGO OID is the correct method. Kerberos v5 works for IIS but not JBoss. Unwrapping the initial token when using SPNEGO OID looks like what is described + * here... + * * http://msdn.microsoft.com/en-us/library/ms995330.aspx - * + * * Another helpful URL... - * + * * http://publib.boulder.ibm.com/infocenter/wasinfo/v7r0/index.jsp?topic=/com.ibm.websphere.express.doc/info/exp/ae/tsec_SPNEGO_token.html - * + * * Unfortunately SPNEGO is JRE >=1.6. */ @@ -106,9 +105,7 @@ public String generateToken(String server) throws Throwable { try { GSSManager manager = GSSManager.getInstance(); GSSName serverName = manager.createName("HTTP@" + server, GSSName.NT_HOSTBASED_SERVICE); - gssContext = manager.createContext( - serverName.canonicalize(negotiationOid), negotiationOid, null, - GSSContext.DEFAULT_LIFETIME); + gssContext = manager.createContext(serverName.canonicalize(negotiationOid), negotiationOid, null, GSSContext.DEFAULT_LIFETIME); gssContext.requestMutualAuth(true); gssContext.requestCredDeleg(true); } catch (GSSException ex) { @@ -124,14 +121,12 @@ public String generateToken(String server) throws Throwable { } if (tryKerberos) { - /* Kerberos v5 GSS-API mechanism defined in RFC 1964.*/ + /* Kerberos v5 GSS-API mechanism defined in RFC 1964. */ log.debug("Using Kerberos MECH {}", KERBEROS_OID); negotiationOid = new Oid(KERBEROS_OID); GSSManager manager = GSSManager.getInstance(); GSSName serverName = manager.createName("HTTP@" + server, GSSName.NT_HOSTBASED_SERVICE); - gssContext = manager.createContext( - serverName.canonicalize(negotiationOid), negotiationOid, null, - GSSContext.DEFAULT_LIFETIME); + gssContext = manager.createContext(serverName.canonicalize(negotiationOid), negotiationOid, null, GSSContext.DEFAULT_LIFETIME); gssContext.requestMutualAuth(true); gssContext.requestCredDeleg(true); } @@ -147,8 +142,7 @@ public String generateToken(String server) throws Throwable { } /* - * IIS accepts Kerberos and SPNEGO tokens. Some other servers Jboss, Glassfish? - * seem to only accept SPNEGO. Below wraps Kerberos into SPNEGO token. + * IIS accepts Kerberos and SPNEGO tokens. Some other servers Jboss, Glassfish? seem to only accept SPNEGO. Below wraps Kerberos into SPNEGO token. */ if (spnegoGenerator != null && negotiationOid.toString().equals(KERBEROS_OID)) { token = spnegoGenerator.generateSpnegoDERObject(token); @@ -162,14 +156,11 @@ public String generateToken(String server) throws Throwable { return tokenstr; } catch (GSSException gsse) { log.error("generateToken", gsse); - if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL - || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) + if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) throw new Exception(gsse.getMessage(), gsse); if (gsse.getMajor() == GSSException.NO_CRED) throw new Exception(gsse.getMessage(), gsse); - if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN - || gsse.getMajor() == GSSException.DUPLICATE_TOKEN - || gsse.getMajor() == GSSException.OLD_TOKEN) + if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN || gsse.getMajor() == GSSException.DUPLICATE_TOKEN || gsse.getMajor() == GSSException.OLD_TOKEN) throw new Exception(gsse.getMessage(), gsse); // other error throw new Exception(gsse.getMessage()); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/spnego/SpnegoTokenGenerator.java b/api/src/main/java/org/asynchttpclient/spnego/SpnegoTokenGenerator.java similarity index 97% rename from providers/netty/src/main/java/org/asynchttpclient/providers/netty/spnego/SpnegoTokenGenerator.java rename to api/src/main/java/org/asynchttpclient/spnego/SpnegoTokenGenerator.java index 20d6dcc1ad..ead1c19335 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/spnego/SpnegoTokenGenerator.java +++ b/api/src/main/java/org/asynchttpclient/spnego/SpnegoTokenGenerator.java @@ -36,7 +36,7 @@ * */ -package org.asynchttpclient.providers.netty.spnego; +package org.asynchttpclient.spnego; import java.io.IOException; diff --git a/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java b/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java index ef0dfacf51..49a340fde9 100644 --- a/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java +++ b/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java @@ -18,6 +18,9 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.concurrent.Future; import org.asynchttpclient.ByteArrayPart; @@ -40,11 +43,11 @@ public abstract class SimpleAsyncHttpClientTest extends AbstractBasicTest { @Test(groups = { "standalone", "default_provider" }) public void inpuStreamBodyConsumerTest() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setIdleConnectionInPoolTimeoutInMs(100).setMaximumConnectionsTotal(50).setRequestTimeoutInMs(5 * 60 * 1000).setUrl(getTargetUrl()).setHeader("Content-Type", "text/html").build(); + SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setIdleConnectionInPoolTimeoutInMs(100) + .setMaximumConnectionsTotal(50).setRequestTimeoutInMs(5 * 60 * 1000).setUrl(getTargetUrl()).setHeader("Content-Type", "text/html").build(); try { Future future = client.post(new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes()))); - System.out.println("waiting for response"); Response response = future.get(); assertEquals(response.getStatusCode(), 200); assertEquals(response.getResponseBody(), MY_MESSAGE); @@ -56,12 +59,12 @@ public void inpuStreamBodyConsumerTest() throws Exception { @Test(groups = { "standalone", "default_provider" }) public void stringBuilderBodyConsumerTest() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setIdleConnectionInPoolTimeoutInMs(100).setMaximumConnectionsTotal(50).setRequestTimeoutInMs(5 * 60 * 1000).setUrl(getTargetUrl()).setHeader("Content-Type", "text/html").build(); + SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setIdleConnectionInPoolTimeoutInMs(100) + .setMaximumConnectionsTotal(50).setRequestTimeoutInMs(5 * 60 * 1000).setUrl(getTargetUrl()).setHeader("Content-Type", "text/html").build(); try { StringBuilder s = new StringBuilder(); Future future = client.post(new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes())), new AppendableBodyConsumer(s)); - System.out.println("waiting for response"); Response response = future.get(); assertEquals(response.getStatusCode(), 200); assertEquals(s.toString(), MY_MESSAGE); @@ -73,12 +76,12 @@ public void stringBuilderBodyConsumerTest() throws Exception { @Test(groups = { "standalone", "default_provider" }) public void byteArrayOutputStreamBodyConsumerTest() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setIdleConnectionInPoolTimeoutInMs(100).setMaximumConnectionsTotal(50).setRequestTimeoutInMs(5 * 60 * 1000).setUrl(getTargetUrl()).setHeader("Content-Type", "text/html").build(); + SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setIdleConnectionInPoolTimeoutInMs(100) + .setMaximumConnectionsTotal(50).setRequestTimeoutInMs(5 * 60 * 1000).setUrl(getTargetUrl()).setHeader("Content-Type", "text/html").build(); try { ByteArrayOutputStream o = new ByteArrayOutputStream(10); Future future = client.post(new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes())), new OutputStreamBodyConsumer(o)); - System.out.println("waiting for response"); Response response = future.get(); assertEquals(response.getStatusCode(), 200); assertEquals(o.toString(), MY_MESSAGE); @@ -95,7 +98,6 @@ public void requestByteArrayOutputStreamBodyConsumerTest() throws Exception { ByteArrayOutputStream o = new ByteArrayOutputStream(10); Future future = client.post(new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes())), new OutputStreamBodyConsumer(o)); - System.out.println("waiting for response"); Response response = future.get(); assertEquals(response.getStatusCode(), 200); assertEquals(o.toString(), MY_MESSAGE); @@ -109,7 +111,8 @@ public void requestByteArrayOutputStreamBodyConsumerTest() throws Exception { */ @Test(groups = { "standalone", "default_provider" }, enabled = true) public void testPutZeroBytesFileTest() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setIdleConnectionInPoolTimeoutInMs(100).setMaximumConnectionsTotal(50).setRequestTimeoutInMs(5 * 1000).setUrl(getTargetUrl() + "/testPutZeroBytesFileTest.txt").setHeader("Content-Type", "text/plain") + SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setIdleConnectionInPoolTimeoutInMs(100) + .setMaximumConnectionsTotal(50).setRequestTimeoutInMs(5 * 1000).setUrl(getTargetUrl() + "/testPutZeroBytesFileTest.txt").setHeader("Content-Type", "text/plain") .build(); try { File tmpfile = File.createTempFile("testPutZeroBytesFile", ".tmp"); @@ -164,37 +167,66 @@ public void testDeriveOverrideURL() throws Exception { @Test(groups = { "standalone", "default_provider" }) public void testSimpleTransferListener() throws Exception { + final List errors = Collections.synchronizedList(new ArrayList()); + SimpleAHCTransferListener listener = new SimpleAHCTransferListener() { public void onStatus(String url, int statusCode, String statusText) { - assertEquals(statusCode, 200); - assertEquals(url, getTargetUrl()); + try { + assertEquals(statusCode, 200); + assertEquals(url, getTargetUrl()); + } catch (Error e) { + errors.add(e); + throw e; + } } public void onHeaders(String url, HeaderMap headers) { - assertEquals(url, getTargetUrl()); - assertNotNull(headers); - assertTrue(!headers.isEmpty()); - assertEquals(headers.getFirstValue("X-Custom"), "custom"); + try { + assertEquals(url, getTargetUrl()); + assertNotNull(headers); + assertTrue(!headers.isEmpty()); + assertEquals(headers.getFirstValue("X-Custom"), "custom"); + } catch (Error e) { + errors.add(e); + throw e; + } } public void onCompleted(String url, int statusCode, String statusText) { - assertEquals(statusCode, 200); - assertEquals(url, getTargetUrl()); + try { + assertEquals(statusCode, 200); + assertEquals(url, getTargetUrl()); + } catch (Error e) { + errors.add(e); + throw e; + } } public void onBytesSent(String url, long amount, long current, long total) { - assertEquals(url, getTargetUrl()); - assertEquals(total, MY_MESSAGE.getBytes().length); + try { + assertEquals(url, getTargetUrl()); + // FIXME Netty bug, see https://github.com/netty/netty/issues/1855 +// assertEquals(total, MY_MESSAGE.getBytes().length); + } catch (Error e) { + errors.add(e); + throw e; + } } public void onBytesReceived(String url, long amount, long current, long total) { - assertEquals(url, getTargetUrl()); - assertEquals(total, -1); + try { + assertEquals(url, getTargetUrl()); + assertEquals(total, -1); + } catch (Error e) { + errors.add(e); + throw e; + } } }; - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setUrl(getTargetUrl()).setHeader("Custom", "custom").setListener(listener).build(); + SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setUrl(getTargetUrl()).setHeader("Custom", "custom") + .setListener(listener).build(); try { ByteArrayOutputStream o = new ByteArrayOutputStream(10); @@ -204,6 +236,14 @@ public void onBytesReceived(String url, long amount, long current, long total) { Future future = client.post(generator, consumer); Response response = future.get(); + + if (!errors.isEmpty()) { + for (Error e : errors) { + e.printStackTrace(); + } + throw errors.get(0); + } + assertEquals(response.getStatusCode(), 200); assertEquals(o.toString(), MY_MESSAGE); } finally { @@ -220,7 +260,8 @@ public void testNullUrl() throws Exception { } catch (NullPointerException ex) { fail(); } finally { - if (client != null) client.close(); + if (client != null) + client.close(); } } diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java index d2724f5925..0cd609ad8f 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java @@ -25,7 +25,6 @@ import org.asynchttpclient.Response; import org.asynchttpclient.UpgradeHandler; import org.asynchttpclient.listener.TransferCompletionHandler; -import org.asynchttpclient.listener.TransferCompletionHandler.TransferAdapter; import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; import org.asynchttpclient.providers.grizzly.GrizzlyResponseFuture; import org.asynchttpclient.providers.grizzly.HttpTxContext; @@ -395,8 +394,7 @@ private static void initTransferCompletionHandler(final Request request, if (h instanceof TransferCompletionHandler) { final FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(request.getHeaders()); - TransferCompletionHandler.class.cast(h) - .transferAdapter(new TransferAdapter(map)); + TransferCompletionHandler.class.cast(h).headers(map); } } diff --git a/providers/netty/pom.xml b/providers/netty/pom.xml index 1e1cf573c8..8905736367 100644 --- a/providers/netty/pom.xml +++ b/providers/netty/pom.xml @@ -1,6 +1,5 @@ - + org.asynchttpclient async-http-client-providers-parent @@ -8,16 +7,44 @@ 4.0.0 async-http-client-netty-provider - Asynchronous Http Client Netty Provider + Asynchronous Http Client Netty 4 Provider - The Async Http Client Netty Provider. + The Async Http Client Netty 4 Provider. + + + sonatype-releases + https://oss.sonatype.org/content/repositories/releases + + true + + + false + + + + sonatype-snapshots + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + io.netty - netty - 3.7.0.Final + netty-all + 4.0.10.Final-SNAPSHOT + + + org.javassist + javassist + 3.18.0-GA diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/BodyChunkedInput.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/BodyChunkedInput.java deleted file mode 100644 index 8c260cb25b..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/BodyChunkedInput.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty; - -import org.asynchttpclient.Body; -import org.jboss.netty.buffer.ChannelBuffers; -import org.jboss.netty.handler.stream.ChunkedInput; - -import java.nio.ByteBuffer; - -/** - * Adapts a {@link Body} to Netty's {@link ChunkedInput}. - */ -class BodyChunkedInput implements ChunkedInput { - - private static final int DEFAULT_CHUNK_SIZE = 8 * 1024; - - private final Body body; - private final int contentLength; - private final int chunkSize; - - private boolean endOfInput; - - public BodyChunkedInput(Body body) { - if (body == null) { - throw new IllegalArgumentException("no body specified"); - } - this.body = body; - contentLength = (int) body.getContentLength(); - if (contentLength <= 0) - chunkSize = DEFAULT_CHUNK_SIZE; - else - chunkSize = Math.min(contentLength, DEFAULT_CHUNK_SIZE); - } - - public boolean hasNextChunk() throws Exception { - // unused - throw new UnsupportedOperationException(); - } - - public Object nextChunk() throws Exception { - if (endOfInput) { - return null; - } else { - ByteBuffer buffer = ByteBuffer.allocate(chunkSize); - long r = body.read(buffer); - if (r < 0L) { - endOfInput = true; - return null; - } else { - endOfInput = r == contentLength || r < chunkSize && contentLength > 0; - buffer.flip(); - return ChannelBuffers.wrappedBuffer(buffer); - } - } - } - - public boolean isEndOfInput() throws Exception { - // called by ChunkedWriteHandler AFTER nextChunk - return endOfInput; - } - - public void close() throws Exception { - body.close(); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/BodyFileRegion.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/BodyFileRegion.java deleted file mode 100644 index 68a271a4da..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/BodyFileRegion.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty; - -import org.asynchttpclient.RandomAccessBody; -import org.jboss.netty.channel.FileRegion; - -import java.io.IOException; -import java.nio.channels.WritableByteChannel; - -/** - * Adapts a {@link RandomAccessBody} to Netty's {@link FileRegion}. - */ -class BodyFileRegion - implements FileRegion { - - private final RandomAccessBody body; - - public BodyFileRegion(RandomAccessBody body) { - if (body == null) { - throw new IllegalArgumentException("no body specified"); - } - this.body = body; - } - - public long getPosition() { - return 0; - } - - public long getCount() { - return body.getContentLength(); - } - - public long transferTo(WritableByteChannel target, long position) - throws IOException { - return body.transferTo(position, Long.MAX_VALUE, target); - } - - public void releaseExternalResources() { - try { - body.close(); - } catch (IOException e) { - // we tried - } - } - -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/Callback.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/Callback.java similarity index 72% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/Callback.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/Callback.java index 477f55feae..277b424e13 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/Callback.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/Callback.java @@ -1,4 +1,6 @@ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty; + +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; public abstract class Callback { diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/Constants.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/Constants.java similarity index 86% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/Constants.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/Constants.java index 1565673ff1..ede1d05e27 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/Constants.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/Constants.java @@ -1,4 +1,4 @@ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty; import java.nio.charset.Charset; diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/DiscardEvent.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/DiscardEvent.java similarity index 66% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/DiscardEvent.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/DiscardEvent.java index 6796d5bbc7..f18c40f962 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/DiscardEvent.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/DiscardEvent.java @@ -1,4 +1,4 @@ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty; // Simple marker for stopping publishing bytes. public enum DiscardEvent { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/FeedableBodyGenerator.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/FeedableBodyGenerator.java deleted file mode 100644 index 2c121c46bb..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/FeedableBodyGenerator.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicInteger; - -import org.asynchttpclient.Body; -import org.asynchttpclient.BodyGenerator; - -/** - * {@link BodyGenerator} which may return just part of the payload at the time - * handler is requesting it. If it happens - PartialBodyGenerator becomes responsible - * for finishing payload transferring asynchronously. - */ -public class FeedableBodyGenerator implements BodyGenerator { - private final static byte[] END_PADDING = "\r\n".getBytes(); - private final static byte[] ZERO = "0".getBytes(); - private final Queue queue = new ConcurrentLinkedQueue(); - private final AtomicInteger queueSize = new AtomicInteger(); - private FeedListener listener; - - @Override - public Body createBody() throws IOException { - return new PushBody(); - } - - public void feed(final ByteBuffer buffer, final boolean isLast) throws IOException { - queue.offer(new BodyPart(buffer, isLast)); - queueSize.incrementAndGet(); - if (listener != null) { - listener.onContentAdded(); - } - } - - public static interface FeedListener { - public void onContentAdded(); - } - - public void setListener(FeedListener listener) { - this.listener = listener; - } - - private final class PushBody implements Body { - private final int ONGOING = 0; - private final int CLOSING = 1; - private final int FINISHED = 2; - - private int finishState = 0; - - @Override - public long getContentLength() { - return -1; - } - - @Override - public long read(final ByteBuffer buffer) throws IOException { - BodyPart nextPart = queue.peek(); - if (nextPart == null) { - // Nothing in the queue - switch (finishState) { - case ONGOING: - return 0; - case CLOSING: - buffer.put(ZERO); - buffer.put(END_PADDING); - finishState = FINISHED; - return buffer.position(); - case FINISHED: - buffer.put(END_PADDING); - return -1; - } - } - int capacity = buffer.remaining() - 10; // be safe (we'll have to add size, ending, etc.) - int size = Math.min(nextPart.buffer.remaining(), capacity); - buffer.put(Integer.toHexString(size).getBytes()); - buffer.put(END_PADDING); - for (int i=0; i < size; i++) { - buffer.put(nextPart.buffer.get()); - } - buffer.put(END_PADDING); - if (!nextPart.buffer.hasRemaining()) { - if (nextPart.isLast) { - finishState = CLOSING; - } - queue.remove(); - } - return size; - } - - @Override - public void close() throws IOException { - } - - } - - private final static class BodyPart { - private final boolean isLast; - private final ByteBuffer buffer; - - public BodyPart(final ByteBuffer buffer, final boolean isLast) { - this.buffer = buffer; - this.isLast = isLast; - } - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java index e6f97d738d..60656ffad2 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java @@ -15,2342 +15,73 @@ */ package org.asynchttpclient.providers.netty; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHandler.STATE; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.Body; -import org.asynchttpclient.BodyGenerator; -import org.asynchttpclient.ConnectionPoolKeyStrategy; -import org.asynchttpclient.ConnectionsPool; -import org.asynchttpclient.Cookie; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; import org.asynchttpclient.HttpResponseBodyPart; import org.asynchttpclient.HttpResponseHeaders; import org.asynchttpclient.HttpResponseStatus; import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.MaxRedirectException; -import org.asynchttpclient.ProgressAsyncHandler; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.RandomAccessBody; -import org.asynchttpclient.Realm; import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; import org.asynchttpclient.Response; -import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterException; -import org.asynchttpclient.filter.IOExceptionFilter; -import org.asynchttpclient.filter.ResponseFilter; -import org.asynchttpclient.generators.InputStreamBodyGenerator; -import org.asynchttpclient.listener.TransferCompletionHandler; -import org.asynchttpclient.listener.TransferCompletionHandler.TransferAdapter; -import org.asynchttpclient.multipart.MultipartBody; -import org.asynchttpclient.multipart.MultipartRequestEntity; -import org.asynchttpclient.ntlm.NTLMEngine; -import org.asynchttpclient.ntlm.NTLMEngineException; -import org.asynchttpclient.org.jboss.netty.handler.codec.http.CookieDecoder; -import org.asynchttpclient.org.jboss.netty.handler.codec.http.CookieEncoder; -import org.asynchttpclient.providers.netty.FeedableBodyGenerator.FeedListener; -import org.asynchttpclient.providers.netty.spnego.SpnegoEngine; -import org.asynchttpclient.providers.netty.util.CleanupChannelGroup; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.asynchttpclient.util.AuthenticatorUtils; -import org.asynchttpclient.util.ProxyUtils; -import org.asynchttpclient.util.SslUtils; -import org.asynchttpclient.util.UTF8UrlEncoder; -import org.asynchttpclient.websocket.WebSocketUpgradeHandler; -import org.jboss.netty.bootstrap.ClientBootstrap; -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelFuture; -import org.jboss.netty.channel.ChannelFutureProgressListener; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.ChannelPipelineFactory; -import org.jboss.netty.channel.ChannelStateEvent; -import org.jboss.netty.channel.DefaultChannelFuture; -import org.jboss.netty.channel.ExceptionEvent; -import org.jboss.netty.channel.FileRegion; -import org.jboss.netty.channel.MessageEvent; -import org.jboss.netty.channel.SimpleChannelUpstreamHandler; -import org.jboss.netty.channel.group.ChannelGroup; -import org.jboss.netty.channel.socket.ClientSocketChannelFactory; -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; -import org.jboss.netty.channel.socket.oio.OioClientSocketChannelFactory; -import org.jboss.netty.handler.codec.PrematureChannelClosureException; -import org.jboss.netty.handler.codec.http.DefaultHttpChunkTrailer; -import org.jboss.netty.handler.codec.http.DefaultHttpRequest; -import org.jboss.netty.handler.codec.http.HttpChunk; -import org.jboss.netty.handler.codec.http.HttpChunkTrailer; -import org.jboss.netty.handler.codec.http.HttpClientCodec; -import org.jboss.netty.handler.codec.http.HttpContentCompressor; -import org.jboss.netty.handler.codec.http.HttpContentDecompressor; -import org.jboss.netty.handler.codec.http.HttpHeaders; -import org.jboss.netty.handler.codec.http.HttpMethod; -import org.jboss.netty.handler.codec.http.HttpRequest; -import org.jboss.netty.handler.codec.http.HttpRequestEncoder; -import org.jboss.netty.handler.codec.http.HttpResponse; -import org.jboss.netty.handler.codec.http.HttpResponseDecoder; -import org.jboss.netty.handler.codec.http.HttpVersion; -import org.jboss.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; -import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame; -import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame; -import org.jboss.netty.handler.codec.http.websocketx.WebSocket08FrameDecoder; -import org.jboss.netty.handler.codec.http.websocketx.WebSocket08FrameEncoder; -import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame; -import org.jboss.netty.handler.ssl.SslHandler; -import org.jboss.netty.handler.stream.ChunkedFile; -import org.jboss.netty.handler.stream.ChunkedWriteHandler; +import org.asynchttpclient.providers.netty.channel.Channels; +import org.asynchttpclient.providers.netty.handler.NettyChannelHandler; +import org.asynchttpclient.providers.netty.request.NettyRequestSender; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.net.ssl.SSLEngine; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.net.ConnectException; -import java.net.InetSocketAddress; -import java.net.MalformedURLException; -import java.net.URI; -import java.nio.channels.ClosedChannelException; -import java.nio.channels.FileChannel; -import java.nio.channels.WritableByteChannel; -import java.nio.charset.Charset; -import java.security.GeneralSecurityException; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map.Entry; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.asynchttpclient.util.AsyncHttpProviderUtils.DEFAULT_CHARSET; -import static org.asynchttpclient.util.DateUtil.millisTime; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; -import static org.jboss.netty.channel.Channels.pipeline; - -public class NettyAsyncHttpProvider extends SimpleChannelUpstreamHandler implements AsyncHttpProvider { - private final static String HTTP_HANDLER = "httpHandler"; - protected final static String SSL_HANDLER = "sslHandler"; - private final static String HTTPS = "https"; - private final static String HTTP = "http"; - private static final String WEBSOCKET = "ws"; - private static final String WEBSOCKET_SSL = "wss"; +public class NettyAsyncHttpProvider implements AsyncHttpProvider { - private final static Logger log = LoggerFactory.getLogger(NettyAsyncHttpProvider.class); - private final static Charset UTF8 = Charset.forName("UTF-8"); + private static final Logger LOGGER = LoggerFactory.getLogger(NettyAsyncHttpProvider.class); - private final ClientBootstrap plainBootstrap; - private final ClientBootstrap secureBootstrap; - private final ClientBootstrap webSocketBootstrap; - private final ClientBootstrap secureWebSocketBootstrap; - private final static int MAX_BUFFERED_BYTES = 8192; private final AsyncHttpClientConfig config; - private final AtomicBoolean isClose = new AtomicBoolean(false); - private final ClientSocketChannelFactory socketChannelFactory; - private final boolean allowReleaseSocketChannelFactory; - - private final ChannelGroup openChannels = new CleanupChannelGroup("asyncHttpClient") { - @Override - public boolean remove(Object o) { - boolean removed = super.remove(o); - if (removed && trackConnections) { - freeConnections.release(); - } - return removed; - } - }; - private final ConnectionsPool connectionsPool; - private Semaphore freeConnections = null; private final NettyAsyncHttpProviderConfig asyncHttpProviderConfig; - private boolean executeConnectAsync = true; - public static final ThreadLocal IN_IO_THREAD = new ThreadLocalBoolean(); - private final boolean trackConnections; - private final boolean useRawUrl; - private final static NTLMEngine ntlmEngine = new NTLMEngine(); - private static SpnegoEngine spnegoEngine = null; - private final Protocol httpProtocol = new HttpProtocol(); - private final Protocol webSocketProtocol = new WebSocketProtocol(); - private final boolean managedExecutorService; - private ExecutorService service; - - private static boolean isNTLM(List auth) { - return isNonEmpty(auth) && auth.get(0).startsWith("NTLM"); - } + private final AtomicBoolean closed = new AtomicBoolean(false); + private final Channels channels; + private final NettyRequestSender requestSender; + private final NettyChannelHandler channelHandler; public NettyAsyncHttpProvider(AsyncHttpClientConfig config) { - if (config.getAsyncHttpProviderConfig() instanceof NettyAsyncHttpProviderConfig) { - asyncHttpProviderConfig = NettyAsyncHttpProviderConfig.class.cast(config.getAsyncHttpProviderConfig()); - } else { - asyncHttpProviderConfig = new NettyAsyncHttpProviderConfig(); - } - service = config.executorService(); - managedExecutorService = (service == null); - if (service == null) { - service = AsyncHttpProviderUtils.createDefaultExecutorService(); - } - - if (asyncHttpProviderConfig.isUseBlockingIO()) { - socketChannelFactory = new OioClientSocketChannelFactory(service); - this.allowReleaseSocketChannelFactory = true; - } else { - // check if external NioClientSocketChannelFactory is defined - NioClientSocketChannelFactory scf = asyncHttpProviderConfig.getSocketChannelFactory(); - if (scf != null) { - this.socketChannelFactory = scf; - - // cannot allow releasing shared channel factory - this.allowReleaseSocketChannelFactory = false; - } else { - ExecutorService e = asyncHttpProviderConfig.getBossExecutorService(); - if (e == null) { - e = Executors.newCachedThreadPool(); - } - int numWorkers = config.getIoThreadMultiplier() * Runtime.getRuntime().availableProcessors(); - log.debug("Number of application's worker threads is {}", numWorkers); - socketChannelFactory = new NioClientSocketChannelFactory(e, service, numWorkers); - this.allowReleaseSocketChannelFactory = true; - } - } - plainBootstrap = new ClientBootstrap(socketChannelFactory); - secureBootstrap = new ClientBootstrap(socketChannelFactory); - webSocketBootstrap = new ClientBootstrap(socketChannelFactory); - secureWebSocketBootstrap = new ClientBootstrap(socketChannelFactory); this.config = config; + asyncHttpProviderConfig = config.getAsyncHttpProviderConfig() instanceof NettyAsyncHttpProviderConfig ? // + NettyAsyncHttpProviderConfig.class.cast(config.getAsyncHttpProviderConfig()) + : new NettyAsyncHttpProviderConfig(); - configureNetty(); - - // This is dangerous as we can't catch a wrong typed ConnectionsPool - ConnectionsPool cp = (ConnectionsPool) config.getConnectionsPool(); - if (cp == null && config.getAllowPoolingConnection()) { - cp = new NettyConnectionsPool(this); - } else if (cp == null) { - cp = new NonConnectionsPool(); - } - this.connectionsPool = cp; - - if (config.getMaxTotalConnections() != -1) { - trackConnections = true; - freeConnections = new Semaphore(config.getMaxTotalConnections()); - } else { - trackConnections = false; - } - - useRawUrl = config.isUseRawUrl(); + channels = new Channels(config, asyncHttpProviderConfig); + requestSender = new NettyRequestSender(closed, config, channels); + channelHandler = new NettyChannelHandler(config, requestSender, channels, closed); + channels.configure(channelHandler); } @Override public String toString() { - return String.format("NettyAsyncHttpProvider:\n\t- maxConnections: %d\n\t- openChannels: %s\n\t- connectionPools: %s", config.getMaxTotalConnections() - freeConnections.availablePermits(), openChannels.toString(), connectionsPool.toString()); - } - - void configureNetty() { - if (asyncHttpProviderConfig != null) { - for (Entry entry : asyncHttpProviderConfig.propertiesSet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - plainBootstrap.setOption(key, value); - webSocketBootstrap.setOption(key, value); - secureBootstrap.setOption(key, value); - secureWebSocketBootstrap.setOption(key, value); - } - } - - plainBootstrap.setPipelineFactory(createPlainPipelineFactory()); - DefaultChannelFuture.setUseDeadLockChecker(false); - - if (asyncHttpProviderConfig != null) { - executeConnectAsync = config.isAsyncConnectMode(); - if (!executeConnectAsync) { - DefaultChannelFuture.setUseDeadLockChecker(true); - } - } - - webSocketBootstrap.setPipelineFactory(new ChannelPipelineFactory() { - - /* @Override */ - public ChannelPipeline getPipeline() throws Exception { - ChannelPipeline pipeline = pipeline(); - pipeline.addLast("http-decoder", new HttpResponseDecoder()); - pipeline.addLast("http-encoder", new HttpRequestEncoder()); - pipeline.addLast("httpProcessor", NettyAsyncHttpProvider.this); - return pipeline; - } - }); - } - - protected HttpClientCodec newHttpClientCodec() { - if (asyncHttpProviderConfig != null) { - return new HttpClientCodec(asyncHttpProviderConfig.getMaxInitialLineLength(), asyncHttpProviderConfig.getMaxHeaderSize(), asyncHttpProviderConfig.getMaxChunkSize(), false); - - } else { - return new HttpClientCodec(); - } - } - - protected ChannelPipelineFactory createPlainPipelineFactory() { - return new ChannelPipelineFactory() { - - /* @Override */ - public ChannelPipeline getPipeline() throws Exception { - ChannelPipeline pipeline = pipeline(); - - pipeline.addLast(HTTP_HANDLER, newHttpClientCodec()); - - if (config.getRequestCompressionLevel() > 0) { - pipeline.addLast("deflater", new HttpContentCompressor(config.getRequestCompressionLevel())); - } - - if (config.isCompressionEnabled()) { - pipeline.addLast("inflater", new HttpContentDecompressor()); - } - pipeline.addLast("chunkedWriter", new ChunkedWriteHandler()); - pipeline.addLast("httpProcessor", NettyAsyncHttpProvider.this); - return pipeline; - } - }; - } - - void constructSSLPipeline(final NettyConnectListener cl) { - - secureBootstrap.setPipelineFactory(new ChannelPipelineFactory() { - - /* @Override */ - public ChannelPipeline getPipeline() throws Exception { - ChannelPipeline pipeline = pipeline(); - - try { - pipeline.addLast(SSL_HANDLER, new SslHandler(createSSLEngine())); - } catch (Throwable ex) { - abort(cl.future(), ex); - } - - pipeline.addLast(HTTP_HANDLER, newHttpClientCodec()); - - if (config.isCompressionEnabled()) { - pipeline.addLast("inflater", new HttpContentDecompressor()); - } - pipeline.addLast("chunkedWriter", new ChunkedWriteHandler()); - pipeline.addLast("httpProcessor", NettyAsyncHttpProvider.this); - return pipeline; - } - }); - - secureWebSocketBootstrap.setPipelineFactory(new ChannelPipelineFactory() { - - /* @Override */ - public ChannelPipeline getPipeline() throws Exception { - ChannelPipeline pipeline = pipeline(); - - try { - pipeline.addLast(SSL_HANDLER, new SslHandler(createSSLEngine())); - } catch (Throwable ex) { - abort(cl.future(), ex); - } - - pipeline.addLast("http-decoder", new HttpResponseDecoder()); - pipeline.addLast("http-encoder", new HttpRequestEncoder()); - pipeline.addLast("httpProcessor", NettyAsyncHttpProvider.this); - - return pipeline; - } - }); - } - - private Channel lookupInCache(URI uri, ConnectionPoolKeyStrategy connectionPoolKeyStrategy) { - final Channel channel = connectionsPool.poll(connectionPoolKeyStrategy.getKey(uri)); - - if (channel != null) { - log.debug("Using cached Channel {}\n for uri {}\n", channel, uri); - - try { - // Always make sure the channel who got cached support the proper protocol. It could - // only occurs when a HttpMethod.CONNECT is used against a proxy that require upgrading from http to - // https. - return verifyChannelPipeline(channel, uri.getScheme()); - } catch (Exception ex) { - log.debug(ex.getMessage(), ex); - } - } - return null; - } - - private SSLEngine createSSLEngine() throws IOException, GeneralSecurityException { - SSLEngine sslEngine = config.getSSLEngineFactory().newSSLEngine(); - if (sslEngine == null) { - sslEngine = SslUtils.getSSLEngine(); - } - return sslEngine; - } - - private Channel verifyChannelPipeline(Channel channel, String scheme) throws IOException, GeneralSecurityException { - - if (channel.getPipeline().get(SSL_HANDLER) != null && HTTP.equalsIgnoreCase(scheme)) { - channel.getPipeline().remove(SSL_HANDLER); - } else if (channel.getPipeline().get(HTTP_HANDLER) != null && HTTP.equalsIgnoreCase(scheme)) { - return channel; - } else if (channel.getPipeline().get(SSL_HANDLER) == null && isSecure(scheme)) { - channel.getPipeline().addFirst(SSL_HANDLER, new SslHandler(createSSLEngine())); - } - return channel; - } - - protected final void writeRequest(final Channel channel, final AsyncHttpClientConfig config, final NettyResponseFuture future, final HttpRequest nettyRequest) { - try { - /** - * If the channel is dead because it was pooled and the remote server decided to close it, we just let it go and the closeChannel do it's work. - */ - if (!channel.isOpen() || !channel.isConnected()) { - return; - } - - Body body = null; - if (!future.getNettyRequest().getMethod().equals(HttpMethod.CONNECT)) { - BodyGenerator bg = future.getRequest().getBodyGenerator(); - if (bg != null) { - // Netty issue with chunking. - if (bg instanceof InputStreamBodyGenerator) { - InputStreamBodyGenerator.class.cast(bg).patchNettyChunkingIssue(true); - } - - try { - body = bg.createBody(); - } catch (IOException ex) { - throw new IllegalStateException(ex); - } - long length = body.getContentLength(); - if (length >= 0) { - nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH, length); - } else { - nettyRequest.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); - } - } else if (future.getRequest().getParts() != null) { - String contentType = nettyRequest.getHeader(HttpHeaders.Names.CONTENT_TYPE); - String length = nettyRequest.getHeader(HttpHeaders.Names.CONTENT_LENGTH); - body = new MultipartBody(future.getRequest().getParts(), contentType, length); - } - } - - if (future.getAsyncHandler() instanceof TransferCompletionHandler) { - - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - for (String s : future.getNettyRequest().getHeaderNames()) { - for (String header : future.getNettyRequest().getHeaders(s)) { - h.add(s, header); - } - } - - TransferCompletionHandler.class.cast(future.getAsyncHandler()).transferAdapter(new TransferAdapter(h)); - } - - // Leave it to true. - if (future.getAndSetWriteHeaders(true)) { - try { - channel.write(nettyRequest).addListener(new ProgressListener(true, future.getAsyncHandler(), future)); - } catch (Throwable cause) { - log.debug(cause.getMessage(), cause); - try { - channel.close(); - } catch (RuntimeException ex) { - log.debug(ex.getMessage(), ex); - } - return; - } - } - - if (future.getAndSetWriteBody(true)) { - if (!future.getNettyRequest().getMethod().equals(HttpMethod.CONNECT)) { - - if (future.getRequest().getFile() != null) { - final File file = future.getRequest().getFile(); - long fileLength = 0; - final RandomAccessFile raf = new RandomAccessFile(file, "r"); - - try { - fileLength = raf.length(); - - ChannelFuture writeFuture; - if (channel.getPipeline().get(SslHandler.class) != null) { - writeFuture = channel.write(new ChunkedFile(raf, 0, fileLength, MAX_BUFFERED_BYTES)); - } else { - final FileRegion region = new OptimizedFileRegion(raf, 0, fileLength); - writeFuture = channel.write(region); - } - writeFuture.addListener(new ProgressListener(false, future.getAsyncHandler(), future) { - public void operationComplete(ChannelFuture cf) { - try { - raf.close(); - } catch (IOException e) { - log.warn("Failed to close request body: {}", e.getMessage(), e); - } - super.operationComplete(cf); - } - }); - } catch (IOException ex) { - if (raf != null) { - try { - raf.close(); - } catch (IOException e) { - } - } - throw ex; - } - } else if (body != null) { - - ChannelFuture writeFuture; - if (channel.getPipeline().get(SslHandler.class) == null && (body instanceof RandomAccessBody)) { - BodyFileRegion bodyFileRegion = new BodyFileRegion((RandomAccessBody) body); - writeFuture = channel.write(bodyFileRegion); - } else { - BodyChunkedInput bodyChunkedInput = new BodyChunkedInput(body); - BodyGenerator bg = future.getRequest().getBodyGenerator(); - if (bg instanceof FeedableBodyGenerator) { - ((FeedableBodyGenerator) bg).setListener(new FeedListener() { - @Override - public void onContentAdded() { - channel.getPipeline().get(ChunkedWriteHandler.class).resumeTransfer(); - } - }); - } - writeFuture = channel.write(bodyChunkedInput); - } - - final Body b = body; - writeFuture.addListener(new ProgressListener(false, future.getAsyncHandler(), future) { - public void operationComplete(ChannelFuture cf) { - try { - b.close(); - } catch (IOException e) { - log.warn("Failed to close request body: {}", e.getMessage(), e); - } - super.operationComplete(cf); - } - }); - } - } - } - } catch (Throwable ioe) { - try { - channel.close(); - } catch (RuntimeException ex) { - log.debug(ex.getMessage(), ex); - } - } - - try { - future.touch(); - int requestTimeout = AsyncHttpProviderUtils.requestTimeout(config, future.getRequest()); - int schedulePeriod = requestTimeout != -1 ? (config.getIdleConnectionTimeoutInMs() != -1 ? Math.min(requestTimeout, config.getIdleConnectionTimeoutInMs()) : requestTimeout) : config.getIdleConnectionTimeoutInMs(); - - if (schedulePeriod != -1 && !future.isDone() && !future.isCancelled()) { - ReaperFuture reaperFuture = new ReaperFuture(future); - Future scheduledFuture = config.reaper().scheduleAtFixedRate(reaperFuture, 0, schedulePeriod, TimeUnit.MILLISECONDS); - reaperFuture.setScheduledFuture(scheduledFuture); - future.setReaperFuture(reaperFuture); - } - } catch (RejectedExecutionException ex) { - abort(future, ex); - } - - } - - protected final static HttpRequest buildRequest(AsyncHttpClientConfig config, Request request, URI uri, boolean allowConnect, ChannelBuffer buffer, ProxyServer proxyServer) throws IOException { - - String method = request.getMethod(); - if (allowConnect && proxyServer != null && isSecure(uri)) { - method = HttpMethod.CONNECT.toString(); - } - return construct(config, request, new HttpMethod(method), uri, buffer, proxyServer); - } - - private static SpnegoEngine getSpnegoEngine() { - if (spnegoEngine == null) - spnegoEngine = new SpnegoEngine(); - return spnegoEngine; - } - - private static HttpRequest construct(AsyncHttpClientConfig config, Request request, HttpMethod m, URI uri, ChannelBuffer buffer, ProxyServer proxyServer) throws IOException { - - String host = null; - boolean webSocket = isWebSocket(uri); - - if (request.getVirtualHost() != null) { - host = request.getVirtualHost(); - } else { - host = AsyncHttpProviderUtils.getHost(uri); - } - - HttpRequest nettyRequest; - if (m.equals(HttpMethod.CONNECT)) { - nettyRequest = new DefaultHttpRequest(HttpVersion.HTTP_1_0, m, AsyncHttpProviderUtils.getAuthority(uri)); - } else { - String path = null; - if (proxyServer != null && !(isSecure(uri) && config.isUseRelativeURIsWithSSLProxies())) - path = uri.toString(); - else if (uri.getRawQuery() != null) - path = uri.getRawPath() + "?" + uri.getRawQuery(); - else - path = uri.getRawPath(); - nettyRequest = new DefaultHttpRequest(HttpVersion.HTTP_1_1, m, path); - } - - if (webSocket) { - nettyRequest.addHeader(HttpHeaders.Names.UPGRADE, HttpHeaders.Values.WEBSOCKET); - nettyRequest.addHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.UPGRADE); - nettyRequest.addHeader(HttpHeaders.Names.ORIGIN, "http://" + uri.getHost() + ":" + (uri.getPort() == -1 ? isSecure(uri.getScheme()) ? 443 : 80 : uri.getPort())); - nettyRequest.addHeader(HttpHeaders.Names.SEC_WEBSOCKET_KEY, WebSocketUtil.getKey()); - nettyRequest.addHeader(HttpHeaders.Names.SEC_WEBSOCKET_VERSION, "13"); - } - - if (host != null) { - if (request.getVirtualHost() != null || uri.getPort() == -1) { - nettyRequest.setHeader(HttpHeaders.Names.HOST, host); - } else { - nettyRequest.setHeader(HttpHeaders.Names.HOST, host + ":" + uri.getPort()); - } - } else { - host = "127.0.0.1"; - } - - if (!m.equals(HttpMethod.CONNECT)) { - FluentCaseInsensitiveStringsMap h = request.getHeaders(); - if (h != null) { - for (Entry> header : h) { - String name = header.getKey(); - if (!HttpHeaders.Names.HOST.equalsIgnoreCase(name)) { - for (String value : header.getValue()) { - nettyRequest.addHeader(name, value); - } - } - } - } - - if (config.isCompressionEnabled()) { - nettyRequest.setHeader(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP); - } - } else { - List auth = request.getHeaders().get(HttpHeaders.Names.PROXY_AUTHORIZATION); - if (isNTLM(auth)) { - nettyRequest.addHeader(HttpHeaders.Names.PROXY_AUTHORIZATION, auth.get(0)); - } - } - Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - - if (realm != null && realm.getUsePreemptiveAuth()) { - - String domain = realm.getNtlmDomain(); - if (proxyServer != null && proxyServer.getNtlmDomain() != null) { - domain = proxyServer.getNtlmDomain(); - } - - String authHost = realm.getNtlmHost(); - if (proxyServer != null && proxyServer.getHost() != null) { - host = proxyServer.getHost(); - } - - switch (realm.getAuthScheme()) { - case BASIC: - nettyRequest.setHeader(HttpHeaders.Names.AUTHORIZATION, AuthenticatorUtils.computeBasicAuthentication(realm)); - break; - case DIGEST: - if (isNonEmpty(realm.getNonce())) { - try { - nettyRequest.setHeader(HttpHeaders.Names.AUTHORIZATION, AuthenticatorUtils.computeDigestAuthentication(realm)); - } catch (NoSuchAlgorithmException e) { - throw new SecurityException(e); - } - } - break; - case NTLM: - try { - String msg = ntlmEngine.generateType1Msg("NTLM " + domain, authHost); - nettyRequest.setHeader(HttpHeaders.Names.AUTHORIZATION, "NTLM " + msg); - } catch (NTLMEngineException e) { - IOException ie = new IOException(); - ie.initCause(e); - throw ie; - } - break; - case KERBEROS: - case SPNEGO: - String challengeHeader = null; - String server = proxyServer == null ? host : proxyServer.getHost(); - try { - challengeHeader = getSpnegoEngine().generateToken(server); - } catch (Throwable e) { - IOException ie = new IOException(); - ie.initCause(e); - throw ie; - } - nettyRequest.setHeader(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader); - break; - case NONE: - break; - default: - throw new IllegalStateException("Invalid Authentication " + realm); - } - } - - if (!webSocket && !request.getHeaders().containsKey(HttpHeaders.Names.CONNECTION)) { - nettyRequest.setHeader(HttpHeaders.Names.CONNECTION, AsyncHttpProviderUtils.keepAliveHeaderValue(config)); - } - - if (proxyServer != null) { - if (!request.getHeaders().containsKey("Proxy-Connection")) { - nettyRequest.setHeader("Proxy-Connection", AsyncHttpProviderUtils.keepAliveHeaderValue(config)); - } - - if (proxyServer.getPrincipal() != null) { - if (isNonEmpty(proxyServer.getNtlmDomain())) { - - List auth = request.getHeaders().get(HttpHeaders.Names.PROXY_AUTHORIZATION); - if (!isNTLM(auth)) { - try { - String msg = ntlmEngine.generateType1Msg(proxyServer.getNtlmDomain(), proxyServer.getHost()); - nettyRequest.setHeader(HttpHeaders.Names.PROXY_AUTHORIZATION, "NTLM " + msg); - } catch (NTLMEngineException e) { - IOException ie = new IOException(); - ie.initCause(e); - throw ie; - } - } - } else { - nettyRequest.setHeader(HttpHeaders.Names.PROXY_AUTHORIZATION, AuthenticatorUtils.computeBasicAuthentication(proxyServer)); - } - } - } - - // Add default accept headers. - if (request.getHeaders().getFirstValue(HttpHeaders.Names.ACCEPT) == null) { - nettyRequest.setHeader(HttpHeaders.Names.ACCEPT, "*/*"); - } - - String userAgentHeader = request.getHeaders().getFirstValue(HttpHeaders.Names.USER_AGENT); - if (userAgentHeader != null) { - nettyRequest.setHeader(HttpHeaders.Names.USER_AGENT, userAgentHeader); - } else if (config.getUserAgent() != null) { - nettyRequest.setHeader(HttpHeaders.Names.USER_AGENT, config.getUserAgent()); - } else { - nettyRequest.setHeader(HttpHeaders.Names.USER_AGENT, AsyncHttpProviderUtils.constructUserAgent(NettyAsyncHttpProvider.class, config)); - } - - if (!m.equals(HttpMethod.CONNECT)) { - if (isNonEmpty(request.getCookies())) { - nettyRequest.setHeader(HttpHeaders.Names.COOKIE, CookieEncoder.encodeClientSide(request.getCookies(), config.isRfc6265CookieEncoding())); - } - - String reqType = request.getMethod(); - if (!"HEAD".equals(reqType) && !"OPTION".equals(reqType) && !"TRACE".equals(reqType)) { - - String bodyCharset = request.getBodyEncoding() == null ? DEFAULT_CHARSET : request.getBodyEncoding(); - - // We already have processed the body. - if (buffer != null && buffer.writerIndex() != 0) { - nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH, buffer.writerIndex()); - nettyRequest.setContent(buffer); - } else if (request.getByteData() != null) { - nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(request.getByteData().length)); - nettyRequest.setContent(ChannelBuffers.wrappedBuffer(request.getByteData())); - } else if (request.getStringData() != null) { - byte[] bytes = request.getStringData().getBytes(bodyCharset); - nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(bytes.length)); - nettyRequest.setContent(ChannelBuffers.wrappedBuffer(bytes)); - } else if (request.getStreamData() != null) { - int[] lengthWrapper = new int[1]; - // FIXME should be streaming instead! - byte[] bytes = AsyncHttpProviderUtils.readFully(request.getStreamData(), lengthWrapper); - int length = lengthWrapper[0]; - nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(length)); - nettyRequest.setContent(ChannelBuffers.wrappedBuffer(bytes, 0, length)); - } else if (isNonEmpty(request.getParams())) { - StringBuilder sb = new StringBuilder(); - for (final Entry> paramEntry : request.getParams()) { - final String key = paramEntry.getKey(); - for (final String value : paramEntry.getValue()) { - if (sb.length() > 0) { - sb.append("&"); - } - UTF8UrlEncoder.appendEncoded(sb, key); - sb.append("="); - UTF8UrlEncoder.appendEncoded(sb, value); - } - } - nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(sb.length())); - nettyRequest.setContent(ChannelBuffers.wrappedBuffer(sb.toString().getBytes(bodyCharset))); - - if (!request.getHeaders().containsKey(HttpHeaders.Names.CONTENT_TYPE)) { - nettyRequest.setHeader(HttpHeaders.Names.CONTENT_TYPE, HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED); - } - - } else if (request.getParts() != null) { - MultipartRequestEntity mre = AsyncHttpProviderUtils.createMultipartRequestEntity(request.getParts(), request.getHeaders()); - - nettyRequest.setHeader(HttpHeaders.Names.CONTENT_TYPE, mre.getContentType()); - nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(mre.getContentLength())); - - } else if (request.getFile() != null) { - File file = request.getFile(); - if (!file.isFile()) { - throw new IOException(String.format("File %s is not a file or doesn't exist", file.getAbsolutePath())); - } - nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH, file.length()); - } - } - } - return nettyRequest; + return String.format("NettyAsyncHttpProvider4:\n\t- maxConnections: %d\n\t- openChannels: %s\n\t- connectionPools: %s", config.getMaxTotalConnections() + - channels.freeConnections.availablePermits(), channels.openChannels.toString(), channels.connectionsPool.toString()); } + @Override public void close() { - isClose.set(true); + closed.set(true); try { - connectionsPool.destroy(); - openChannels.close(); - - for (Channel channel : openChannels) { - ChannelHandlerContext ctx = channel.getPipeline().getContext(NettyAsyncHttpProvider.class); - if (ctx.getAttachment() instanceof NettyResponseFuture) { - NettyResponseFuture future = (NettyResponseFuture) ctx.getAttachment(); - future.setReaperFuture(null); - } - } - - if (managedExecutorService) { - service.shutdown(); - } + channels.close(); config.reaper().shutdown(); - if (this.allowReleaseSocketChannelFactory) { - socketChannelFactory.releaseExternalResources(); - plainBootstrap.releaseExternalResources(); - secureBootstrap.releaseExternalResources(); - webSocketBootstrap.releaseExternalResources(); - secureWebSocketBootstrap.releaseExternalResources(); - } } catch (Throwable t) { - log.warn("Unexpected error on close", t); + LOGGER.warn("Unexpected error on close", t); } } - /* @Override */ - + @Override public Response prepareResponse(final HttpResponseStatus status, final HttpResponseHeaders headers, final List bodyParts) { - return new NettyResponse(status, headers, bodyParts); - } - - /* @Override */ - - public ListenableFuture execute(Request request, final AsyncHandler asyncHandler) throws IOException { - return doConnect(request, asyncHandler, null, true, executeConnectAsync, false); - } - - private void execute(final Request request, final NettyResponseFuture f, boolean useCache, boolean asyncConnect, boolean reclaimCache) throws IOException { - doConnect(request, f.getAsyncHandler(), f, useCache, asyncConnect, reclaimCache); - } - - private ListenableFuture doConnect(final Request request, final AsyncHandler asyncHandler, NettyResponseFuture f, boolean useCache, boolean asyncConnect, boolean reclaimCache) throws IOException { - - if (isClose.get()) { - throw new IOException("Closed"); - } - - if (request.getUrl().startsWith(WEBSOCKET) && !validateWebSocketRequest(request, asyncHandler)) { - throw new IOException("WebSocket method must be a GET"); - } - - ProxyServer proxyServer = ProxyUtils.getProxyServer(config, request); - boolean useProxy = proxyServer != null; - URI uri; - if (useRawUrl) { - uri = request.getRawURI(); - } else { - uri = request.getURI(); - } - Channel channel = null; - - if (useCache) { - if (f != null && f.reuseChannel() && f.channel() != null) { - channel = f.channel(); - } else { - URI connectionKeyUri = useProxy ? proxyServer.getURI() : uri; - channel = lookupInCache(connectionKeyUri, request.getConnectionPoolKeyStrategy()); - } - } - - ChannelBuffer bufferedBytes = null; - if (f != null && f.getRequest().getFile() == null && !f.getNettyRequest().getMethod().getName().equals(HttpMethod.CONNECT.getName())) { - bufferedBytes = f.getNettyRequest().getContent(); - } - - boolean useSSl = isSecure(uri) && !useProxy; - if (channel != null && channel.isOpen() && channel.isConnected()) { - HttpRequest nettyRequest = null; - - if (f == null) { - nettyRequest = buildRequest(config, request, uri, false, bufferedBytes, proxyServer); - f = newFuture(uri, request, asyncHandler, nettyRequest, config, this, proxyServer); - } else { - nettyRequest = buildRequest(config, request, uri, f.isConnectAllowed(), bufferedBytes, proxyServer); - f.setNettyRequest(nettyRequest); - } - f.setState(NettyResponseFuture.STATE.POOLED); - f.attachChannel(channel, false); - - log.debug("\nUsing cached Channel {}\n for request \n{}\n", channel, nettyRequest); - channel.getPipeline().getContext(NettyAsyncHttpProvider.class).setAttachment(f); - - try { - writeRequest(channel, config, f, nettyRequest); - } catch (Exception ex) { - log.debug("writeRequest failure", ex); - if (useSSl && ex.getMessage() != null && ex.getMessage().contains("SSLEngine")) { - log.debug("SSLEngine failure", ex); - f = null; - } else { - try { - asyncHandler.onThrowable(ex); - } catch (Throwable t) { - log.warn("doConnect.writeRequest()", t); - } - IOException ioe = new IOException(ex.getMessage()); - ioe.initCause(ex); - throw ioe; - } - } - return f; - } - - // Do not throw an exception when we need an extra connection for a redirect. - if (!reclaimCache && !connectionsPool.canCacheConnection()) { - IOException ex = new IOException("Too many connections " + config.getMaxTotalConnections()); - try { - asyncHandler.onThrowable(ex); - } catch (Throwable t) { - log.warn("!connectionsPool.canCacheConnection()", t); - } - throw ex; - } - - boolean acquiredConnection = false; - - if (trackConnections) { - if (!reclaimCache) { - if (!freeConnections.tryAcquire()) { - IOException ex = new IOException("Too many connections " + config.getMaxTotalConnections()); - try { - asyncHandler.onThrowable(ex); - } catch (Throwable t) { - log.warn("!connectionsPool.canCacheConnection()", t); - } - throw ex; - } else { - acquiredConnection = true; - } - } - } - - NettyConnectListener c = new NettyConnectListener.Builder(config, request, asyncHandler, f, this, bufferedBytes).build(uri); - boolean avoidProxy = ProxyUtils.avoidProxy(proxyServer, uri.getHost()); - - if (useSSl) { - constructSSLPipeline(c); - } - - ChannelFuture channelFuture; - ClientBootstrap bootstrap = request.getUrl().startsWith(WEBSOCKET) ? (useSSl ? secureWebSocketBootstrap : webSocketBootstrap) : (useSSl ? secureBootstrap : plainBootstrap); - bootstrap.setOption("connectTimeoutMillis", config.getConnectionTimeoutInMs()); - - try { - InetSocketAddress remoteAddress; - if (request.getInetAddress() != null) { - remoteAddress = new InetSocketAddress(request.getInetAddress(), AsyncHttpProviderUtils.getPort(uri)); - } else if (proxyServer == null || avoidProxy) { - remoteAddress = new InetSocketAddress(AsyncHttpProviderUtils.getHost(uri), AsyncHttpProviderUtils.getPort(uri)); - } else { - remoteAddress = new InetSocketAddress(proxyServer.getHost(), proxyServer.getPort()); - } - - if (request.getLocalAddress() != null) { - channelFuture = bootstrap.connect(remoteAddress, new InetSocketAddress(request.getLocalAddress(), 0)); - } else { - channelFuture = bootstrap.connect(remoteAddress); - } - - } catch (Throwable t) { - if (acquiredConnection) { - freeConnections.release(); - } - abort(c.future(), t.getCause() == null ? t : t.getCause()); - return c.future(); - } - - boolean directInvokation = !(IN_IO_THREAD.get() && DefaultChannelFuture.isUseDeadLockChecker()); - - if (directInvokation && !asyncConnect && request.getFile() == null) { - int timeOut = config.getConnectionTimeoutInMs() > 0 ? config.getConnectionTimeoutInMs() : Integer.MAX_VALUE; - if (!channelFuture.awaitUninterruptibly(timeOut, TimeUnit.MILLISECONDS)) { - if (acquiredConnection) { - freeConnections.release(); - } - channelFuture.cancel(); - abort(c.future(), new ConnectException(String.format("Connect operation to %s timeout %s", uri, timeOut))); - } - - try { - c.operationComplete(channelFuture); - } catch (Exception e) { - if (acquiredConnection) { - freeConnections.release(); - } - IOException ioe = new IOException(e.getMessage()); - ioe.initCause(e); - try { - asyncHandler.onThrowable(ioe); - } catch (Throwable t) { - log.warn("c.operationComplete()", t); - } - throw ioe; - } - } else { - channelFuture.addListener(c); - } - - log.debug("\nNon cached request \n{}\n\nusing Channel \n{}\n", c.future().getNettyRequest(), channelFuture.getChannel()); - - if (!c.future().isCancelled() || !c.future().isDone()) { - openChannels.add(channelFuture.getChannel()); - c.future().attachChannel(channelFuture.getChannel(), false); - } - return c.future(); - } - - private void closeChannel(final ChannelHandlerContext ctx) { - connectionsPool.removeAll(ctx.getChannel()); - finishChannel(ctx); - } - - private void finishChannel(final ChannelHandlerContext ctx) { - ctx.setAttachment(new DiscardEvent()); - - // The channel may have already been removed if a timeout occurred, and this method may be called just after. - if (ctx.getChannel() == null) { - return; - } - - log.debug("Closing Channel {} ", ctx.getChannel()); - - try { - ctx.getChannel().close(); - } catch (Throwable t) { - log.debug("Error closing a connection", t); - } - - if (ctx.getChannel() != null) { - openChannels.remove(ctx.getChannel()); - } - + throw new UnsupportedOperationException("Mocked, should be refactored"); } @Override - public void messageReceived(final ChannelHandlerContext ctx, MessageEvent e) throws Exception { - // call super to reset the read timeout - super.messageReceived(ctx, e); - IN_IO_THREAD.set(Boolean.TRUE); - if (ctx.getAttachment() == null) { - log.debug("ChannelHandlerContext wasn't having any attachment"); - } - - if (ctx.getAttachment() instanceof DiscardEvent) { - return; - } else if (ctx.getAttachment() instanceof AsyncCallable) { - if (e.getMessage() instanceof HttpChunk) { - HttpChunk chunk = (HttpChunk) e.getMessage(); - if (chunk.isLast()) { - AsyncCallable ac = (AsyncCallable) ctx.getAttachment(); - ac.call(); - } else { - return; - } - } else { - AsyncCallable ac = (AsyncCallable) ctx.getAttachment(); - ac.call(); - } - ctx.setAttachment(new DiscardEvent()); - return; - } else if (!(ctx.getAttachment() instanceof NettyResponseFuture)) { - try { - ctx.getChannel().close(); - } catch (Throwable t) { - log.trace("Closing an orphan channel {}", ctx.getChannel()); - } - return; - } - - Protocol p = (ctx.getPipeline().get(HttpClientCodec.class) != null ? httpProtocol : webSocketProtocol); - p.handle(ctx, e); - } - - private Realm kerberosChallenge(List proxyAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, NettyResponseFuture future) throws NTLMEngineException { - - URI uri = request.getURI(); - String host = request.getVirtualHost() == null ? AsyncHttpProviderUtils.getHost(uri) : request.getVirtualHost(); - String server = proxyServer == null ? host : proxyServer.getHost(); - try { - String challengeHeader = getSpnegoEngine().generateToken(server); - headers.remove(HttpHeaders.Names.AUTHORIZATION); - headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader); - - Realm.RealmBuilder realmBuilder; - if (realm != null) { - realmBuilder = new Realm.RealmBuilder().clone(realm); - } else { - realmBuilder = new Realm.RealmBuilder(); - } - return realmBuilder.setUri(uri.getRawPath()).setMethodName(request.getMethod()).setScheme(Realm.AuthScheme.KERBEROS).build(); - } catch (Throwable throwable) { - if (isNTLM(proxyAuth)) { - return ntlmChallenge(proxyAuth, request, proxyServer, headers, realm, future); - } - abort(future, throwable); - return null; - } - } - - private void addType3NTLMAuthorizationHeader(List auth, FluentCaseInsensitiveStringsMap headers, String username, String password, String domain, String workstation) - throws NTLMEngineException { - headers.remove(HttpHeaders.Names.AUTHORIZATION); - - if (isNTLM(auth)) { - String serverChallenge = auth.get(0).trim().substring("NTLM ".length()); - String challengeHeader = ntlmEngine.generateType3Msg(username, password, domain, workstation, serverChallenge); - - headers.add(HttpHeaders.Names.AUTHORIZATION, "NTLM " + challengeHeader); - } - } - - private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, NettyResponseFuture future) throws NTLMEngineException { - - boolean useRealm = (proxyServer == null && realm != null); - - String ntlmDomain = useRealm ? realm.getNtlmDomain() : proxyServer.getNtlmDomain(); - String ntlmHost = useRealm ? realm.getNtlmHost() : proxyServer.getHost(); - String principal = useRealm ? realm.getPrincipal() : proxyServer.getPrincipal(); - String password = useRealm ? realm.getPassword() : proxyServer.getPassword(); - - Realm newRealm; - if (realm != null && !realm.isNtlmMessageType2Received()) { - String challengeHeader = ntlmEngine.generateType1Msg(ntlmDomain, ntlmHost); - - URI uri = request.getURI(); - headers.add(HttpHeaders.Names.AUTHORIZATION, "NTLM " + challengeHeader); - newRealm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(uri.getRawPath()).setMethodName(request.getMethod()).setNtlmMessageType2Received(true).build(); - future.getAndSetAuth(false); - } else { - addType3NTLMAuthorizationHeader(wwwAuth, headers, principal, password, ntlmDomain, ntlmHost); - - Realm.RealmBuilder realmBuilder; - Realm.AuthScheme authScheme; - if (realm != null) { - realmBuilder = new Realm.RealmBuilder().clone(realm); - authScheme = realm.getAuthScheme(); - } else { - realmBuilder = new Realm.RealmBuilder(); - authScheme = Realm.AuthScheme.NTLM; - } - newRealm = realmBuilder.setScheme(authScheme).setUri(request.getURI().getPath()).setMethodName(request.getMethod()).build(); - } - - return newRealm; - } - - private Realm ntlmProxyChallenge(List wwwAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, NettyResponseFuture future) throws NTLMEngineException { - future.getAndSetAuth(false); - headers.remove(HttpHeaders.Names.PROXY_AUTHORIZATION); - - addType3NTLMAuthorizationHeader(wwwAuth, headers, proxyServer.getPrincipal(), proxyServer.getPassword(), proxyServer.getNtlmDomain(), proxyServer.getHost()); - - Realm newRealm; - Realm.RealmBuilder realmBuilder; - if (realm != null) { - realmBuilder = new Realm.RealmBuilder().clone(realm); - } else { - realmBuilder = new Realm.RealmBuilder(); - } - newRealm = realmBuilder// .setScheme(realm.getAuthScheme()) - .setUri(request.getURI().getPath()).setMethodName(request.getMethod()).build(); - - return newRealm; - } - - private String getPoolKey(NettyResponseFuture future) throws MalformedURLException { - URI uri = future.getProxyServer() != null ? future.getProxyServer().getURI() : future.getURI(); - return future.getConnectionPoolKeyStrategy().getKey(uri); - } - - private void drainChannel(final ChannelHandlerContext ctx, final NettyResponseFuture future) { - ctx.setAttachment(new AsyncCallable(future) { - public Object call() throws Exception { - if (future.isKeepAlive() && ctx.getChannel().isReadable() && connectionsPool.offer(getPoolKey(future), ctx.getChannel())) { - return null; - } - - finishChannel(ctx); - return null; - } - - @Override - public String toString() { - return "Draining task for channel " + ctx.getChannel(); - } - }); - } - - private FilterContext handleIoException(FilterContext fc, NettyResponseFuture future) { - for (IOExceptionFilter asyncFilter : config.getIOExceptionFilters()) { - try { - fc = asyncFilter.filter(fc); - if (fc == null) { - throw new NullPointerException("FilterContext is null"); - } - } catch (FilterException efe) { - abort(future, efe); - } - } - return fc; - } - - private void replayRequest(final NettyResponseFuture future, FilterContext fc, HttpResponse response, ChannelHandlerContext ctx) throws IOException { - final Request newRequest = fc.getRequest(); - future.setAsyncHandler(fc.getAsyncHandler()); - future.setState(NettyResponseFuture.STATE.NEW); - future.touch(); - - log.debug("\n\nReplaying Request {}\n for Future {}\n", newRequest, future); - drainChannel(ctx, future); - nextRequest(newRequest, future); - return; - } - - private List getAuthorizationToken(List> list, String headerAuth) { - ArrayList l = new ArrayList(); - for (Entry e : list) { - if (e.getKey().equalsIgnoreCase(headerAuth)) { - l.add(e.getValue().trim()); - } - } - return l; - } - - private void nextRequest(final Request request, final NettyResponseFuture future) throws IOException { - nextRequest(request, future, true); - } - - private void nextRequest(final Request request, final NettyResponseFuture future, final boolean useCache) throws IOException { - execute(request, future, useCache, true, true); - } - - private void abort(NettyResponseFuture future, Throwable t) { - Channel channel = future.channel(); - if (channel != null && openChannels.contains(channel)) { - closeChannel(channel.getPipeline().getContext(NettyAsyncHttpProvider.class)); - openChannels.remove(channel); - } - - if (!future.isCancelled() && !future.isDone()) { - log.debug("Aborting Future {}\n", future); - log.debug(t.getMessage(), t); - } - - future.abort(t); - } - - private void upgradeProtocol(ChannelPipeline p, String scheme) throws IOException, GeneralSecurityException { - if (p.get(HTTP_HANDLER) != null) { - p.remove(HTTP_HANDLER); - } - - if (isSecure(scheme)) { - if (p.get(SSL_HANDLER) == null) { - p.addFirst(HTTP_HANDLER, newHttpClientCodec()); - p.addFirst(SSL_HANDLER, new SslHandler(createSSLEngine())); - } else { - p.addAfter(SSL_HANDLER, HTTP_HANDLER, newHttpClientCodec()); - } - - } else { - p.addFirst(HTTP_HANDLER, newHttpClientCodec()); - } - } - - public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { - - if (isClose.get()) { - return; - } - - connectionsPool.removeAll(ctx.getChannel()); - try { - super.channelClosed(ctx, e); - } catch (Exception ex) { - log.trace("super.channelClosed", ex); - } - - log.debug("Channel Closed: {} with attachment {}", e.getChannel(), ctx.getAttachment()); - - if (ctx.getAttachment() instanceof AsyncCallable) { - AsyncCallable ac = (AsyncCallable) ctx.getAttachment(); - ctx.setAttachment(ac.future()); - ac.call(); - return; - } - - if (ctx.getAttachment() instanceof NettyResponseFuture) { - NettyResponseFuture future = (NettyResponseFuture) ctx.getAttachment(); - future.touch(); - - if (!config.getIOExceptionFilters().isEmpty()) { - FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(future.getAsyncHandler()).request(future.getRequest()).ioException(new IOException("Channel Closed")).build(); - fc = handleIoException(fc, future); - - if (fc.replayRequest() && !future.cannotBeReplay()) { - replayRequest(future, fc, null, ctx); - return; - } - } - - Protocol p = (ctx.getPipeline().get(HttpClientCodec.class) != null ? httpProtocol : webSocketProtocol); - p.onClose(ctx, e); - - if (future != null && !future.isDone() && !future.isCancelled()) { - if (remotelyClosed(ctx.getChannel(), future)) { - abort(future, new IOException("Remotely Closed")); - } - } else { - closeChannel(ctx); - } - } - } - - protected boolean remotelyClosed(Channel channel, NettyResponseFuture future) { - - if (isClose.get()) { - return true; - } - - connectionsPool.removeAll(channel); - - if (future == null) { - Object attachment = channel.getPipeline().getContext(NettyAsyncHttpProvider.class).getAttachment(); - if (attachment instanceof NettyResponseFuture) - future = (NettyResponseFuture) attachment; - } - - if (future == null || future.cannotBeReplay()) { - log.debug("Unable to recover future {}\n", future); - return true; - } - - future.setState(NettyResponseFuture.STATE.RECONNECTED); - future.getAndSetStatusReceived(false); - - log.debug("Trying to recover request {}\n", future.getNettyRequest()); - - try { - nextRequest(future.getRequest(), future); - return false; - } catch (IOException iox) { - future.setState(NettyResponseFuture.STATE.CLOSED); - future.abort(iox); - log.error("Remotely Closed, unable to recover", iox); - } - return true; - } - - private void markAsDone(final NettyResponseFuture future, final ChannelHandlerContext ctx) throws MalformedURLException { - // We need to make sure everything is OK before adding the connection back to the pool. - try { - future.done(); - } catch (Throwable t) { - // Never propagate exception once we know we are done. - log.debug(t.getMessage(), t); - } - - if (!future.isKeepAlive() || !ctx.getChannel().isReadable()) { - closeChannel(ctx); - } - } - - private void finishUpdate(final NettyResponseFuture future, final ChannelHandlerContext ctx, boolean lastValidChunk) throws IOException { - if (lastValidChunk && future.isKeepAlive()) { - drainChannel(ctx, future); - } else { - if (future.isKeepAlive() && ctx.getChannel().isReadable() && connectionsPool.offer(getPoolKey(future), ctx.getChannel())) { - markAsDone(future, ctx); - return; - } - finishChannel(ctx); - } - markAsDone(future, ctx); - } - - private final boolean updateStatusAndInterrupt(AsyncHandler handler, HttpResponseStatus c) throws Exception { - return handler.onStatusReceived(c) != STATE.CONTINUE; - } - - private final boolean updateHeadersAndInterrupt(AsyncHandler handler, HttpResponseHeaders c) throws Exception { - return handler.onHeadersReceived(c) != STATE.CONTINUE; - } - - private final boolean updateBodyAndInterrupt(final NettyResponseFuture future, AsyncHandler handler, HttpResponseBodyPart c) throws Exception { - boolean state = handler.onBodyPartReceived(c) != STATE.CONTINUE; - if (c.closeUnderlyingConnection()) { - future.setKeepAlive(false); - } - return state; - } - - // Simple marker for stopping publishing bytes. - - final static class DiscardEvent { - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { - Channel channel = e.getChannel(); - Throwable cause = e.getCause(); - NettyResponseFuture future = null; - - if (e.getCause() instanceof PrematureChannelClosureException) { - return; - } - - if (log.isDebugEnabled()) { - log.debug("Unexpected I/O exception on channel {}", channel, cause); - } - - try { - - if (cause instanceof ClosedChannelException) { - return; - } - - if (ctx.getAttachment() instanceof NettyResponseFuture) { - future = (NettyResponseFuture) ctx.getAttachment(); - future.attachChannel(null, false); - future.touch(); - - if (cause instanceof IOException) { - - if (!config.getIOExceptionFilters().isEmpty()) { - FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(future.getAsyncHandler()).request(future.getRequest()).ioException(new IOException("Channel Closed")).build(); - fc = handleIoException(fc, future); - - if (fc.replayRequest()) { - replayRequest(future, fc, null, ctx); - return; - } - } else { - // Close the channel so the recovering can occurs. - try { - ctx.getChannel().close(); - } catch (Throwable t) { - ; // Swallow. - } - return; - } - } - - if (abortOnReadCloseException(cause) || abortOnWriteCloseException(cause)) { - log.debug("Trying to recover from dead Channel: {}", channel); - return; - } - } else if (ctx.getAttachment() instanceof AsyncCallable) { - future = ((AsyncCallable) ctx.getAttachment()).future(); - } - } catch (Throwable t) { - cause = t; - } - - if (future != null) { - try { - log.debug("Was unable to recover Future: {}", future); - abort(future, cause); - } catch (Throwable t) { - log.error(t.getMessage(), t); - } - } - - Protocol p = (ctx.getPipeline().get(HttpClientCodec.class) != null ? httpProtocol : webSocketProtocol); - p.onError(ctx, e); - - closeChannel(ctx); - ctx.sendUpstream(e); - } - - protected static boolean abortOnConnectCloseException(Throwable cause) { - try { - for (StackTraceElement element : cause.getStackTrace()) { - if (element.getClassName().equals("sun.nio.ch.SocketChannelImpl") && element.getMethodName().equals("checkConnect")) { - return true; - } - } - - if (cause.getCause() != null) { - return abortOnConnectCloseException(cause.getCause()); - } - - } catch (Throwable t) { - } - return false; - } - - protected static boolean abortOnDisconnectException(Throwable cause) { - try { - for (StackTraceElement element : cause.getStackTrace()) { - if (element.getClassName().equals("org.jboss.netty.handler.ssl.SslHandler") && element.getMethodName().equals("channelDisconnected")) { - return true; - } - } - - if (cause.getCause() != null) { - return abortOnConnectCloseException(cause.getCause()); - } - - } catch (Throwable t) { - } - return false; - } - - protected static boolean abortOnReadCloseException(Throwable cause) { - - for (StackTraceElement element : cause.getStackTrace()) { - if (element.getClassName().equals("sun.nio.ch.SocketDispatcher") && element.getMethodName().equals("read")) { - return true; - } - } - - if (cause.getCause() != null) { - return abortOnReadCloseException(cause.getCause()); - } - - return false; - } - - protected static boolean abortOnWriteCloseException(Throwable cause) { - - for (StackTraceElement element : cause.getStackTrace()) { - if (element.getClassName().equals("sun.nio.ch.SocketDispatcher") && element.getMethodName().equals("write")) { - return true; - } - } - - if (cause.getCause() != null) { - return abortOnReadCloseException(cause.getCause()); - } - - return false; - } - - private final static int getPredefinedContentLength(Request request, HttpRequest r) { - int length = (int) request.getContentLength(); - if (length == -1 && r.getHeader(HttpHeaders.Names.CONTENT_LENGTH) != null) { - length = Integer.valueOf(r.getHeader(HttpHeaders.Names.CONTENT_LENGTH)); - } - - return length; - } - - public static NettyResponseFuture newFuture(URI uri, Request request, AsyncHandler asyncHandler, HttpRequest nettyRequest, AsyncHttpClientConfig config, NettyAsyncHttpProvider provider, ProxyServer proxyServer) { - - int requestTimeout = AsyncHttpProviderUtils.requestTimeout(config, request); - NettyResponseFuture f = new NettyResponseFuture(uri,// - request,// - asyncHandler,// - nettyRequest,// - requestTimeout,// - config.getIdleConnectionTimeoutInMs(),// - provider,// - request.getConnectionPoolKeyStrategy(),// - proxyServer); - - String expectHeader = request.getHeaders().getFirstValue(HttpHeaders.Names.EXPECT); - if (expectHeader != null && expectHeader.equalsIgnoreCase(HttpHeaders.Values.CONTINUE)) { - f.getAndSetWriteBody(false); - } - return f; - } - - private class ProgressListener implements ChannelFutureProgressListener { - - private final boolean notifyHeaders; - private final AsyncHandler asyncHandler; - private final NettyResponseFuture future; - - public ProgressListener(boolean notifyHeaders, AsyncHandler asyncHandler, NettyResponseFuture future) { - this.notifyHeaders = notifyHeaders; - this.asyncHandler = asyncHandler; - this.future = future; - } - - public void operationComplete(ChannelFuture cf) { - // The write operation failed. If the channel was cached, it means it got asynchronously closed. - // Let's retry a second time. - Throwable cause = cf.getCause(); - if (cause != null && future.getState() != NettyResponseFuture.STATE.NEW) { - - if (cause instanceof IllegalStateException) { - log.debug(cause.getMessage(), cause); - try { - cf.getChannel().close(); - } catch (RuntimeException ex) { - log.debug(ex.getMessage(), ex); - } - return; - } - - if (cause instanceof ClosedChannelException || abortOnReadCloseException(cause) || abortOnWriteCloseException(cause)) { - - if (log.isDebugEnabled()) { - log.debug(cf.getCause() == null ? "" : cf.getCause().getMessage(), cf.getCause()); - } - - try { - cf.getChannel().close(); - } catch (RuntimeException ex) { - log.debug(ex.getMessage(), ex); - } - return; - } else { - future.abort(cause); - } - return; - } - future.touch(); - - /** - * We need to make sure we aren't in the middle of an authorization process before publishing events as we will re-publish again the same event after the authorization, causing unpredictable behavior. - */ - Realm realm = future.getRequest().getRealm() != null ? future.getRequest().getRealm() : NettyAsyncHttpProvider.this.getConfig().getRealm(); - boolean startPublishing = future.isInAuth() || realm == null || realm.getUsePreemptiveAuth() == true; - - if (startPublishing && asyncHandler instanceof ProgressAsyncHandler) { - if (notifyHeaders) { - ProgressAsyncHandler.class.cast(asyncHandler).onHeaderWriteCompleted(); - } else { - ProgressAsyncHandler.class.cast(asyncHandler).onContentWriteCompleted(); - } - } - } - - public void operationProgressed(ChannelFuture cf, long amount, long current, long total) { - future.touch(); - if (asyncHandler instanceof ProgressAsyncHandler) { - ProgressAsyncHandler.class.cast(asyncHandler).onContentWriteProgress(amount, current, total); - } - } - } - - /** - * Because some implementation of the ThreadSchedulingService do not clean up cancel task until they try to run them, we wrap the task with the future so the when the NettyResponseFuture cancel the reaper future this wrapper will release the references to the channel and the - * nettyResponseFuture immediately. Otherwise, the memory referenced this way will only be released after the request timeout period which can be arbitrary long. - */ - private final class ReaperFuture implements Future, Runnable { - private Future scheduledFuture; - private NettyResponseFuture nettyResponseFuture; - - public ReaperFuture(NettyResponseFuture nettyResponseFuture) { - this.nettyResponseFuture = nettyResponseFuture; - } - - public void setScheduledFuture(Future scheduledFuture) { - this.scheduledFuture = scheduledFuture; - } - - /** - * @Override - */ - public boolean cancel(boolean mayInterruptIfRunning) { - nettyResponseFuture = null; - return scheduledFuture.cancel(mayInterruptIfRunning); - } - - /** - * @Override - */ - public Object get() throws InterruptedException, ExecutionException { - return scheduledFuture.get(); - } - - /** - * @Override - */ - public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - return scheduledFuture.get(timeout, unit); - } - - /** - * @Override - */ - public boolean isCancelled() { - return scheduledFuture.isCancelled(); - } - - /** - * @Override - */ - public boolean isDone() { - return scheduledFuture.isDone(); - } - - private void expire(String message) { - log.debug("{} for {}", message, nettyResponseFuture); - abort(nettyResponseFuture, new TimeoutException(message)); - nettyResponseFuture = null; - } - - /** - * @Override - */ - public synchronized void run() { - if (isClose.get()) { - cancel(true); - return; - } - - boolean futureDone = nettyResponseFuture.isDone(); - boolean futureCanceled = nettyResponseFuture.isCancelled(); - - if (nettyResponseFuture != null && !futureDone && !futureCanceled) { - long now = millisTime(); - if (nettyResponseFuture.hasRequestTimedOut(now)) { - long age = now - nettyResponseFuture.getStart(); - expire("Request reached time out of " + nettyResponseFuture.getRequestTimeoutInMs() + " ms after " + age + " ms"); - } else if (nettyResponseFuture.hasConnectionIdleTimedOut(now)) { - long age = now - nettyResponseFuture.getStart(); - expire("Request reached idle time out of " + nettyResponseFuture.getIdleConnectionTimeoutInMs() + " ms after " + age + " ms"); - } - - } else if (nettyResponseFuture == null || futureDone || futureCanceled) { - cancel(true); - } - } - } - - private abstract class AsyncCallable implements Callable { - - private final NettyResponseFuture future; - - public AsyncCallable(NettyResponseFuture future) { - this.future = future; - } - - abstract public Object call() throws Exception; - - public NettyResponseFuture future() { - return future; - } - } - - public static class ThreadLocalBoolean extends ThreadLocal { - - private final boolean defaultValue; - - public ThreadLocalBoolean() { - this(false); - } - - public ThreadLocalBoolean(boolean defaultValue) { - this.defaultValue = defaultValue; - } - - @Override - protected Boolean initialValue() { - return defaultValue ? Boolean.TRUE : Boolean.FALSE; - } - } - - public static class OptimizedFileRegion implements FileRegion { - - private final FileChannel file; - private final RandomAccessFile raf; - private final long position; - private final long count; - private long byteWritten; - - public OptimizedFileRegion(RandomAccessFile raf, long position, long count) { - this.raf = raf; - this.file = raf.getChannel(); - this.position = position; - this.count = count; - } - - public long getPosition() { - return position; - } - - public long getCount() { - return count; - } - - public long transferTo(WritableByteChannel target, long position) throws IOException { - long count = this.count - position; - if (count < 0 || position < 0) { - throw new IllegalArgumentException("position out of range: " + position + " (expected: 0 - " + (this.count - 1) + ")"); - } - if (count == 0) { - return 0L; - } - - long bw = file.transferTo(this.position + position, count, target); - byteWritten += bw; - if (byteWritten == raf.length()) { - releaseExternalResources(); - } - return bw; - } - - public void releaseExternalResources() { - try { - file.close(); - } catch (IOException e) { - log.warn("Failed to close a file.", e); - } - - try { - raf.close(); - } catch (IOException e) { - log.warn("Failed to close a file.", e); - } - } - } - - protected AsyncHttpClientConfig getConfig() { - return config; - } - - private static class NonConnectionsPool implements ConnectionsPool { - - public boolean offer(String uri, Channel connection) { - return false; - } - - public Channel poll(String uri) { - return null; - } - - public boolean removeAll(Channel connection) { - return false; - } - - public boolean canCacheConnection() { - return true; - } - - public void destroy() { - } - } - - private static final boolean validateWebSocketRequest(Request request, AsyncHandler asyncHandler) { - if (request.getMethod() != "GET" || !(asyncHandler instanceof WebSocketUpgradeHandler)) { - return false; - } - return true; - } - - private boolean redirect(Request request, NettyResponseFuture future, HttpResponse response, final ChannelHandlerContext ctx) throws Exception { - - int statusCode = response.getStatus().getCode(); - boolean redirectEnabled = request.isRedirectOverrideSet() ? request.isRedirectEnabled() : config.isRedirectEnabled(); - if (redirectEnabled && (statusCode == 302 || statusCode == 301 || statusCode == 303 || statusCode == 307)) { - - if (future.incrementAndGetCurrentRedirectCount() < config.getMaxRedirects()) { - // We must allow 401 handling again. - future.getAndSetAuth(false); - - String location = response.getHeader(HttpHeaders.Names.LOCATION); - URI uri = AsyncHttpProviderUtils.getRedirectUri(future.getURI(), location); - boolean stripQueryString = config.isRemoveQueryParamOnRedirect(); - if (!uri.toString().equals(future.getURI().toString())) { - final RequestBuilder nBuilder = stripQueryString ? new RequestBuilder(future.getRequest()).setQueryParameters(null) : new RequestBuilder(future.getRequest()); - - if (!(statusCode < 302 || statusCode > 303) && !(statusCode == 302 && config.isStrict302Handling())) { - nBuilder.setMethod("GET"); - } - final boolean initialConnectionKeepAlive = future.isKeepAlive(); - final String initialPoolKey = getPoolKey(future); - future.setURI(uri); - String newUrl = uri.toString(); - if (request.getUrl().startsWith(WEBSOCKET)) { - newUrl = newUrl.replace(HTTP, WEBSOCKET); - } - - log.debug("Redirecting to {}", newUrl); - for (String cookieStr : future.getHttpResponse().getHeaders(HttpHeaders.Names.SET_COOKIE)) { - for (Cookie c : CookieDecoder.decode(cookieStr)) { - nBuilder.addOrReplaceCookie(c); - } - } - - for (String cookieStr : future.getHttpResponse().getHeaders(HttpHeaders.Names.SET_COOKIE2)) { - for (Cookie c : CookieDecoder.decode(cookieStr)) { - nBuilder.addOrReplaceCookie(c); - } - } - - AsyncCallable ac = new AsyncCallable(future) { - public Object call() throws Exception { - if (initialConnectionKeepAlive && ctx.getChannel().isReadable() && connectionsPool.offer(initialPoolKey, ctx.getChannel())) { - return null; - } - finishChannel(ctx); - return null; - } - }; - - if (response.isChunked()) { - // We must make sure there is no bytes left before executing the next request. - ctx.setAttachment(ac); - } else { - ac.call(); - } - nextRequest(nBuilder.setUrl(newUrl).build(), future); - return true; - } - } else { - throw new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects()); - } - } - return false; - } - - private final class HttpProtocol implements Protocol { - // @Override - public void handle(final ChannelHandlerContext ctx, final MessageEvent e) throws Exception { - final NettyResponseFuture future = (NettyResponseFuture) ctx.getAttachment(); - future.touch(); - - // The connect timeout occured. - if (future.isCancelled() || future.isDone()) { - finishChannel(ctx); - return; - } - - HttpRequest nettyRequest = future.getNettyRequest(); - AsyncHandler handler = future.getAsyncHandler(); - Request request = future.getRequest(); - ProxyServer proxyServer = future.getProxyServer(); - HttpResponse response = null; - try { - if (e.getMessage() instanceof HttpResponse) { - response = (HttpResponse) e.getMessage(); - - log.debug("\n\nRequest {}\n\nResponse {}\n", nettyRequest, response); - - // Required if there is some trailing headers. - future.setHttpResponse(response); - - int statusCode = response.getStatus().getCode(); - - String ka = response.getHeader(HttpHeaders.Names.CONNECTION); - future.setKeepAlive(ka == null || !ka.toLowerCase(Locale.ENGLISH).equals("close")); - - List wwwAuth = getAuthorizationToken(response.getHeaders(), HttpHeaders.Names.WWW_AUTHENTICATE); - Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - - HttpResponseStatus status = new ResponseStatus(future.getURI(), response, NettyAsyncHttpProvider.this); - HttpResponseHeaders responseHeaders = new ResponseHeaders(future.getURI(), response, NettyAsyncHttpProvider.this); - FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(handler).request(request).responseStatus(status).responseHeaders(responseHeaders).build(); - - for (ResponseFilter asyncFilter : config.getResponseFilters()) { - try { - fc = asyncFilter.filter(fc); - if (fc == null) { - throw new NullPointerException("FilterContext is null"); - } - } catch (FilterException efe) { - abort(future, efe); - } - } - - // The handler may have been wrapped. - handler = fc.getAsyncHandler(); - future.setAsyncHandler(handler); - - // The request has changed - if (fc.replayRequest()) { - replayRequest(future, fc, response, ctx); - return; - } - - Realm newRealm = null; - final FluentCaseInsensitiveStringsMap headers = request.getHeaders(); - final RequestBuilder builder = new RequestBuilder(future.getRequest()); - - // if (realm != null && !future.getURI().getPath().equalsIgnoreCase(realm.getUri())) { - // builder.setUrl(future.getURI().toString()); - // } - - if (statusCode == 401 && realm != null && !wwwAuth.isEmpty() && !future.getAndSetAuth(true)) { - - future.setState(NettyResponseFuture.STATE.NEW); - // NTLM - if (!wwwAuth.contains("Kerberos") && (isNTLM(wwwAuth) || (wwwAuth.contains("Negotiate")))) { - newRealm = ntlmChallenge(wwwAuth, request, proxyServer, headers, realm, future); - // SPNEGO KERBEROS - } else if (wwwAuth.contains("Negotiate")) { - newRealm = kerberosChallenge(wwwAuth, request, proxyServer, headers, realm, future); - if (newRealm == null) - return; - } else { - newRealm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(request.getURI().getPath()).setMethodName(request.getMethod()).setUsePreemptiveAuth(true).parseWWWAuthenticateHeader(wwwAuth.get(0)).build(); - } - - final Realm nr = new Realm.RealmBuilder().clone(newRealm).setUri(URI.create(request.getUrl()).getPath()).build(); - - log.debug("Sending authentication to {}", request.getUrl()); - AsyncCallable ac = new AsyncCallable(future) { - public Object call() throws Exception { - drainChannel(ctx, future); - nextRequest(builder.setHeaders(headers).setRealm(nr).build(), future); - return null; - } - }; - - if (future.isKeepAlive() && response.isChunked()) { - // We must make sure there is no bytes left before executing the next request. - ctx.setAttachment(ac); - } else { - ac.call(); - } - return; - } - - if (statusCode == 100) { - future.getAndSetWriteHeaders(false); - future.getAndSetWriteBody(true); - writeRequest(ctx.getChannel(), config, future, nettyRequest); - return; - } - - List proxyAuth = getAuthorizationToken(response.getHeaders(), HttpHeaders.Names.PROXY_AUTHENTICATE); - if (statusCode == 407 && realm != null && !proxyAuth.isEmpty() && !future.getAndSetAuth(true)) { - - log.debug("Sending proxy authentication to {}", request.getUrl()); - - future.setState(NettyResponseFuture.STATE.NEW); - - if (!proxyAuth.contains("Kerberos") && (isNTLM(proxyAuth) || (proxyAuth.contains("Negotiate")))) { - newRealm = ntlmProxyChallenge(proxyAuth, request, proxyServer, headers, realm, future); - // SPNEGO KERBEROS - } else if (proxyAuth.contains("Negotiate")) { - newRealm = kerberosChallenge(proxyAuth, request, proxyServer, headers, realm, future); - if (newRealm == null) - return; - } else { - newRealm = future.getRequest().getRealm(); - } - - Request req = builder.setHeaders(headers).setRealm(newRealm).build(); - future.setReuseChannel(true); - future.setConnectAllowed(true); - nextRequest(req, future); - return; - } - - if (future.getNettyRequest().getMethod().equals(HttpMethod.CONNECT) && statusCode == 200) { - - log.debug("Connected to {}:{}", proxyServer.getHost(), proxyServer.getPort()); - - if (future.isKeepAlive()) { - future.attachChannel(ctx.getChannel(), true); - } - - try { - log.debug("Connecting to proxy {} for scheme {}", proxyServer, request.getUrl()); - upgradeProtocol(ctx.getChannel().getPipeline(), request.getURI().getScheme()); - } catch (Throwable ex) { - abort(future, ex); - } - Request req = builder.build(); - future.setReuseChannel(true); - future.setConnectAllowed(false); - nextRequest(req, future); - return; - } - - if (redirect(request, future, response, ctx)) - return; - - if (!future.getAndSetStatusReceived(true) && updateStatusAndInterrupt(handler, status)) { - finishUpdate(future, ctx, response.isChunked()); - return; - } else if (updateHeadersAndInterrupt(handler, responseHeaders)) { - finishUpdate(future, ctx, response.isChunked()); - return; - } else if (!response.isChunked()) { - if (response.getContent().readableBytes() != 0) { - updateBodyAndInterrupt(future, handler, new ResponseBodyPart(future.getURI(), response, NettyAsyncHttpProvider.this, true)); - } - finishUpdate(future, ctx, false); - return; - } - - if (nettyRequest.getMethod().equals(HttpMethod.HEAD)) { - updateBodyAndInterrupt(future, handler, new ResponseBodyPart(future.getURI(), response, NettyAsyncHttpProvider.this, true)); - markAsDone(future, ctx); - drainChannel(ctx, future); - } - - } else if (e.getMessage() instanceof HttpChunk) { - HttpChunk chunk = (HttpChunk) e.getMessage(); - - if (handler != null) { - if (chunk.isLast() || updateBodyAndInterrupt(future, handler, new ResponseBodyPart(future.getURI(), null, NettyAsyncHttpProvider.this, chunk, chunk.isLast()))) { - if (chunk instanceof DefaultHttpChunkTrailer) { - updateHeadersAndInterrupt(handler, new ResponseHeaders(future.getURI(), future.getHttpResponse(), NettyAsyncHttpProvider.this, (HttpChunkTrailer) chunk)); - } - finishUpdate(future, ctx, !chunk.isLast()); - } - } - } - } catch (Exception t) { - if (t instanceof IOException && !config.getIOExceptionFilters().isEmpty()) { - FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(future.getAsyncHandler()).request(future.getRequest()).ioException(IOException.class.cast(t)).build(); - fc = handleIoException(fc, future); - - if (fc.replayRequest()) { - replayRequest(future, fc, response, ctx); - return; - } - } - - try { - abort(future, t); - } finally { - finishUpdate(future, ctx, false); - throw t; - } - } - } - - // @Override - public void onError(ChannelHandlerContext ctx, ExceptionEvent e) { - } - - // @Override - public void onClose(ChannelHandlerContext ctx, ChannelStateEvent e) { - } - } - - private final class WebSocketProtocol implements Protocol { - private static final byte OPCODE_CONT = 0x0; - private static final byte OPCODE_TEXT = 0x1; - private static final byte OPCODE_BINARY = 0x2; - private static final byte OPCODE_UNKNOWN = -1; - protected byte pendingOpcode = OPCODE_UNKNOWN; - - // We don't need to synchronize as replacing the "ws-decoder" will process using the same thread. - private void invokeOnSucces(ChannelHandlerContext ctx, WebSocketUpgradeHandler h) { - if (!h.touchSuccess()) { - try { - h.onSuccess(new NettyWebSocket(ctx.getChannel())); - } catch (Exception ex) { - NettyAsyncHttpProvider.log.warn("onSuccess unexexpected exception", ex); - } - } - } - - // @Override - public void handle(ChannelHandlerContext ctx, MessageEvent e) throws Exception { - NettyResponseFuture future = NettyResponseFuture.class.cast(ctx.getAttachment()); - WebSocketUpgradeHandler h = WebSocketUpgradeHandler.class.cast(future.getAsyncHandler()); - Request request = future.getRequest(); - - if (e.getMessage() instanceof HttpResponse) { - HttpResponse response = (HttpResponse) e.getMessage(); - - HttpResponseStatus s = new ResponseStatus(future.getURI(), response, NettyAsyncHttpProvider.this); - HttpResponseHeaders responseHeaders = new ResponseHeaders(future.getURI(), response, NettyAsyncHttpProvider.this); - FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(h).request(request).responseStatus(s).responseHeaders(responseHeaders).build(); - for (ResponseFilter asyncFilter : config.getResponseFilters()) { - try { - fc = asyncFilter.filter(fc); - if (fc == null) { - throw new NullPointerException("FilterContext is null"); - } - } catch (FilterException efe) { - abort(future, efe); - } - - } - - // The handler may have been wrapped. - future.setAsyncHandler(fc.getAsyncHandler()); - - // The request has changed - if (fc.replayRequest()) { - replayRequest(future, fc, response, ctx); - return; - } - - future.setHttpResponse(response); - if (redirect(request, future, response, ctx)) - return; - - final org.jboss.netty.handler.codec.http.HttpResponseStatus status = new org.jboss.netty.handler.codec.http.HttpResponseStatus(101, "Web Socket Protocol Handshake"); - - final boolean validStatus = response.getStatus().equals(status); - final boolean validUpgrade = response.getHeader(HttpHeaders.Names.UPGRADE) != null; - String c = response.getHeader(HttpHeaders.Names.CONNECTION); - if (c == null) { - c = response.getHeader(HttpHeaders.Names.CONNECTION.toLowerCase(Locale.ENGLISH)); - } - - final boolean validConnection = c == null ? false : c.equalsIgnoreCase(HttpHeaders.Values.UPGRADE); - - s = new ResponseStatus(future.getURI(), response, NettyAsyncHttpProvider.this); - final boolean statusReceived = h.onStatusReceived(s) == STATE.UPGRADE; - - final boolean headerOK = h.onHeadersReceived(responseHeaders) == STATE.CONTINUE; - if (!headerOK || !validStatus || !validUpgrade || !validConnection || !statusReceived) { - abort(future, new IOException("Invalid handshake response")); - return; - } - - String accept = response.getHeader(HttpHeaders.Names.SEC_WEBSOCKET_ACCEPT); - String key = WebSocketUtil.getAcceptKey(future.getNettyRequest().getHeader(HttpHeaders.Names.SEC_WEBSOCKET_KEY)); - if (accept == null || !accept.equals(key)) { - throw new IOException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, key)); - } - - ctx.getPipeline().replace("http-encoder", "ws-encoder", new WebSocket08FrameEncoder(true)); - ctx.getPipeline().get(HttpResponseDecoder.class).replace("ws-decoder", new WebSocket08FrameDecoder(false, false)); - - invokeOnSucces(ctx, h); - future.done(); - } else if (e.getMessage() instanceof WebSocketFrame) { - - invokeOnSucces(ctx, h); - - final WebSocketFrame frame = (WebSocketFrame) e.getMessage(); - - if (frame instanceof TextWebSocketFrame) { - pendingOpcode = OPCODE_TEXT; - } else if (frame instanceof BinaryWebSocketFrame) { - pendingOpcode = OPCODE_BINARY; - } - - HttpChunk webSocketChunk = new HttpChunk() { - private ChannelBuffer content; - - // @Override - public boolean isLast() { - return false; - } - - // @Override - public ChannelBuffer getContent() { - return content; - } - - // @Override - public void setContent(ChannelBuffer content) { - this.content = content; - } - }; - - if (frame.getBinaryData() != null) { - webSocketChunk.setContent(ChannelBuffers.wrappedBuffer(frame.getBinaryData())); - ResponseBodyPart rp = new ResponseBodyPart(future.getURI(), null, NettyAsyncHttpProvider.this, webSocketChunk, true); - h.onBodyPartReceived(rp); - - NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted()); - - if (webSocket != null) { - if (pendingOpcode == OPCODE_BINARY) { - webSocket.onBinaryFragment(rp.getBodyPartBytes(), frame.isFinalFragment()); - } else { - webSocket.onTextFragment(frame.getBinaryData().toString(UTF8), frame.isFinalFragment()); - } - - if (frame instanceof CloseWebSocketFrame) { - try { - ctx.setAttachment(DiscardEvent.class); - webSocket.onClose(CloseWebSocketFrame.class.cast(frame).getStatusCode(), CloseWebSocketFrame.class.cast(frame).getReasonText()); - } catch (Throwable t) { - // Swallow any exception that may comes from a Netty version released before 3.4.0 - log.trace("", t); - } - } - } else { - log.debug("UpgradeHandler returned a null NettyWebSocket "); - } - } - } else { - log.error("Invalid attachment {}", ctx.getAttachment()); - } - } - - // @Override - public void onError(ChannelHandlerContext ctx, ExceptionEvent e) { - try { - log.warn("onError {}", e); - if (!(ctx.getAttachment() instanceof NettyResponseFuture)) { - return; - } - - NettyResponseFuture nettyResponse = NettyResponseFuture.class.cast(ctx.getAttachment()); - WebSocketUpgradeHandler h = WebSocketUpgradeHandler.class.cast(nettyResponse.getAsyncHandler()); - - NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted()); - if (webSocket != null) { - webSocket.onError(e.getCause()); - webSocket.close(); - } - } catch (Throwable t) { - log.error("onError", t); - } - } - - // @Override - public void onClose(ChannelHandlerContext ctx, ChannelStateEvent e) { - log.trace("onClose {}", e); - if (!(ctx.getAttachment() instanceof NettyResponseFuture)) { - return; - } - - try { - NettyResponseFuture nettyResponse = NettyResponseFuture.class.cast(ctx.getAttachment()); - WebSocketUpgradeHandler h = WebSocketUpgradeHandler.class.cast(nettyResponse.getAsyncHandler()); - NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted()); - - if (!(ctx.getAttachment() instanceof DiscardEvent)) - webSocket.close(1006, "Connection was closed abnormally (that is, with no close frame being sent)."); - } catch (Throwable t) { - log.error("onError", t); - } - } - } - - private static boolean isWebSocket(URI uri) { - return WEBSOCKET.equalsIgnoreCase(uri.getScheme()) || WEBSOCKET_SSL.equalsIgnoreCase(uri.getScheme()); - } - - private static boolean isSecure(String scheme) { - return HTTPS.equalsIgnoreCase(scheme) || WEBSOCKET_SSL.equalsIgnoreCase(scheme); - } - - private static boolean isSecure(URI uri) { - return isSecure(uri.getScheme()); + public ListenableFuture execute(Request request, final AsyncHandler asyncHandler) throws IOException { + return requestSender.sendRequest(request, asyncHandler, null, asyncHttpProviderConfig.isAsyncConnect(), false); } } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java index d184b19aa7..210f1dc8a9 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java @@ -16,16 +16,17 @@ */ package org.asynchttpclient.providers.netty; +import io.netty.channel.Channel; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; + import java.util.HashMap; -import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.concurrent.ExecutorService; -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; +import org.asynchttpclient.AsyncHttpProviderConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.asynchttpclient.AsyncHttpProviderConfig; /** * This class can be used to pass Netty's internal configuration options. See Netty documentation for more information. @@ -40,14 +41,19 @@ public class NettyAsyncHttpProviderConfig implements AsyncHttpProviderConfig properties = new HashMap(); @@ -96,7 +102,7 @@ public NettyAsyncHttpProviderConfig() { */ public NettyAsyncHttpProviderConfig addProperty(String name, Object value) { - if (name.equals(REUSE_ADDRESS) && value == Boolean.TRUE && System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win")) { + if (name.equals(REUSE_ADDRESS) && value == Boolean.TRUE && System.getProperty("os.name").toLowerCase().contains("win")) { LOGGER.warn("Can't enable {} on Windows", REUSE_ADDRESS); } else { properties.put(name, value); @@ -142,20 +148,20 @@ public void setUseBlockingIO(boolean useBlockingIO) { this.useBlockingIO = useBlockingIO; } - public NioClientSocketChannelFactory getSocketChannelFactory() { - return socketChannelFactory; + public EventLoopGroup getEventLoopGroup() { + return eventLoopGroup; } - public void setSocketChannelFactory(NioClientSocketChannelFactory socketChannelFactory) { - this.socketChannelFactory = socketChannelFactory; + public void setEventLoopGroup(EventLoopGroup eventLoopGroup) { + this.eventLoopGroup = eventLoopGroup; } - public ExecutorService getBossExecutorService() { - return bossExecutorService; + public boolean isAsyncConnect() { + return asyncConnect; } - public void setBossExecutorService(ExecutorService bossExecutorService) { - this.bossExecutorService = bossExecutorService; + public void setAsyncConnect(boolean asyncConnect) { + this.asyncConnect = asyncConnect; } public int getMaxInitialLineLength() { @@ -181,4 +187,41 @@ public int getMaxChunkSize() { public void setMaxChunkSize(int maxChunkSize) { this.maxChunkSize = maxChunkSize; } + + public AdditionalChannelInitializer getHttpAdditionalChannelInitializer() { + return httpAdditionalChannelInitializer; + } + + public void setHttpAdditionalChannelInitializer(AdditionalChannelInitializer httpAdditionalChannelInitializer) { + this.httpAdditionalChannelInitializer = httpAdditionalChannelInitializer; + } + + public AdditionalChannelInitializer getWsAdditionalChannelInitializer() { + return wsAdditionalChannelInitializer; + } + + public void setWsAdditionalChannelInitializer(AdditionalChannelInitializer wsAdditionalChannelInitializer) { + this.wsAdditionalChannelInitializer = wsAdditionalChannelInitializer; + } + + public AdditionalChannelInitializer getHttpsAdditionalChannelInitializer() { + return httpsAdditionalChannelInitializer; + } + + public void setHttpsAdditionalChannelInitializer(AdditionalChannelInitializer httpsAdditionalChannelInitializer) { + this.httpsAdditionalChannelInitializer = httpsAdditionalChannelInitializer; + } + + public AdditionalChannelInitializer getWssAdditionalChannelInitializer() { + return wssAdditionalChannelInitializer; + } + + public void setWssAdditionalChannelInitializer(AdditionalChannelInitializer wssAdditionalChannelInitializer) { + this.wssAdditionalChannelInitializer = wssAdditionalChannelInitializer; + } + + public static interface AdditionalChannelInitializer { + + void initChannel(Channel ch) throws Exception; + } } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyConnectListener.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyConnectListener.java deleted file mode 100644 index 0d090275fb..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyConnectListener.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file to you under the Apache License, version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - */ -package org.asynchttpclient.providers.netty; - -import java.io.IOException; -import java.net.ConnectException; -import java.net.URI; -import java.nio.channels.ClosedChannelException; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.net.ssl.HostnameVerifier; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelFuture; -import org.jboss.netty.channel.ChannelFutureListener; -import org.jboss.netty.handler.codec.http.HttpRequest; -import org.jboss.netty.handler.ssl.SslHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Request; -import org.asynchttpclient.util.ProxyUtils; - - -/** - * Non Blocking connect. - */ -final class NettyConnectListener implements ChannelFutureListener { - private final static Logger logger = LoggerFactory.getLogger(NettyConnectListener.class); - private final AsyncHttpClientConfig config; - private final NettyResponseFuture future; - private final HttpRequest nettyRequest; - private final AtomicBoolean handshakeDone = new AtomicBoolean(false); - - private NettyConnectListener(AsyncHttpClientConfig config, - NettyResponseFuture future, - HttpRequest nettyRequest) { - this.config = config; - this.future = future; - this.nettyRequest = nettyRequest; - } - - public NettyResponseFuture future() { - return future; - } - - public final void operationComplete(ChannelFuture f) throws Exception { - if (f.isSuccess()) { - Channel channel = f.getChannel(); - channel.getPipeline().getContext(NettyAsyncHttpProvider.class).setAttachment(future); - SslHandler sslHandler = (SslHandler) channel.getPipeline().get(NettyAsyncHttpProvider.SSL_HANDLER); - if (!handshakeDone.getAndSet(true) && (sslHandler != null)) { - ((SslHandler) channel.getPipeline().get(NettyAsyncHttpProvider.SSL_HANDLER)).handshake().addListener(this); - return; - } - - HostnameVerifier v = config.getHostnameVerifier(); - if (sslHandler != null) { - if (!v.verify(future.getURI().getHost(), sslHandler.getEngine().getSession())) { - ConnectException exception = new ConnectException("HostnameVerifier exception."); - future.abort(exception); - throw exception; - } - } - - future.provider().writeRequest(f.getChannel(), config, future, nettyRequest); - } else { - Throwable cause = f.getCause(); - - logger.debug("Trying to recover a dead cached channel {} with a retry value of {} ", f.getChannel(), future.canRetry()); - if (future.canRetry() && cause != null && (NettyAsyncHttpProvider.abortOnDisconnectException(cause) - || cause instanceof ClosedChannelException - || future.getState() != NettyResponseFuture.STATE.NEW)) { - - logger.debug("Retrying {} ", nettyRequest); - if (future.provider().remotelyClosed(f.getChannel(), future)) { - return; - } - } - - logger.debug("Failed to recover from exception: {} with channel {}", cause, f.getChannel()); - - boolean printCause = f.getCause() != null && cause.getMessage() != null; - ConnectException e = new ConnectException(printCause ? cause.getMessage() + " to " + future.getURI().toString() : future.getURI().toString()); - if (cause != null) { - e.initCause(cause); - } - future.abort(e); - } - } - - public static class Builder { - private final AsyncHttpClientConfig config; - - private final Request request; - private final AsyncHandler asyncHandler; - private NettyResponseFuture future; - private final NettyAsyncHttpProvider provider; - private final ChannelBuffer buffer; - - public Builder(AsyncHttpClientConfig config, Request request, AsyncHandler asyncHandler, - NettyAsyncHttpProvider provider, ChannelBuffer buffer) { - - this.config = config; - this.request = request; - this.asyncHandler = asyncHandler; - this.future = null; - this.provider = provider; - this.buffer = buffer; - } - - public Builder(AsyncHttpClientConfig config, Request request, AsyncHandler asyncHandler, - NettyResponseFuture future, NettyAsyncHttpProvider provider, ChannelBuffer buffer) { - - this.config = config; - this.request = request; - this.asyncHandler = asyncHandler; - this.future = future; - this.provider = provider; - this.buffer = buffer; - } - - public NettyConnectListener build(final URI uri) throws IOException { - ProxyServer proxyServer = ProxyUtils.getProxyServer(config, request); - HttpRequest nettyRequest = NettyAsyncHttpProvider.buildRequest(config, request, uri, true, buffer, proxyServer); - if (future == null) { - future = NettyAsyncHttpProvider.newFuture(uri, request, asyncHandler, nettyRequest, config, provider, proxyServer); - } else { - future.setNettyRequest(nettyRequest); - future.setRequest(request); - } - return new NettyConnectListener(config, future, nettyRequest); - } - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyConnectionsPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyConnectionsPool.java deleted file mode 100644 index 96b273f879..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyConnectionsPool.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty; - -import static org.asynchttpclient.util.DateUtil.millisTime; -import org.asynchttpclient.ConnectionsPool; -import org.jboss.netty.channel.Channel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * A simple implementation of {@link org.asynchttpclient.ConnectionsPool} based on a {@link java.util.concurrent.ConcurrentHashMap} - */ -public class NettyConnectionsPool implements ConnectionsPool { - - private final static Logger log = LoggerFactory.getLogger(NettyConnectionsPool.class); - private final ConcurrentHashMap> connectionsPool = new ConcurrentHashMap>(); - private final ConcurrentHashMap channel2IdleChannel = new ConcurrentHashMap(); - private final ConcurrentHashMap channel2CreationDate = new ConcurrentHashMap(); - private final AtomicBoolean isClosed = new AtomicBoolean(false); - private final Timer idleConnectionDetector; - private final boolean sslConnectionPoolEnabled; - private final int maxTotalConnections; - private final int maxConnectionPerHost; - private final int maxConnectionLifeTimeInMs; - private final long maxIdleTime; - - public NettyConnectionsPool(NettyAsyncHttpProvider provider) { - this(provider.getConfig().getMaxTotalConnections(), provider.getConfig().getMaxConnectionPerHost(), provider.getConfig().getIdleConnectionInPoolTimeoutInMs(), provider.getConfig().isSslConnectionPoolEnabled(), provider.getConfig().getMaxConnectionLifeTimeInMs(), new Timer(true)); - } - - public NettyConnectionsPool(int maxTotalConnections, int maxConnectionPerHost, long maxIdleTime, boolean sslConnectionPoolEnabled, int maxConnectionLifeTimeInMs, Timer idleConnectionDetector) { - this.maxTotalConnections = maxTotalConnections; - this.maxConnectionPerHost = maxConnectionPerHost; - this.sslConnectionPoolEnabled = sslConnectionPoolEnabled; - this.maxIdleTime = maxIdleTime; - this.maxConnectionLifeTimeInMs = maxConnectionLifeTimeInMs; - this.idleConnectionDetector = idleConnectionDetector; - this.idleConnectionDetector.schedule(new IdleChannelDetector(), maxIdleTime, maxIdleTime); - } - - private static class IdleChannel { - final String uri; - final Channel channel; - final long start; - - IdleChannel(String uri, Channel channel) { - this.uri = uri; - this.channel = channel; - this.start = millisTime(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof IdleChannel)) return false; - - IdleChannel that = (IdleChannel) o; - - if (channel != null ? !channel.equals(that.channel) : that.channel != null) return false; - - return true; - } - - @Override - public int hashCode() { - return channel != null ? channel.hashCode() : 0; - } - } - - private class IdleChannelDetector extends TimerTask { - @Override - public void run() { - try { - if (isClosed.get()) return; - - if (log.isDebugEnabled()) { - Set keys = connectionsPool.keySet(); - - for (String s : keys) { - log.debug("Entry count for : {} : {}", s, connectionsPool.get(s).size()); - } - } - - List channelsInTimeout = new ArrayList(); - long currentTime = millisTime(); - - for (IdleChannel idleChannel : channel2IdleChannel.values()) { - long age = currentTime - idleChannel.start; - if (age > maxIdleTime) { - - log.debug("Adding Candidate Idle Channel {}", idleChannel.channel); - - // store in an unsynchronized list to minimize the impact on the ConcurrentHashMap. - channelsInTimeout.add(idleChannel); - } - } - long endConcurrentLoop = millisTime(); - - for (IdleChannel idleChannel : channelsInTimeout) { - Object attachment = idleChannel.channel.getPipeline().getContext(NettyAsyncHttpProvider.class).getAttachment(); - if (attachment != null) { - if (attachment instanceof NettyResponseFuture) { - NettyResponseFuture future = (NettyResponseFuture) attachment; - - if (!future.isDone() && !future.isCancelled()) { - log.debug("Future not in appropriate state %s\n", future); - continue; - } - } - } - - if (remove(idleChannel)) { - log.debug("Closing Idle Channel {}", idleChannel.channel); - close(idleChannel.channel); - } - } - - if (log.isTraceEnabled()) { - int openChannels = 0; - for (ConcurrentLinkedQueue hostChannels: connectionsPool.values()) { - openChannels += hostChannels.size(); - } - log.trace(String.format("%d channel open, %d idle channels closed (times: 1st-loop=%d, 2nd-loop=%d).\n", - openChannels, channelsInTimeout.size(), endConcurrentLoop - currentTime, millisTime() - endConcurrentLoop)); - } - } catch (Throwable t) { - log.error("uncaught exception!", t); - } - } - } - - /** - * {@inheritDoc} - */ - public boolean offer(String uri, Channel channel) { - if (isClosed.get()) return false; - - if (!sslConnectionPoolEnabled && uri.startsWith("https")) { - return false; - } - - Long createTime = channel2CreationDate.get(channel); - if (createTime == null){ - channel2CreationDate.putIfAbsent(channel, millisTime()); - } - else if (maxConnectionLifeTimeInMs != -1 && (createTime + maxConnectionLifeTimeInMs) < millisTime() ) { - log.debug("Channel {} expired", channel); - return false; - } - - log.debug("Adding uri: {} for channel {}", uri, channel); - channel.getPipeline().getContext(NettyAsyncHttpProvider.class).setAttachment(new NettyAsyncHttpProvider.DiscardEvent()); - - ConcurrentLinkedQueue idleConnectionForHost = connectionsPool.get(uri); - if (idleConnectionForHost == null) { - ConcurrentLinkedQueue newPool = new ConcurrentLinkedQueue(); - idleConnectionForHost = connectionsPool.putIfAbsent(uri, newPool); - if (idleConnectionForHost == null) idleConnectionForHost = newPool; - } - - boolean added; - int size = idleConnectionForHost.size(); - if (maxConnectionPerHost == -1 || size < maxConnectionPerHost) { - IdleChannel idleChannel = new IdleChannel(uri, channel); - synchronized (idleConnectionForHost) { - added = idleConnectionForHost.add(idleChannel); - - if (channel2IdleChannel.put(channel, idleChannel) != null) { - log.error("Channel {} already exists in the connections pool!", channel); - } - } - } else { - log.debug("Maximum number of requests per host reached {} for {}", maxConnectionPerHost, uri); - added = false; - } - return added; - } - - /** - * {@inheritDoc} - */ - public Channel poll(String uri) { - if (!sslConnectionPoolEnabled && uri.startsWith("https")) { - return null; - } - - IdleChannel idleChannel = null; - ConcurrentLinkedQueue idleConnectionForHost = connectionsPool.get(uri); - if (idleConnectionForHost != null) { - boolean poolEmpty = false; - while (!poolEmpty && idleChannel == null) { - if (idleConnectionForHost.size() > 0) { - synchronized (idleConnectionForHost) { - idleChannel = idleConnectionForHost.poll(); - if (idleChannel != null) { - channel2IdleChannel.remove(idleChannel.channel); - } - } - } - - if (idleChannel == null) { - poolEmpty = true; - } else if (!idleChannel.channel.isConnected() || !idleChannel.channel.isOpen()) { - idleChannel = null; - log.trace("Channel not connected or not opened!"); - } - } - } - return idleChannel != null ? idleChannel.channel : null; - } - - private boolean remove(IdleChannel pooledChannel) { - if (pooledChannel == null || isClosed.get()) return false; - - boolean isRemoved = false; - ConcurrentLinkedQueue pooledConnectionForHost = connectionsPool.get(pooledChannel.uri); - if (pooledConnectionForHost != null) { - isRemoved = pooledConnectionForHost.remove(pooledChannel); - } - isRemoved |= channel2IdleChannel.remove(pooledChannel.channel) != null; - return isRemoved; - } - - /** - * {@inheritDoc} - */ - public boolean removeAll(Channel channel) { - channel2CreationDate.remove(channel); - return !isClosed.get() && remove(channel2IdleChannel.get(channel)); - } - - /** - * {@inheritDoc} - */ - public boolean canCacheConnection() { - if (!isClosed.get() && maxTotalConnections != -1 && channel2IdleChannel.size() >= maxTotalConnections) { - return false; - } else { - return true; - } - } - - /** - * {@inheritDoc} - */ - public void destroy() { - if (isClosed.getAndSet(true)) return; - - // stop timer - idleConnectionDetector.cancel(); - idleConnectionDetector.purge(); - - for (Channel channel : channel2IdleChannel.keySet()) { - close(channel); - } - connectionsPool.clear(); - channel2IdleChannel.clear(); - channel2CreationDate.clear(); - } - - private void close(Channel channel) { - try { - channel.getPipeline().getContext(NettyAsyncHttpProvider.class).setAttachment(new NettyAsyncHttpProvider.DiscardEvent()); - channel2CreationDate.remove(channel); - channel.close(); - } catch (Throwable t) { - // noop - } - } - - public final String toString() { - return String.format("NettyConnectionPool: {pool-size: %d}", channel2IdleChannel.size()); - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyResponse.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyResponse.java deleted file mode 100644 index 81b520fbc6..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyResponse.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file to you under the Apache License, version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package org.asynchttpclient.providers.netty; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBufferInputStream; -import org.jboss.netty.buffer.ChannelBuffers; - -import org.asynchttpclient.Cookie; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.providers.ResponseBase; -import org.asynchttpclient.providers.netty.util.ChannelBufferUtil; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.asynchttpclient.org.jboss.netty.handler.codec.http.CookieDecoder; - -/** - * Wrapper around the {@link org.asynchttpclient.Response} API. - */ -public class NettyResponse extends ResponseBase { - - public NettyResponse(HttpResponseStatus status, - HttpResponseHeaders headers, - List bodyParts) { - super(status, headers, bodyParts); - } - - /* @Override */ - public String getResponseBodyExcerpt(int maxLength) throws IOException { - return getResponseBodyExcerpt(maxLength, null); - } - - public String getResponseBodyExcerpt(int maxLength, String charset) throws IOException { - // should be fine; except that it may split multi-byte chars (last char may become '?') - charset = calculateCharset(charset); - byte[] b = AsyncHttpProviderUtils.contentToBytes(bodyParts, maxLength); - return new String(b, charset); - } - - protected List buildCookies() { - List cookies = new ArrayList(); - for (Map.Entry> header : headers.getHeaders().entrySet()) { - if (header.getKey().equalsIgnoreCase("Set-Cookie")) { - // TODO: ask for parsed header - List v = header.getValue(); - for (String value : v) { - cookies.addAll(CookieDecoder.decode(value)); - } - } - } - return Collections.unmodifiableList(cookies); - } - - /* @Override */ - public byte[] getResponseBodyAsBytes() throws IOException { - return ChannelBufferUtil.channelBuffer2bytes(getResponseBodyAsChannelBuffer()); - } - - /* @Override */ - public ByteBuffer getResponseBodyAsByteBuffer() throws IOException { - return getResponseBodyAsChannelBuffer().toByteBuffer(); - } - - /* @Override */ - public String getResponseBody() throws IOException { - return getResponseBody(null); - } - - /* @Override */ - public String getResponseBody(String charset) throws IOException { - return getResponseBodyAsChannelBuffer().toString(Charset.forName(calculateCharset(charset))); - } - - /* @Override */ - public InputStream getResponseBodyAsStream() throws IOException { - return new ChannelBufferInputStream(getResponseBodyAsChannelBuffer()); - } - - public ChannelBuffer getResponseBodyAsChannelBuffer() throws IOException { - ChannelBuffer b = null; - switch (bodyParts.size()) { - case 0: - b = ChannelBuffers.EMPTY_BUFFER; - break; - case 1: - b = ResponseBodyPart.class.cast(bodyParts.get(0)).getChannelBuffer(); - break; - default: - ChannelBuffer[] channelBuffers = new ChannelBuffer[bodyParts.size()]; - for (int i = 0; i < bodyParts.size(); i++) { - channelBuffers[i] = ResponseBodyPart.class.cast(bodyParts.get(i)).getChannelBuffer(); - } - b = ChannelBuffers.wrappedBuffer(channelBuffers); - } - - return b; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyResponseFuture.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyResponseFuture.java deleted file mode 100755 index a5e75f4c32..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyResponseFuture.java +++ /dev/null @@ -1,515 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file to you under the Apache License, version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package org.asynchttpclient.providers.netty; - -import static org.asynchttpclient.util.DateUtil.millisTime; -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.ConnectionPoolKeyStrategy; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Request; -import org.asynchttpclient.listenable.AbstractListenableFuture; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.handler.codec.http.HttpRequest; -import org.jboss.netty.handler.codec.http.HttpResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.MalformedURLException; -import java.net.URI; -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 java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; - -/** - * A {@link Future} that can be used to track when an asynchronous HTTP request has been fully processed. - * - * @param - */ -public final class NettyResponseFuture extends AbstractListenableFuture { - - private final static Logger logger = LoggerFactory.getLogger(NettyResponseFuture.class); - public final static String MAX_RETRY = "org.asynchttpclient.providers.netty.maxRetry"; - - enum STATE { - NEW, POOLED, RECONNECTED, CLOSED, - } - - private final CountDownLatch latch = new CountDownLatch(1); - private final AtomicBoolean isDone = new AtomicBoolean(false); - private final AtomicBoolean isCancelled = new AtomicBoolean(false); - private AsyncHandler asyncHandler; - private final int requestTimeoutInMs; - private final int idleConnectionTimeoutInMs; - private Request request; - private HttpRequest nettyRequest; - private final AtomicReference content = new AtomicReference(); - private URI uri; - private boolean keepAlive = true; - private HttpResponse httpResponse; - private final AtomicReference exEx = new AtomicReference(); - private final AtomicInteger redirectCount = new AtomicInteger(); - private volatile Future reaperFuture; - private final AtomicBoolean inAuth = new AtomicBoolean(false); - private final AtomicBoolean statusReceived = new AtomicBoolean(false); - private final AtomicLong touch = new AtomicLong(millisTime()); - private final long start = millisTime(); - private final NettyAsyncHttpProvider asyncHttpProvider; - private final AtomicReference state = new AtomicReference(STATE.NEW); - private final AtomicBoolean contentProcessed = new AtomicBoolean(false); - private Channel channel; - private boolean reuseChannel = false; - private final AtomicInteger currentRetry = new AtomicInteger(0); - private final int maxRetry; - private boolean writeHeaders; - private boolean writeBody; - private final AtomicBoolean throwableCalled = new AtomicBoolean(false); - private boolean allowConnect = false; - private final ConnectionPoolKeyStrategy connectionPoolKeyStrategy; - private final ProxyServer proxyServer; - - public NettyResponseFuture(URI uri,// - Request request,// - AsyncHandler asyncHandler,// - HttpRequest nettyRequest,// - int requestTimeoutInMs,// - int idleConnectionTimeoutInMs,// - NettyAsyncHttpProvider asyncHttpProvider,// - ConnectionPoolKeyStrategy connectionPoolKeyStrategy,// - ProxyServer proxyServer) { - - this.asyncHandler = asyncHandler; - this.requestTimeoutInMs = requestTimeoutInMs; - this.idleConnectionTimeoutInMs = idleConnectionTimeoutInMs; - this.request = request; - this.nettyRequest = nettyRequest; - this.uri = uri; - this.asyncHttpProvider = asyncHttpProvider; - this.connectionPoolKeyStrategy = connectionPoolKeyStrategy; - this.proxyServer = proxyServer; - - if (System.getProperty(MAX_RETRY) != null) { - maxRetry = Integer.valueOf(System.getProperty(MAX_RETRY)); - } else { - maxRetry = asyncHttpProvider.getConfig().getMaxRequestRetry(); - } - writeHeaders = true; - writeBody = true; - } - - protected URI getURI() throws MalformedURLException { - return uri; - } - - protected void setURI(URI uri) { - this.uri = uri; - } - - public ConnectionPoolKeyStrategy getConnectionPoolKeyStrategy() { - return connectionPoolKeyStrategy; - } - - public ProxyServer getProxyServer() { - return proxyServer; - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public boolean isDone() { - return isDone.get(); - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public boolean isCancelled() { - return isCancelled.get(); - } - - void setAsyncHandler(AsyncHandler asyncHandler) { - this.asyncHandler = asyncHandler; - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public boolean cancel(boolean force) { - cancelReaper(); - - if (isCancelled.get()) - return false; - - try { - channel.getPipeline().getContext(NettyAsyncHttpProvider.class).setAttachment(new NettyAsyncHttpProvider.DiscardEvent()); - channel.close(); - } catch (Throwable t) { - // Ignore - } - if (!throwableCalled.getAndSet(true)) { - try { - asyncHandler.onThrowable(new CancellationException()); - } catch (Throwable t) { - logger.warn("cancel", t); - } - } - latch.countDown(); - isCancelled.set(true); - runListeners(); - return true; - } - - /** - * Is the Future still valid - * - * @return true if response has expired and should be terminated. - */ - public boolean hasExpired() { - long now = millisTime(); - return hasConnectionIdleTimedOut(now) || hasRequestTimedOut(now); - } - - public boolean hasConnectionIdleTimedOut(long now) { - return idleConnectionTimeoutInMs != -1 && (now - touch.get()) >= idleConnectionTimeoutInMs; - } - - public boolean hasRequestTimedOut(long now) { - return requestTimeoutInMs != -1 && (now - start) >= requestTimeoutInMs; - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public V get() throws InterruptedException, ExecutionException { - try { - return get(requestTimeoutInMs, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - cancelReaper(); - throw new ExecutionException(e); - } - } - - void cancelReaper() { - if (reaperFuture != null) { - reaperFuture.cancel(false); - } - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public V get(long l, TimeUnit tu) throws InterruptedException, TimeoutException, ExecutionException { - if (!isDone() && !isCancelled()) { - boolean expired = false; - if (l == -1) { - latch.await(); - } else { - expired = !latch.await(l, tu); - } - - if (expired) { - isCancelled.set(true); - try { - channel.getPipeline().getContext(NettyAsyncHttpProvider.class).setAttachment(new NettyAsyncHttpProvider.DiscardEvent()); - channel.close(); - } catch (Throwable t) { - // Ignore - } - TimeoutException te = new TimeoutException(String.format("No response received after %s %s", l, tu.name().toLowerCase())); - if (!throwableCalled.getAndSet(true)) { - try { - asyncHandler.onThrowable(te); - } catch (Throwable t) { - logger.debug("asyncHandler.onThrowable", t); - } - cancelReaper(); - throw new ExecutionException(te); - } - } - isDone.set(true); - - ExecutionException e = exEx.getAndSet(null); - if (e != null) { - throw e; - } - } - return getContent(); - } - - V getContent() throws ExecutionException { - ExecutionException e = exEx.getAndSet(null); - if (e != null) { - throw e; - } - - V update = content.get(); - // No more retry - currentRetry.set(maxRetry); - if (exEx.get() == null && !contentProcessed.getAndSet(true)) { - try { - update = asyncHandler.onCompleted(); - } catch (Throwable ex) { - if (!throwableCalled.getAndSet(true)) { - try { - asyncHandler.onThrowable(ex); - } catch (Throwable t) { - logger.debug("asyncHandler.onThrowable", t); - } - cancelReaper(); - throw new RuntimeException(ex); - } - } - content.compareAndSet(null, update); - } - return update; - } - - public final void done() { - - try { - cancelReaper(); - - if (exEx.get() != null) { - return; - } - getContent(); - isDone.set(true); - } catch (ExecutionException t) { - return; - } catch (RuntimeException t) { - Throwable exception = t.getCause() != null ? t.getCause() : t; - exEx.compareAndSet(null, new ExecutionException(exception)); - - } finally { - latch.countDown(); - } - - runListeners(); - } - - public final void abort(final Throwable t) { - cancelReaper(); - - if (isDone.get() || isCancelled.get()) - return; - - exEx.compareAndSet(null, new ExecutionException(t)); - if (!throwableCalled.getAndSet(true)) { - try { - asyncHandler.onThrowable(t); - } catch (Throwable te) { - logger.debug("asyncHandler.onThrowable", te); - } finally { - isCancelled.set(true); - } - } - latch.countDown(); - runListeners(); - } - - public void content(V v) { - content.set(v); - } - - protected final Request getRequest() { - return request; - } - - public final HttpRequest getNettyRequest() { - return nettyRequest; - } - - protected final void setNettyRequest(HttpRequest nettyRequest) { - this.nettyRequest = nettyRequest; - } - - protected final AsyncHandler getAsyncHandler() { - return asyncHandler; - } - - protected final boolean isKeepAlive() { - return keepAlive; - } - - protected final void setKeepAlive(final boolean keepAlive) { - this.keepAlive = keepAlive; - } - - protected final HttpResponse getHttpResponse() { - return httpResponse; - } - - protected final void setHttpResponse(final HttpResponse httpResponse) { - this.httpResponse = httpResponse; - } - - protected int incrementAndGetCurrentRedirectCount() { - return redirectCount.incrementAndGet(); - } - - protected void setReaperFuture(Future reaperFuture) { - cancelReaper(); - this.reaperFuture = reaperFuture; - } - - protected boolean isInAuth() { - return inAuth.get(); - } - - protected boolean getAndSetAuth(boolean inDigestAuth) { - return inAuth.getAndSet(inDigestAuth); - } - - protected STATE getState() { - return state.get(); - } - - protected void setState(STATE state) { - this.state.set(state); - } - - public boolean getAndSetStatusReceived(boolean sr) { - return statusReceived.getAndSet(sr); - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public void touch() { - touch.set(millisTime()); - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public boolean getAndSetWriteHeaders(boolean writeHeaders) { - boolean b = this.writeHeaders; - this.writeHeaders = writeHeaders; - return b; - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public boolean getAndSetWriteBody(boolean writeBody) { - boolean b = this.writeBody; - this.writeBody = writeBody; - return b; - } - - protected NettyAsyncHttpProvider provider() { - return asyncHttpProvider; - } - - protected void attachChannel(Channel channel) { - this.channel = channel; - } - - public void setReuseChannel(boolean reuseChannel) { - this.reuseChannel = reuseChannel; - } - - public boolean isConnectAllowed() { - return allowConnect; - } - - public void setConnectAllowed(boolean allowConnect) { - this.allowConnect = allowConnect; - } - - protected void attachChannel(Channel channel, boolean reuseChannel) { - this.channel = channel; - this.reuseChannel = reuseChannel; - } - - protected Channel channel() { - return channel; - } - - protected boolean reuseChannel() { - return reuseChannel; - } - - protected boolean canRetry() { - if (currentRetry.incrementAndGet() > maxRetry) { - return false; - } - return true; - } - - public void setRequest(Request request) { - this.request = request; - } - - /** - * Return true if the {@link Future} cannot be recovered. There is some scenario where a connection can be closed by an unexpected IOException, and in some situation we can - * recover from that exception. - * - * @return true if that {@link Future} cannot be recovered. - */ - public boolean cannotBeReplay() { - return isDone() || !canRetry() || isCancelled() || (channel() != null && channel().isOpen() && uri.getScheme().compareToIgnoreCase("https") != 0) || isInAuth(); - } - - public long getStart() { - return start; - } - - public long getRequestTimeoutInMs() { - return requestTimeoutInMs; - } - - public long getIdleConnectionTimeoutInMs() { - return idleConnectionTimeoutInMs; - } - - @Override - public String toString() { - return "NettyResponseFuture{" + // - "currentRetry=" + currentRetry + // - ",\n\tisDone=" + isDone + // - ",\n\tisCancelled=" + isCancelled + // - ",\n\tasyncHandler=" + asyncHandler + // - ",\n\trequestTimeoutInMs=" + requestTimeoutInMs + // - ",\n\tnettyRequest=" + nettyRequest + // - ",\n\tcontent=" + content + // - ",\n\turi=" + uri + // - ",\n\tkeepAlive=" + keepAlive + // - ",\n\thttpResponse=" + httpResponse + // - ",\n\texEx=" + exEx + // - ",\n\tredirectCount=" + redirectCount + // - ",\n\treaperFuture=" + reaperFuture + // - ",\n\tinAuth=" + inAuth + // - ",\n\tstatusReceived=" + statusReceived + // - ",\n\ttouch=" + touch + // - '}'; - } - -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyWebSocket.java deleted file mode 100644 index 38c08df1a3..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyWebSocket.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty; - -import org.asynchttpclient.websocket.WebSocket; -import org.asynchttpclient.websocket.WebSocketByteListener; -import org.asynchttpclient.websocket.WebSocketCloseCodeReasonListener; -import org.asynchttpclient.websocket.WebSocketListener; -import org.asynchttpclient.websocket.WebSocketTextListener; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; -import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame; -import org.jboss.netty.handler.codec.http.websocketx.PingWebSocketFrame; -import org.jboss.netty.handler.codec.http.websocketx.PongWebSocketFrame; -import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayOutputStream; -import java.util.concurrent.ConcurrentLinkedQueue; - -import static org.jboss.netty.buffer.ChannelBuffers.wrappedBuffer; - -public class NettyWebSocket implements WebSocket { - private final static Logger logger = LoggerFactory.getLogger(NettyWebSocket.class); - - private final Channel channel; - private final ConcurrentLinkedQueue listeners = new ConcurrentLinkedQueue(); - - private StringBuilder textBuffer; - private ByteArrayOutputStream byteBuffer; - private int maxBufferSize = 128000000; - - public NettyWebSocket(Channel channel) { - this.channel = channel; - } - - // @Override - public WebSocket sendMessage(byte[] message) { - channel.write(new BinaryWebSocketFrame(wrappedBuffer(message))); - return this; - } - - // @Override - public WebSocket stream(byte[] fragment, boolean last) { - throw new UnsupportedOperationException("Streaming currently only supported by the Grizzly provider."); - } - - // @Override - public WebSocket stream(byte[] fragment, int offset, int len, boolean last) { - throw new UnsupportedOperationException("Streaming currently only supported by the Grizzly provider."); - } - - // @Override - public WebSocket sendTextMessage(String message) { - channel.write(new TextWebSocketFrame(message)); - return this; - } - - // @Override - public WebSocket streamText(String fragment, boolean last) { - throw new UnsupportedOperationException("Streaming currently only supported by the Grizzly provider."); - } - - // @Override - public WebSocket sendPing(byte[] payload) { - channel.write(new PingWebSocketFrame(wrappedBuffer(payload))); - return this; - } - - // @Override - public WebSocket sendPong(byte[] payload) { - channel.write(new PongWebSocketFrame(wrappedBuffer(payload))); - return this; - } - - // @Override - public WebSocket addWebSocketListener(WebSocketListener l) { - listeners.add(l); - return this; - } - - // @Override - public WebSocket removeWebSocketListener(WebSocketListener l) { - listeners.remove(l); - return this; - } - - public int getMaxBufferSize() { - return maxBufferSize; - } - - public void setMaxBufferSize(int bufferSize) { - maxBufferSize = bufferSize; - - if(maxBufferSize < 8192) - maxBufferSize = 8192; - } - - // @Override - public boolean isOpen() { - return channel.isOpen(); - } - - // @Override - public void close() { - onClose(); - listeners.clear(); - try { - channel.write(new CloseWebSocketFrame()); - channel.getCloseFuture().awaitUninterruptibly(); - } finally { - channel.close(); - } - } - - // @Override - public void close(int statusCode, String reason) { - onClose(statusCode, reason); - listeners.clear(); - try { - channel.write(new CloseWebSocketFrame(statusCode, reason)); - channel.getCloseFuture().awaitUninterruptibly(); - } finally { - channel.close(); - } - } - - protected void onBinaryFragment(byte[] message, boolean last) { - for (WebSocketListener l : listeners) { - if (l instanceof WebSocketByteListener) { - try { - WebSocketByteListener.class.cast(l).onFragment(message,last); - - if(byteBuffer == null) { - byteBuffer = new ByteArrayOutputStream(); - } - - byteBuffer.write(message); - - if(byteBuffer.size() > maxBufferSize) { - Exception e = new Exception("Exceeded Netty Web Socket maximum buffer size of " + getMaxBufferSize()); - l.onError(e); - this.close(); - return; - } - - - if(last) { - WebSocketByteListener.class.cast(l).onMessage(byteBuffer.toByteArray()); - byteBuffer = null; - textBuffer = null; - } - } catch (Exception ex) { - l.onError(ex); - } - } - } - } - - protected void onTextFragment(String message, boolean last) { - for (WebSocketListener l : listeners) { - if (l instanceof WebSocketTextListener) { - try { - WebSocketTextListener.class.cast(l).onFragment(message,last); - - if(textBuffer == null) { - textBuffer = new StringBuilder(); - } - - textBuffer.append(message); - - if(textBuffer.length() > maxBufferSize) { - Exception e = new Exception("Exceeded Netty Web Socket maximum buffer size of " + getMaxBufferSize()); - l.onError(e); - this.close(); - return; - } - - if(last) { - WebSocketTextListener.class.cast(l).onMessage(textBuffer.toString()); - byteBuffer = null; - textBuffer = null; - } - } catch (Exception ex) { - l.onError(ex); - } - } - } - } - - protected void onError(Throwable t) { - for (WebSocketListener l : listeners) { - try { - l.onError(t); - } catch (Throwable t2) { - logger.error("", t2); - } - - } - } - - protected void onClose() { - onClose(1000, "Normal closure; the connection successfully completed whatever purpose for which it was created."); - } - - protected void onClose(int code, String reason) { - for (WebSocketListener l : listeners) { - try { - if (l instanceof WebSocketCloseCodeReasonListener) { - WebSocketCloseCodeReasonListener.class.cast(l).onClose(this, code, reason); - } - l.onClose(this); - } catch (Throwable t) { - l.onError(t); - } - } - } - - @Override - public String toString() { - return "NettyWebSocket{" + - "channel=" + channel + - '}'; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/Protocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/Protocol.java deleted file mode 100644 index f9d6f39eff..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/Protocol.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty; - -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelStateEvent; -import org.jboss.netty.channel.ExceptionEvent; -import org.jboss.netty.channel.MessageEvent; - -public interface Protocol { - - void handle(ChannelHandlerContext ctx, MessageEvent e) throws Exception; - - void onError(ChannelHandlerContext ctx, ExceptionEvent e); - - void onClose(ChannelHandlerContext ctx, ChannelStateEvent e); -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ResponseBodyPart.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ResponseBodyPart.java deleted file mode 100644 index 7f138bbc36..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ResponseBodyPart.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file to you under the Apache License, version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package org.asynchttpclient.providers.netty; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URI; -import java.nio.ByteBuffer; -import java.util.concurrent.atomic.AtomicReference; - -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.handler.codec.http.HttpChunk; -import org.jboss.netty.handler.codec.http.HttpResponse; - -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.providers.netty.util.ChannelBufferUtil; - -/** - * A callback class used when an HTTP response body is received. - */ -public class ResponseBodyPart extends HttpResponseBodyPart { - - private final HttpChunk chunk; - private final HttpResponse response; - private final AtomicReference bytes = new AtomicReference(null); - private final boolean isLast; - private boolean closeConnection = false; - - /** - * Constructor used for non-chunked GET requests and HEAD requests. - */ - // FIXME Why notify with a null chunk??? - public ResponseBodyPart(URI uri, HttpResponse response, AsyncHttpProvider provider, boolean last) { - this(uri, response, provider, null, last); - } - - public ResponseBodyPart(URI uri, HttpResponse response, AsyncHttpProvider provider, HttpChunk chunk, boolean last) { - super(uri, provider); - this.chunk = chunk; - this.response = response; - isLast = last; - } - - /** - * Return the response body's part bytes received. - * - * @return the response body's part bytes received. - */ - @Override - public byte[] getBodyPartBytes() { - byte[] bp = bytes.get(); - if (bp != null) { - return bp; - } - - byte[] rb = ChannelBufferUtil.channelBuffer2bytes(getChannelBuffer()); - bytes.set(rb); - return rb; - } - - @Override - public InputStream readBodyPartBytes() { - return new ByteArrayInputStream(getBodyPartBytes()); - } - - @Override - public int length() { - ChannelBuffer b = (chunk != null) ? chunk.getContent() : response.getContent(); - return b.readableBytes(); - } - - @Override - public int writeTo(OutputStream outputStream) throws IOException { - ChannelBuffer b = getChannelBuffer(); - int available = b.readableBytes(); - if (available > 0) { - b.getBytes(b.readerIndex(), outputStream, available); - } - return available; - } - - @Override - public ByteBuffer getBodyByteBuffer() { - return ByteBuffer.wrap(getBodyPartBytes()); - } - - public ChannelBuffer getChannelBuffer() { - return chunk != null ? chunk.getContent() : response.getContent(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isLast() { - return isLast; - } - - /** - * {@inheritDoc} - */ - @Override - public void markUnderlyingConnectionAsClosed() { - closeConnection = true; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean closeUnderlyingConnection() { - return closeConnection; - } - - protected HttpChunk chunk() { - return chunk; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ResponseHeaders.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ResponseHeaders.java deleted file mode 100644 index 971eadbf72..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ResponseHeaders.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file to you under the Apache License, version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package org.asynchttpclient.providers.netty; - -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseHeaders; -import org.jboss.netty.handler.codec.http.HttpChunkTrailer; -import org.jboss.netty.handler.codec.http.HttpResponse; - -import java.net.URI; -import java.util.Map; - -/** - * A class that represent the HTTP headers. - */ -public class ResponseHeaders extends HttpResponseHeaders { - - private final HttpChunkTrailer trailingHeaders; - private final HttpResponse response; - private final FluentCaseInsensitiveStringsMap headers; - - public ResponseHeaders(URI uri, HttpResponse response, AsyncHttpProvider provider) { - super(uri, provider, false); - this.trailingHeaders = null; - this.response = response; - headers = computerHeaders(); - } - - public ResponseHeaders(URI uri, HttpResponse response, AsyncHttpProvider provider, HttpChunkTrailer traillingHeaders) { - super(uri, provider, true); - this.trailingHeaders = traillingHeaders; - this.response = response; - headers = computerHeaders(); - } - - private FluentCaseInsensitiveStringsMap computerHeaders() { - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - for (Map.Entry header: response.getHeaders()) { - h.add(header.getKey(), header.getValue()); - } - - if (trailingHeaders != null) { - for (Map.Entry header: trailingHeaders.getHeaders()) { - h.add(header.getKey(), header.getValue()); - } - } - - return h; - } - - /** - * Return the HTTP header - * - * @return an {@link org.asynchttpclient.FluentCaseInsensitiveStringsMap} - */ - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return headers; - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ResponseStatus.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ResponseStatus.java deleted file mode 100644 index 4cec5faf8a..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ResponseStatus.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file to you under the Apache License, version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - */ -package org.asynchttpclient.providers.netty; - -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.HttpResponseStatus; -import org.jboss.netty.handler.codec.http.HttpResponse; - -import java.net.URI; - -/** - * A class that represent the HTTP response' status line (code + text) - */ -public class ResponseStatus extends HttpResponseStatus { - - private final HttpResponse response; - - public ResponseStatus(URI uri, HttpResponse response, AsyncHttpProvider provider) { - super(uri, provider); - this.response = response; - } - - /** - * Return the response status code - * - * @return the response status code - */ - public int getStatusCode() { - return response.getStatus().getCode(); - } - - /** - * Return the response status text - * - * @return the response status text - */ - public String getStatusText() { - return response.getStatus().getReasonPhrase(); - } - - @Override - public String getProtocolName() { - return response.getProtocolVersion().getProtocolName(); - } - - @Override - public int getProtocolMajorVersion() { - return response.getProtocolVersion().getMajorVersion(); - } - - @Override - public int getProtocolMinorVersion() { - return response.getProtocolVersion().getMinorVersion(); - } - - @Override - public String getProtocolText() { - return response.getProtocolVersion().getText(); - } - -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/WebSocketUtil.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/WebSocketUtil.java deleted file mode 100644 index 8aabcc9542..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/WebSocketUtil.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty; - -import org.asynchttpclient.util.Base64; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public final class WebSocketUtil { - public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - - public static String getKey() { - byte[] nonce = createRandomBytes(16); - return base64Encode(nonce); - } - - public static String getAcceptKey(String key) throws UnsupportedEncodingException { - String acceptSeed = key + MAGIC_GUID; - byte[] sha1 = sha1(acceptSeed.getBytes("US-ASCII")); - return base64Encode(sha1); - } - - public static byte[] md5(byte[] bytes) { - try { - MessageDigest md = MessageDigest.getInstance("MD5"); - return md.digest(bytes); - } catch (NoSuchAlgorithmException e) { - throw new InternalError("MD5 not supported on this platform"); - } - } - - public static byte[] sha1(byte[] bytes) { - try { - MessageDigest md = MessageDigest.getInstance("SHA1"); - return md.digest(bytes); - } catch (NoSuchAlgorithmException e) { - throw new InternalError("SHA-1 not supported on this platform"); - } - } - - public static String base64Encode(byte[] bytes) { - return Base64.encode(bytes); - } - - public static byte[] createRandomBytes(int size) { - byte[] bytes = new byte[size]; - - for (int i = 0; i < size; i++) { - bytes[i] = (byte) createRandomNumber(0, 255); - } - - return bytes; - } - - public static int createRandomNumber(int min, int max) { - return (int) (Math.random() * max + min); - } - -} - diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/Channels.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/Channels.java similarity index 84% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/Channels.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/Channels.java index b6ee69b065..84e73efb71 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/Channels.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/Channels.java @@ -1,8 +1,21 @@ -package org.asynchttpclient.providers.netty4; - -import static org.asynchttpclient.providers.netty4.util.HttpUtil.HTTP; -import static org.asynchttpclient.providers.netty4.util.HttpUtil.WEBSOCKET; -import static org.asynchttpclient.providers.netty4.util.HttpUtil.isSecure; +/* + * Copyright 2010-2013 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package org.asynchttpclient.providers.netty.channel; + +import static org.asynchttpclient.providers.netty.util.HttpUtil.*; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; @@ -43,7 +56,12 @@ import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.ConnectionPoolKeyStrategy; import org.asynchttpclient.ConnectionsPool; -import org.asynchttpclient.providers.netty4.util.CleanupChannelGroup; +import org.asynchttpclient.providers.netty.Callback; +import org.asynchttpclient.providers.netty.DiscardEvent; +import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; +import org.asynchttpclient.providers.netty.handler.NettyChannelHandler; +import org.asynchttpclient.providers.netty.util.CleanupChannelGroup; import org.asynchttpclient.util.SslUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -89,8 +107,6 @@ public boolean remove(Object o) { } }; - private NettyChannelHandler httpProcessor; - public Channels(final AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig asyncHttpProviderConfig) { this.config = config; @@ -170,26 +186,28 @@ public Channels(final AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig } } - // FIXME clean up - plainBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectionTimeoutInMs()); - webSocketBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectionTimeoutInMs()); - secureBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectionTimeoutInMs()); - secureWebSocketBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectionTimeoutInMs()); + int timeOut = config.getConnectionTimeoutInMs() > 0 ? config.getConnectionTimeoutInMs() : Integer.MAX_VALUE; + plainBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeOut); + webSocketBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeOut); + secureBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeOut); + secureWebSocketBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeOut); + } - // FIXME What was the meaning of this and what is it still a matter with - // Netty4 - // DefaultChannelFuture.setUseDeadLockChecker(false); + private SSLEngine createSSLEngine() throws IOException, GeneralSecurityException { + SSLEngine sslEngine = config.getSSLEngineFactory().newSSLEngine(); + if (sslEngine == null) { + sslEngine = SslUtils.getSSLEngine(); + } + return sslEngine; } public void configure(final NettyChannelHandler httpProcessor) { - this.httpProcessor = httpProcessor; - - ChannelInitializer httpChannelInitializer = new ChannelInitializer() { + plainBootstrap.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline()// - .addLast(HTTP_HANDLER, newHttpClientCodec()); + .addLast(HTTP_HANDLER, newHttpClientCodec()); if (config.getRequestCompressionLevel() > 0) { pipeline.addLast(DEFLATER_HANDLER, new HttpContentCompressor(config.getRequestCompressionLevel())); @@ -199,75 +217,41 @@ protected void initChannel(Channel ch) throws Exception { pipeline.addLast(INFLATER_HANDLER, new HttpContentDecompressor()); } pipeline.addLast(CHUNKED_WRITER_HANDLER, new ChunkedWriteHandler())// - .addLast(AHC_HANDLER, httpProcessor); + .addLast(AHC_HANDLER, httpProcessor); if (asyncHttpProviderConfig.getHttpAdditionalChannelInitializer() != null) { asyncHttpProviderConfig.getHttpAdditionalChannelInitializer().initChannel(ch); } } - }; + }); - ChannelInitializer webSocketChannelInitializer = new ChannelInitializer() { + webSocketBootstrap.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline()// - .addLast(HTTP_DECODER_HANDLER, new HttpResponseDecoder())// - .addLast(HTTP_ENCODER_HANDLER, new HttpRequestEncoder())// - .addLast(AHC_HANDLER, httpProcessor); + .addLast(HTTP_DECODER_HANDLER, new HttpResponseDecoder())// + .addLast(HTTP_ENCODER_HANDLER, new HttpRequestEncoder())// + .addLast(AHC_HANDLER, httpProcessor); if (asyncHttpProviderConfig.getWsAdditionalChannelInitializer() != null) { asyncHttpProviderConfig.getWsAdditionalChannelInitializer().initChannel(ch); } } - }; - - plainBootstrap.handler(httpChannelInitializer); - webSocketBootstrap.handler(webSocketChannelInitializer); - } - - public Bootstrap getBootstrap(String url, boolean useSSl) { - Bootstrap bootstrap = url.startsWith(WEBSOCKET) ? (useSSl ? secureWebSocketBootstrap : webSocketBootstrap) : (useSSl ? secureBootstrap : plainBootstrap); - - return bootstrap; - } - - public void close() { - connectionsPool.destroy(); - for (Channel channel : openChannels) { - Object attribute = getDefaultAttribute(channel); - if (attribute instanceof NettyResponseFuture) { - NettyResponseFuture future = (NettyResponseFuture) attribute; - future.setReaperFuture(null); - } - } - openChannels.close(); - if (allowReleaseEventLoopGroup) { - eventLoopGroup.shutdownGracefully(); - } - } - - void constructSSLPipeline(final NettyResponseFuture future) { + }); secureBootstrap.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { - ChannelPipeline pipeline = ch.pipeline(); - - try { - pipeline.addLast(SSL_HANDLER, new SslHandler(createSSLEngine())); - } catch (Throwable ex) { - LOGGER.error("Channel {} could not add SslHandler {}", ch, ex); - abort(future, ex); - } - - pipeline.addLast(HTTP_HANDLER, newHttpClientCodec()); + ChannelPipeline pipeline = ch.pipeline()// + .addLast(SSL_HANDLER, new SslHandler(createSSLEngine()))// + .addLast(HTTP_HANDLER, newHttpClientCodec()); if (config.isCompressionEnabled()) { pipeline.addLast(INFLATER_HANDLER, new HttpContentDecompressor()); } pipeline.addLast(CHUNKED_WRITER_HANDLER, new ChunkedWriteHandler())// - .addLast(AHC_HANDLER, httpProcessor); + .addLast(AHC_HANDLER, httpProcessor); if (asyncHttpProviderConfig.getHttpsAdditionalChannelInitializer() != null) { asyncHttpProviderConfig.getHttpsAdditionalChannelInitializer().initChannel(ch); @@ -279,18 +263,11 @@ protected void initChannel(Channel ch) throws Exception { @Override protected void initChannel(Channel ch) throws Exception { - ChannelPipeline pipeline = ch.pipeline(); - - try { - pipeline.addLast(SSL_HANDLER, new SslHandler(createSSLEngine())); - } catch (Throwable ex) { - LOGGER.error("Channel {} could not add SslHandler {}", ch, ex); - abort(future, ex); - } - - pipeline.addLast(HTTP_DECODER_HANDLER, new HttpResponseDecoder())// - .addLast(HTTP_ENCODER_HANDLER, new HttpRequestEncoder())// - .addLast(AHC_HANDLER, httpProcessor); + ch.pipeline()// + .addLast(SSL_HANDLER, new SslHandler(createSSLEngine()))// + .addLast(HTTP_DECODER_HANDLER, new HttpResponseDecoder())// + .addLast(HTTP_ENCODER_HANDLER, new HttpRequestEncoder())// + .addLast(AHC_HANDLER, httpProcessor); if (asyncHttpProviderConfig.getWssAdditionalChannelInitializer() != null) { asyncHttpProviderConfig.getWssAdditionalChannelInitializer().initChannel(ch); @@ -299,15 +276,26 @@ protected void initChannel(Channel ch) throws Exception { }); } - private SSLEngine createSSLEngine() throws IOException, GeneralSecurityException { - SSLEngine sslEngine = config.getSSLEngineFactory().newSSLEngine(); - if (sslEngine == null) { - sslEngine = SslUtils.getSSLEngine(); + public Bootstrap getBootstrap(String url, boolean useSSl) { + return url.startsWith(WEBSOCKET) ? (useSSl ? secureWebSocketBootstrap : webSocketBootstrap) : (useSSl ? secureBootstrap : plainBootstrap); + } + + public void close() { + connectionsPool.destroy(); + for (Channel channel : openChannels) { + Object attribute = getDefaultAttribute(channel); + if (attribute instanceof NettyResponseFuture) { + NettyResponseFuture future = (NettyResponseFuture) attribute; + future.setReaperFuture(null); + } + } + openChannels.close(); + if (allowReleaseEventLoopGroup) { + eventLoopGroup.shutdownGracefully(); } - return sslEngine; } - // FIXME what for? + // some servers can use the same port for HTTP and HTTPS public Channel verifyChannelPipeline(Channel channel, String scheme) throws IOException, GeneralSecurityException { if (channel.pipeline().get(SSL_HANDLER) != null && HTTP.equalsIgnoreCase(scheme)) { @@ -507,26 +495,4 @@ public static void setDefaultAttribute(Channel channel, Object o) { public static void setDefaultAttribute(ChannelHandlerContext ctx, Object o) { ctx.attr(DEFAULT_ATTRIBUTE).set(o); } - - private static class NonConnectionsPool implements ConnectionsPool { - - public boolean offer(String uri, Channel connection) { - return false; - } - - public Channel poll(String uri) { - return null; - } - - public boolean removeAll(Channel connection) { - return false; - } - - public boolean canCacheConnection() { - return true; - } - - public void destroy() { - } - } } diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyConnectionsPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/NettyConnectionsPool.java similarity index 98% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyConnectionsPool.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/NettyConnectionsPool.java index 0da9e4a0cd..b9abe8c925 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyConnectionsPool.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/NettyConnectionsPool.java @@ -10,7 +10,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. */ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty.channel; import static org.asynchttpclient.util.DateUtil.millisTime; import io.netty.channel.Channel; @@ -26,6 +26,8 @@ import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.ConnectionsPool; +import org.asynchttpclient.providers.netty.DiscardEvent; +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyProviderUtil.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/NonConnectionsPool.java similarity index 50% rename from providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyProviderUtil.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/NonConnectionsPool.java index d68cdb947d..c4a7961c76 100644 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyProviderUtil.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/NonConnectionsPool.java @@ -1,5 +1,5 @@ /* - * Copyright 2010 Ning, Inc. + * Copyright 2010-2013 Ning, Inc. * * Ning licenses this file to you under the Apache License, version 2.0 * (the "License"); you may not use this file except in compliance with the @@ -13,17 +13,30 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty.channel; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; +import io.netty.channel.Channel; -public class NettyProviderUtil { +import org.asynchttpclient.ConnectionsPool; - public static AsyncHttpClient nettyProvider(AsyncHttpClientConfig config) { - if (config == null) { - config = new AsyncHttpClientConfig.Builder().build(); - } - return new AsyncHttpClient(new NettyAsyncHttpProvider(config), config); +public class NonConnectionsPool implements ConnectionsPool { + + public boolean offer(String uri, Channel connection) { + return false; + } + + public Channel poll(String uri) { + return null; + } + + public boolean removeAll(Channel connection) { + return false; + } + + public boolean canCacheConnection() { + return true; + } + + public void destroy() { } -} +} \ No newline at end of file diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/FutureReaper.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/FutureReaper.java similarity index 91% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/FutureReaper.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/FutureReaper.java index 974e2bc618..550b9d135c 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/FutureReaper.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/FutureReaper.java @@ -1,4 +1,4 @@ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty.future; import static org.asynchttpclient.util.DateUtil.millisTime; @@ -9,12 +9,17 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.providers.netty.channel.Channels; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Because some implementation of the ThreadSchedulingService do not clean up cancel task until they try to run them, we wrap the task with the future so the when the NettyResponseFuture cancel the reaper future this wrapper will release the references to the channel and the * nettyResponseFuture immediately. Otherwise, the memory referenced this way will only be released after the request timeout period which can be arbitrary long. */ public final class FutureReaper implements Runnable { + + private static final Logger LOGGER = LoggerFactory.getLogger(FutureReaper.class); private final AtomicBoolean closed; private final Channels channels; @@ -70,7 +75,7 @@ public boolean isDone() { } private void expire(String message) { - NettyAsyncHttpProvider.LOGGER.debug("{} for {}", message, nettyResponseFuture); + LOGGER.debug("{} for {}", message, nettyResponseFuture); channels.abort(nettyResponseFuture, new TimeoutException(message)); nettyResponseFuture = null; } diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyResponseFuture.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java similarity index 98% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyResponseFuture.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java index 65fa6c714d..ff5790ca3a 100755 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyResponseFuture.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty.future; import static org.asynchttpclient.util.DateUtil.millisTime; import io.netty.channel.Channel; @@ -38,6 +38,8 @@ import org.asynchttpclient.ProxyServer; import org.asynchttpclient.Request; import org.asynchttpclient.listenable.AbstractListenableFuture; +import org.asynchttpclient.providers.netty.DiscardEvent; +import org.asynchttpclient.providers.netty.channel.Channels; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -256,7 +258,7 @@ public V getContent() throws ExecutionException { currentRetry.set(maxRetry); if (exEx.get() == null && !contentProcessed.getAndSet(true)) { try { - update = (V) asyncHandler.onCompleted(); + update = asyncHandler.onCompleted(); } catch (Throwable ex) { if (!throwableCalled.getAndSet(true)) { try { diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyResponseFutures.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFutures.java similarity index 98% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyResponseFutures.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFutures.java index 2d3d34f74b..3b87f48127 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyResponseFutures.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFutures.java @@ -1,4 +1,4 @@ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty.future; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpRequest; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java new file mode 100644 index 0000000000..a5d689ac91 --- /dev/null +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java @@ -0,0 +1,450 @@ +/* + * Copyright 2010-2013 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package org.asynchttpclient.providers.netty.handler; + +import static io.netty.handler.codec.http.HttpResponseStatus.*; +import static org.asynchttpclient.providers.netty.util.HttpUtil.isNTLM; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.LastHttpContent; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicReference; + +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.FluentCaseInsensitiveStringsMap; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.HttpResponseHeaders; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.ProxyServer; +import org.asynchttpclient.Realm; +import org.asynchttpclient.Request; +import org.asynchttpclient.RequestBuilder; +import org.asynchttpclient.AsyncHandler.STATE; +import org.asynchttpclient.filter.FilterContext; +import org.asynchttpclient.filter.FilterException; +import org.asynchttpclient.filter.ResponseFilter; +import org.asynchttpclient.ntlm.NTLMEngine; +import org.asynchttpclient.ntlm.NTLMEngineException; +import org.asynchttpclient.spnego.SpnegoEngine; +import org.asynchttpclient.providers.netty.Callback; +import org.asynchttpclient.providers.netty.channel.Channels; +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; +import org.asynchttpclient.providers.netty.request.NettyRequestSender; +import org.asynchttpclient.providers.netty.response.ResponseBodyPart; +import org.asynchttpclient.providers.netty.response.ResponseHeaders; +import org.asynchttpclient.providers.netty.response.ResponseStatus; +import org.asynchttpclient.util.AsyncHttpProviderUtils; + +final class HttpProtocol extends Protocol { + + public HttpProtocol(Channels channels, AsyncHttpClientConfig config, NettyRequestSender requestSender) { + super(channels, config, requestSender); + } + + private Realm kerberosChallenge(List proxyAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, + NettyResponseFuture future) throws NTLMEngineException { + + URI uri = request.getURI(); + String host = request.getVirtualHost() == null ? AsyncHttpProviderUtils.getHost(uri) : request.getVirtualHost(); + String server = proxyServer == null ? host : proxyServer.getHost(); + try { + String challengeHeader = SpnegoEngine.instance().generateToken(server); + headers.remove(HttpHeaders.Names.AUTHORIZATION); + headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader); + + Realm.RealmBuilder realmBuilder; + if (realm != null) { + realmBuilder = new Realm.RealmBuilder().clone(realm); + } else { + realmBuilder = new Realm.RealmBuilder(); + } + return realmBuilder.setUri(uri.getRawPath()).setMethodName(request.getMethod()).setScheme(Realm.AuthScheme.KERBEROS).build(); + } catch (Throwable throwable) { + if (isNTLM(proxyAuth)) { + return ntlmChallenge(proxyAuth, request, proxyServer, headers, realm, future); + } + channels.abort(future, throwable); + return null; + } + } + + private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, + NettyResponseFuture future) throws NTLMEngineException { + + boolean useRealm = (proxyServer == null && realm != null); + + String ntlmDomain = useRealm ? realm.getNtlmDomain() : proxyServer.getNtlmDomain(); + String ntlmHost = useRealm ? realm.getNtlmHost() : proxyServer.getHost(); + String principal = useRealm ? realm.getPrincipal() : proxyServer.getPrincipal(); + String password = useRealm ? realm.getPassword() : proxyServer.getPassword(); + + Realm newRealm; + if (realm != null && !realm.isNtlmMessageType2Received()) { + String challengeHeader = NTLMEngine.INSTANCE.generateType1Msg(ntlmDomain, ntlmHost); + + URI uri = request.getURI(); + headers.add(HttpHeaders.Names.AUTHORIZATION, "NTLM " + challengeHeader); + newRealm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(uri.getRawPath()).setMethodName(request.getMethod()) + .setNtlmMessageType2Received(true).build(); + future.getAndSetAuth(false); + } else { + addType3NTLMAuthorizationHeader(wwwAuth, headers, principal, password, ntlmDomain, ntlmHost); + + Realm.RealmBuilder realmBuilder; + Realm.AuthScheme authScheme; + if (realm != null) { + realmBuilder = new Realm.RealmBuilder().clone(realm); + authScheme = realm.getAuthScheme(); + } else { + realmBuilder = new Realm.RealmBuilder(); + authScheme = Realm.AuthScheme.NTLM; + } + newRealm = realmBuilder.setScheme(authScheme).setUri(request.getURI().getPath()).setMethodName(request.getMethod()).build(); + } + + return newRealm; + } + + private Realm ntlmProxyChallenge(List wwwAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, + NettyResponseFuture future) throws NTLMEngineException { + future.getAndSetAuth(false); + headers.remove(HttpHeaders.Names.PROXY_AUTHORIZATION); + + addType3NTLMAuthorizationHeader(wwwAuth, headers, proxyServer.getPrincipal(), proxyServer.getPassword(), proxyServer.getNtlmDomain(), proxyServer.getHost()); + + Realm newRealm; + Realm.RealmBuilder realmBuilder; + if (realm != null) { + realmBuilder = new Realm.RealmBuilder().clone(realm); + } else { + realmBuilder = new Realm.RealmBuilder(); + } + newRealm = realmBuilder// .setScheme(realm.getAuthScheme()) + .setUri(request.getURI().getPath()).setMethodName(request.getMethod()).build(); + + return newRealm; + } + + private void addType3NTLMAuthorizationHeader(List auth, FluentCaseInsensitiveStringsMap headers, String username, String password, String domain, String workstation) + throws NTLMEngineException { + headers.remove(HttpHeaders.Names.AUTHORIZATION); + + if (isNTLM(auth)) { + String serverChallenge = auth.get(0).trim().substring("NTLM ".length()); + String challengeHeader = NTLMEngine.INSTANCE.generateType3Msg(username, password, domain, workstation, serverChallenge); + + headers.add(HttpHeaders.Names.AUTHORIZATION, "NTLM " + challengeHeader); + } + } + + private List getAuthorizationToken(Iterable> list, String headerAuth) { + ArrayList l = new ArrayList(); + for (Entry e : list) { + if (e.getKey().equalsIgnoreCase(headerAuth)) { + l.add(e.getValue().trim()); + } + } + return l; + } + + private void finishUpdate(final NettyResponseFuture future, final ChannelHandlerContext ctx, boolean lastValidChunk) throws IOException { + if (lastValidChunk && future.isKeepAlive()) { + channels.drainChannel(ctx, future); + } else { + if (future.isKeepAlive() && ctx.channel().isActive() && channels.offerToPool(channels.getPoolKey(future), ctx.channel())) { + markAsDone(future, ctx); + return; + } + channels.finishChannel(ctx); + } + markAsDone(future, ctx); + } + + private final boolean updateBodyAndInterrupt(final NettyResponseFuture future, AsyncHandler handler, HttpResponseBodyPart c) throws Exception { + boolean state = handler.onBodyPartReceived(c) != STATE.CONTINUE; + if (c.closeUnderlyingConnection()) { + future.setKeepAlive(false); + } + return state; + } + + private void markAsDone(final NettyResponseFuture future, final ChannelHandlerContext ctx) throws MalformedURLException { + // We need to make sure everything is OK before adding the + // connection back to the pool. + try { + future.done(); + } catch (Throwable t) { + // Never propagate exception once we know we are done. + NettyChannelHandler.LOGGER.debug(t.getMessage(), t); + } + + if (!future.isKeepAlive() || !ctx.channel().isActive()) { + channels.closeChannel(ctx); + } + } + + private boolean applyResponseFiltersAndReplayRequest(ChannelHandlerContext ctx, NettyResponseFuture future, HttpResponseStatus status, + HttpResponseHeaders responseHeaders) throws IOException { + + AsyncHandler handler = future.getAsyncHandler(); + FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(handler).request(future.getRequest()).responseStatus(status).responseHeaders(responseHeaders) + .build(); + + for (ResponseFilter asyncFilter : config.getResponseFilters()) { + try { + fc = asyncFilter.filter(fc); + // FIXME Is it worth protecting against this? + if (fc == null) { + throw new NullPointerException("FilterContext is null"); + } + } catch (FilterException efe) { + channels.abort(future, efe); + } + } + + // The handler may have been wrapped. + handler = fc.getAsyncHandler(); + future.setAsyncHandler(handler); + + // The request has changed + if (fc.replayRequest()) { + requestSender.replayRequest(future, fc, ctx); + return true; + } + return false; + } + + private boolean handleResponseAndExit(final ChannelHandlerContext ctx, final NettyResponseFuture future, AsyncHandler handler, HttpRequest nettyRequest, + ProxyServer proxyServer, HttpResponse response) throws Exception { + Request request = future.getRequest(); + int statusCode = response.getStatus().code(); + HttpResponseStatus status = new ResponseStatus(future.getURI(), response); + HttpResponseHeaders responseHeaders = new ResponseHeaders(future.getURI(), response.headers()); + final FluentCaseInsensitiveStringsMap headers = request.getHeaders(); + final RequestBuilder builder = new RequestBuilder(future.getRequest()); + Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); + + // store the original headers so we can re-send all them to + // the handler in case of trailing headers + future.setHttpResponse(response); + + future.setKeepAlive(!HttpHeaders.Values.CLOSE.equalsIgnoreCase(response.headers().get(HttpHeaders.Names.CONNECTION))); + + if (!config.getResponseFilters().isEmpty() && applyResponseFiltersAndReplayRequest(ctx, future, status, responseHeaders)) { + return true; + } + + // FIXME handle without returns + if (statusCode == UNAUTHORIZED.code() && realm != null) { + List wwwAuth = getAuthorizationToken(response.headers(), HttpHeaders.Names.WWW_AUTHENTICATE); + if (!wwwAuth.isEmpty() && !future.getAndSetAuth(true)) { + future.setState(NettyResponseFuture.STATE.NEW); + Realm newRealm = null; + // NTLM + boolean negociate = wwwAuth.contains("Negotiate"); + if (!wwwAuth.contains("Kerberos") && (isNTLM(wwwAuth) || negociate)) { + newRealm = ntlmChallenge(wwwAuth, request, proxyServer, headers, realm, future); + // SPNEGO KERBEROS + } else if (negociate) { + newRealm = kerberosChallenge(wwwAuth, request, proxyServer, headers, realm, future); + if (newRealm == null) { + return true; + } + } else { + newRealm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(request.getURI().getPath()).setMethodName(request.getMethod()) + .setUsePreemptiveAuth(true).parseWWWAuthenticateHeader(wwwAuth.get(0)).build(); + } + + final Realm nr = new Realm.RealmBuilder().clone(newRealm).setUri(URI.create(request.getUrl()).getPath()).build(); + + NettyChannelHandler.LOGGER.debug("Sending authentication to {}", request.getUrl()); + Callback callback = new Callback(future) { + public void call() throws Exception { + channels.drainChannel(ctx, future); + requestSender.sendNextRequest(builder.setHeaders(headers).setRealm(nr).build(), future); + } + }; + + if (future.isKeepAlive() && HttpHeaders.isTransferEncodingChunked(response)) { + // We must make sure there is no bytes left + // before executing the next request. + Channels.setDefaultAttribute(ctx, callback); + } else { + callback.call(); + } + + return true; + } + + } else if (statusCode == CONTINUE.code()) { + future.getAndSetWriteHeaders(false); + future.getAndSetWriteBody(true); + // FIXME why not reuse the channel? + requestSender.writeRequest(ctx.channel(), config, future); + return true; + + } else if (statusCode == PROXY_AUTHENTICATION_REQUIRED.code()) { + List proxyAuth = getAuthorizationToken(response.headers(), HttpHeaders.Names.PROXY_AUTHENTICATE); + if (realm != null && !proxyAuth.isEmpty() && !future.getAndSetAuth(true)) { + NettyChannelHandler.LOGGER.debug("Sending proxy authentication to {}", request.getUrl()); + + future.setState(NettyResponseFuture.STATE.NEW); + Realm newRealm = null; + + boolean negociate = proxyAuth.contains("Negotiate"); + if (!proxyAuth.contains("Kerberos") && (isNTLM(proxyAuth) || negociate)) { + newRealm = ntlmProxyChallenge(proxyAuth, request, proxyServer, headers, realm, future); + // SPNEGO KERBEROS + } else if (negociate) { + newRealm = kerberosChallenge(proxyAuth, request, proxyServer, headers, realm, future); + if (newRealm == null) { + return true; + } + } else { + newRealm = future.getRequest().getRealm(); + } + + future.setReuseChannel(true); + future.setConnectAllowed(true); + requestSender.sendNextRequest(builder.setHeaders(headers).setRealm(newRealm).build(), future); + return true; + } + + } else if (statusCode == OK.code() && nettyRequest.getMethod() == HttpMethod.CONNECT) { + + NettyChannelHandler.LOGGER.debug("Connected to {}:{}", proxyServer.getHost(), proxyServer.getPort()); + + if (future.isKeepAlive()) { + future.attachChannel(ctx.channel(), true); + } + + try { + NettyChannelHandler.LOGGER.debug("Connecting to proxy {} for scheme {}", proxyServer, request.getUrl()); + channels.upgradeProtocol(ctx.channel().pipeline(), request.getURI().getScheme()); + } catch (Throwable ex) { + channels.abort(future, ex); + } + future.setReuseChannel(true); + future.setConnectAllowed(false); + requestSender.sendNextRequest(builder.build(), future); + return true; + + } + + if (redirect(request, future, response, ctx)) { + return true; + } + + if (!future.getAndSetStatusReceived(true) && (handler.onStatusReceived(status) != STATE.CONTINUE || handler.onHeadersReceived(responseHeaders) != STATE.CONTINUE)) { + finishUpdate(future, ctx, HttpHeaders.isTransferEncodingChunked(response)); + return true; + } + + return false; + } + + @Override + public void handle(final ChannelHandlerContext ctx, final NettyResponseFuture future, final Object e) throws Exception { + future.touch(); + + // The connect timeout occurred. + if (future.isCancelled() || future.isDone()) { + channels.finishChannel(ctx); + return; + } + + HttpRequest nettyRequest = future.getNettyRequest(); + AsyncHandler handler = future.getAsyncHandler(); + Request request = future.getRequest(); + ProxyServer proxyServer = future.getProxyServer(); + try { + if (e instanceof HttpResponse) { + HttpResponse response = (HttpResponse) e; + NettyChannelHandler.LOGGER.debug("\n\nRequest {}\n\nResponse {}\n", nettyRequest, response); + future.getPendingResponse().set(response); + return; + } + + if (e instanceof HttpContent) { + + AtomicReference responseRef = future.getPendingResponse(); + HttpResponse response = responseRef.getAndSet(null); + if (handler != null) { + if (response != null && handleResponseAndExit(ctx, future, handler, nettyRequest, proxyServer, response)) { + return; + } + + HttpContent chunk = (HttpContent) e; + + boolean interrupt = false; + boolean last = chunk instanceof LastHttpContent; + + // FIXME + // Netty 3 provider is broken: in case of trailing headers, + // onHeadersReceived should be called before + // updateBodyAndInterrupt + if (last) { + LastHttpContent lastChunk = (LastHttpContent) chunk; + HttpHeaders trailingHeaders = lastChunk.trailingHeaders(); + if (!trailingHeaders.isEmpty()) { + interrupt = handler.onHeadersReceived(new ResponseHeaders(future.getURI(), future.getHttpResponse().headers(), trailingHeaders)) != STATE.CONTINUE; + } + } + + if (!interrupt && chunk.content().readableBytes() > 0) { + // FIXME why + interrupt = updateBodyAndInterrupt(future, handler, new ResponseBodyPart(future.getURI(), chunk.content(), last)); + } + + if (interrupt || last) { + finishUpdate(future, ctx, !last); + } + } + } + } catch (Exception t) { + if (t instanceof IOException && !config.getIOExceptionFilters().isEmpty() + && requestSender.applyIoExceptionFiltersAndReplayRequest(ctx, future, IOException.class.cast(t))) { + return; + } + + try { + channels.abort(future, t); + } finally { + finishUpdate(future, ctx, false); + throw t; + } + } + } + + @Override + public void onError(ChannelHandlerContext ctx, Throwable error) { + } + + @Override + public void onClose(ChannelHandlerContext ctx) { + } +} \ No newline at end of file diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/NettyChannelHandler.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/NettyChannelHandler.java new file mode 100644 index 0000000000..fb47346455 --- /dev/null +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/NettyChannelHandler.java @@ -0,0 +1,199 @@ +/* + * Copyright 2010-2013 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package org.asynchttpclient.providers.netty.handler; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.PrematureChannelClosureException; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.LastHttpContent; + +import java.io.IOException; +import java.nio.channels.ClosedChannelException; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.providers.netty.Callback; +import org.asynchttpclient.providers.netty.DiscardEvent; +import org.asynchttpclient.providers.netty.channel.Channels; +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; +import org.asynchttpclient.providers.netty.future.NettyResponseFutures; +import org.asynchttpclient.providers.netty.request.NettyRequestSender; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Sharable +public class NettyChannelHandler extends ChannelInboundHandlerAdapter { + + static final Logger LOGGER = LoggerFactory.getLogger(NettyChannelHandler.class); + + private final AsyncHttpClientConfig config; + private final NettyRequestSender requestSender; + private final Channels channels; + private final AtomicBoolean closed; + private final Protocol httpProtocol; + private final Protocol webSocketProtocol; + + public NettyChannelHandler(AsyncHttpClientConfig config, NettyRequestSender requestSender, Channels channels, AtomicBoolean isClose) { + this.config = config; + this.requestSender = requestSender; + this.channels = channels; + this.closed = isClose; + httpProtocol = new HttpProtocol(channels, config, requestSender); + webSocketProtocol = new WebSocketProtocol(channels, config, requestSender); + } + + @Override + public void channelRead(final ChannelHandlerContext ctx, Object e) throws Exception { + + Object attribute = Channels.getDefaultAttribute(ctx); + + // FIXME is || !(e instanceof HttpContent) necessary? + if (attribute instanceof Callback && (e instanceof LastHttpContent /* || !(e instanceof HttpContent) */)) { + Callback ac = (Callback) attribute; + ac.call(); + Channels.setDefaultAttribute(ctx, DiscardEvent.INSTANCE); + + } else if (attribute instanceof NettyResponseFuture) { + Protocol p = (ctx.pipeline().get(HttpClientCodec.class) != null ? httpProtocol : webSocketProtocol); + NettyResponseFuture future = (NettyResponseFuture) attribute; + + p.handle(ctx, future, e); + + } else if (attribute != DiscardEvent.INSTANCE) { + try { + LOGGER.trace("Closing an orphan channel {}", ctx.channel()); + ctx.channel().close(); + } catch (Throwable t) { + } + } + } + + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + + if (closed.get()) { + return; + } + + try { + super.channelInactive(ctx); + } catch (Exception ex) { + LOGGER.trace("super.channelClosed", ex); + } + + channels.removeFromPool(ctx); + Object attachment = Channels.getDefaultAttribute(ctx); + LOGGER.debug("Channel Closed: {} with attachment {}", ctx.channel(), attachment); + + if (attachment instanceof Callback) { + Callback callback = (Callback) attachment; + Channels.setDefaultAttribute(ctx, callback.future()); + callback.call(); + + } else if (attachment instanceof NettyResponseFuture) { + NettyResponseFuture future = NettyResponseFuture.class.cast(attachment); + future.touch(); + + if (!config.getIOExceptionFilters().isEmpty() && requestSender.applyIoExceptionFiltersAndReplayRequest(ctx, future, new IOException("Channel Closed"))) { + return; + } + + Protocol p = (ctx.pipeline().get(HttpClientCodec.class) != null ? httpProtocol : webSocketProtocol); + p.onClose(ctx); + + if (future != null && !future.isDone() && !future.isCancelled()) { + if (!requestSender.retry(ctx.channel(), future)) { + channels.abort(future, new IOException("Remotely Closed")); + } + } else { + channels.closeChannel(ctx); + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception { + Channel channel = ctx.channel(); + Throwable cause = e.getCause() != null ? e.getCause() : e; + NettyResponseFuture future = null; + + if (cause instanceof PrematureChannelClosureException) { + return; + } + + LOGGER.debug("Unexpected I/O exception on channel {}", channel, cause); + + try { + if (cause instanceof ClosedChannelException) { + return; + } + + Object attribute = Channels.getDefaultAttribute(ctx); + if (attribute instanceof NettyResponseFuture) { + future = (NettyResponseFuture) attribute; + future.attachChannel(null, false); + future.touch(); + + if (cause instanceof IOException) { + + // FIXME why drop the original exception and create a new + // one? + if (!config.getIOExceptionFilters().isEmpty()) { + if (requestSender.applyIoExceptionFiltersAndReplayRequest(ctx, future, new IOException("Channel Closed"))) { + return; + } + } else { + // Close the channel so the recovering can occurs. + try { + ctx.channel().close(); + } catch (Throwable t) { + // Swallow. + } + return; + } + } + + if (NettyResponseFutures.abortOnReadCloseException(cause) || NettyResponseFutures.abortOnWriteCloseException(cause)) { + LOGGER.debug("Trying to recover from dead Channel: {}", channel); + return; + } + } else if (attribute instanceof Callback) { + future = Callback.class.cast(attribute).future(); + } + } catch (Throwable t) { + cause = t; + } + + if (future != null) { + try { + LOGGER.debug("Was unable to recover Future: {}", future); + channels.abort(future, cause); + } catch (Throwable t) { + LOGGER.error(t.getMessage(), t); + } + } + + Protocol protocol = ctx.pipeline().get(HttpClientCodec.class) != null ? httpProtocol : webSocketProtocol; + protocol.onError(ctx, e); + + channels.closeChannel(ctx); + // FIXME not really sure + // ctx.fireChannelRead(e); + ctx.close(); + } +} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Protocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Protocol.java new file mode 100644 index 0000000000..dcd456f41d --- /dev/null +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Protocol.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package org.asynchttpclient.providers.netty.handler; + +import static io.netty.handler.codec.http.HttpResponseStatus.*; +import static org.asynchttpclient.providers.netty.util.HttpUtil.*; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpResponse; + +import java.net.URI; + +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.Cookie; +import org.asynchttpclient.MaxRedirectException; +import org.asynchttpclient.Request; +import org.asynchttpclient.RequestBuilder; +import org.asynchttpclient.org.jboss.netty.handler.codec.http.CookieDecoder; +import org.asynchttpclient.providers.netty.Callback; +import org.asynchttpclient.providers.netty.channel.Channels; +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; +import org.asynchttpclient.providers.netty.request.NettyRequestSender; +import org.asynchttpclient.util.AsyncHttpProviderUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class Protocol { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + protected final Channels channels; + protected final AsyncHttpClientConfig config; + protected final NettyRequestSender requestSender; + + public Protocol(Channels channels, AsyncHttpClientConfig config, NettyRequestSender requestSender) { + this.channels = channels; + this.config = config; + this.requestSender = requestSender; + } + + public abstract void handle(ChannelHandlerContext ctx, NettyResponseFuture future, Object message) throws Exception; + + public abstract void onError(ChannelHandlerContext ctx, Throwable error); + + public abstract void onClose(ChannelHandlerContext ctx); + + protected boolean redirect(Request request, NettyResponseFuture future, HttpResponse response, final ChannelHandlerContext ctx) throws Exception { + + io.netty.handler.codec.http.HttpResponseStatus status = response.getStatus(); + boolean redirectEnabled = request.isRedirectOverrideSet() ? request.isRedirectEnabled() : config.isRedirectEnabled(); + boolean isRedirectStatus = status.equals(MOVED_PERMANENTLY) || status.equals(FOUND) || status.equals(SEE_OTHER) || status.equals(TEMPORARY_REDIRECT); + if (redirectEnabled && isRedirectStatus) { + + if (future.incrementAndGetCurrentRedirectCount() < config.getMaxRedirects()) { + // We must allow 401 handling again. + future.getAndSetAuth(false); + + String location = response.headers().get(HttpHeaders.Names.LOCATION); + URI uri = AsyncHttpProviderUtils.getRedirectUri(future.getURI(), location); + + if (!uri.toString().equals(future.getURI().toString())) { + final RequestBuilder nBuilder = new RequestBuilder(future.getRequest()); + if (config.isRemoveQueryParamOnRedirect()) { + nBuilder.setQueryParameters(null); + } + + // FIXME why not do that for 301 and 307 too? + if ((status.equals(FOUND) || status.equals(SEE_OTHER)) && !(status.equals(FOUND) && config.isStrict302Handling())) { + nBuilder.setMethod(HttpMethod.GET.name()); + } + + // in case of a redirect from HTTP to HTTPS, future attributes might change + final boolean initialConnectionKeepAlive = future.isKeepAlive(); + final String initialPoolKey = channels.getPoolKey(future); + + future.setURI(uri); + String newUrl = uri.toString(); + if (request.getUrl().startsWith(WEBSOCKET)) { + newUrl = newUrl.replace(HTTP, WEBSOCKET); + } + + logger.debug("Redirecting to {}", newUrl); + + for (String cookieStr : future.getHttpResponse().headers().getAll(HttpHeaders.Names.SET_COOKIE)) { + for (Cookie c : CookieDecoder.decode(cookieStr)) { + nBuilder.addOrReplaceCookie(c); + } + } + + for (String cookieStr : future.getHttpResponse().headers().getAll(HttpHeaders.Names.SET_COOKIE2)) { + for (Cookie c : CookieDecoder.decode(cookieStr)) { + nBuilder.addOrReplaceCookie(c); + } + } + + Callback callback = new Callback(future) { + public void call() throws Exception { + if (!(initialConnectionKeepAlive && ctx.channel().isActive() && channels.offerToPool(initialPoolKey, ctx.channel()))) { + channels.finishChannel(ctx); + } + } + }; + + if (HttpHeaders.isTransferEncodingChunked(response)) { + // We must make sure there is no bytes left before + // executing the next request. + // FIXME investigate this + Channels.setDefaultAttribute(ctx, callback); + } else { + // FIXME don't understand: this offers the connection to the pool, or even closes it, while the request has not been sent, right? + callback.call(); + } + + Request target = nBuilder.setUrl(newUrl).build(); + future.setRequest(target); + // FIXME why not reuse the channel is same host? + requestSender.sendNextRequest(target, future); + return true; + } + } else { + throw new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects()); + } + } + return false; + } +} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java new file mode 100644 index 0000000000..eee8b91515 --- /dev/null +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java @@ -0,0 +1,221 @@ +/* + * Copyright 2010-2013 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package org.asynchttpclient.providers.netty.handler; + +import static io.netty.handler.codec.http.HttpResponseStatus.SWITCHING_PROTOCOLS; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.LastHttpContent; +import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; +import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; + +import java.io.IOException; + +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.HttpResponseHeaders; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.Request; +import org.asynchttpclient.AsyncHandler.STATE; +import org.asynchttpclient.filter.FilterContext; +import org.asynchttpclient.filter.FilterException; +import org.asynchttpclient.filter.ResponseFilter; +import org.asynchttpclient.providers.netty.Constants; +import org.asynchttpclient.providers.netty.DiscardEvent; +import org.asynchttpclient.providers.netty.channel.Channels; +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; +import org.asynchttpclient.providers.netty.request.NettyRequestSender; +import org.asynchttpclient.providers.netty.response.ResponseBodyPart; +import org.asynchttpclient.providers.netty.response.ResponseHeaders; +import org.asynchttpclient.providers.netty.response.ResponseStatus; +import org.asynchttpclient.providers.netty.ws.NettyWebSocket; +import org.asynchttpclient.providers.netty.ws.WebSocketUtil; +import org.asynchttpclient.websocket.WebSocketUpgradeHandler; + +final class WebSocketProtocol extends Protocol { + + private static final byte OPCODE_TEXT = 0x1; + private static final byte OPCODE_BINARY = 0x2; + private static final byte OPCODE_UNKNOWN = -1; + protected byte pendingOpcode = OPCODE_UNKNOWN; + + public WebSocketProtocol(Channels channels, AsyncHttpClientConfig config, NettyRequestSender requestSender) { + super(channels, config, requestSender); + } + + // We don't need to synchronize as replacing the "ws-decoder" will + // process using the same thread. + private void invokeOnSucces(ChannelHandlerContext ctx, WebSocketUpgradeHandler h) { + if (!h.touchSuccess()) { + try { + h.onSuccess(new NettyWebSocket(ctx.channel())); + } catch (Exception ex) { + NettyChannelHandler.LOGGER.warn("onSuccess unexpected exception", ex); + } + } + } + + @Override + public void handle(ChannelHandlerContext ctx, NettyResponseFuture future, Object e) throws Exception { + WebSocketUpgradeHandler h = WebSocketUpgradeHandler.class.cast(future.getAsyncHandler()); + Request request = future.getRequest(); + + if (e instanceof HttpResponse) { + HttpResponse response = (HttpResponse) e; + + HttpResponseStatus s = new ResponseStatus(future.getURI(), response); + HttpResponseHeaders responseHeaders = new ResponseHeaders(future.getURI(), response.headers()); + + // FIXME there's a method for that IIRC + FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(h).request(request).responseStatus(s).responseHeaders(responseHeaders).build(); + for (ResponseFilter asyncFilter : config.getResponseFilters()) { + try { + fc = asyncFilter.filter(fc); + if (fc == null) { + throw new NullPointerException("FilterContext is null"); + } + } catch (FilterException efe) { + channels.abort(future, efe); + } + } + + // The handler may have been wrapped. + future.setAsyncHandler(fc.getAsyncHandler()); + + // The request has changed + if (fc.replayRequest()) { + requestSender.replayRequest(future, fc, ctx); + return; + } + + future.setHttpResponse(response); + if (redirect(request, future, response, ctx)) + return; + + boolean validStatus = response.getStatus().equals(SWITCHING_PROTOCOLS); + boolean validUpgrade = response.headers().get(HttpHeaders.Names.UPGRADE) != null; + String c = response.headers().get(HttpHeaders.Names.CONNECTION); + if (c == null) { + c = response.headers().get(HttpHeaders.Names.CONNECTION.toLowerCase()); + } + + boolean validConnection = c == null ? false : c.equalsIgnoreCase(HttpHeaders.Values.UPGRADE); + + s = new ResponseStatus(future.getURI(), response); + final boolean statusReceived = h.onStatusReceived(s) == STATE.UPGRADE; + + final boolean headerOK = h.onHeadersReceived(responseHeaders) == STATE.CONTINUE; + if (!headerOK || !validStatus || !validUpgrade || !validConnection || !statusReceived) { + channels.abort(future, new IOException("Invalid handshake response")); + return; + } + + String accept = response.headers().get(HttpHeaders.Names.SEC_WEBSOCKET_ACCEPT); + String key = WebSocketUtil.getAcceptKey(future.getNettyRequest().headers().get(HttpHeaders.Names.SEC_WEBSOCKET_KEY)); + if (accept == null || !accept.equals(key)) { + throw new IOException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, key)); + } + + Channels.upgradePipelineForWebSockets(ctx); + + invokeOnSucces(ctx, h); + future.done(); + + } else if (e instanceof WebSocketFrame) { + + final WebSocketFrame frame = (WebSocketFrame) e; + NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted()); + invokeOnSucces(ctx, h); + + if (webSocket != null) { + if (frame instanceof CloseWebSocketFrame) { + Channels.setDefaultAttribute(ctx, DiscardEvent.INSTANCE); + CloseWebSocketFrame closeFrame = CloseWebSocketFrame.class.cast(frame); + webSocket.onClose(closeFrame.statusCode(), closeFrame.reasonText()); + } else { + if (frame instanceof TextWebSocketFrame) { + pendingOpcode = OPCODE_TEXT; + } else if (frame instanceof BinaryWebSocketFrame) { + pendingOpcode = OPCODE_BINARY; + } + + if (frame.content() != null && frame.content().readableBytes() > 0) { + ResponseBodyPart rp = new ResponseBodyPart(future.getURI(), frame.content(), frame.isFinalFragment()); + h.onBodyPartReceived(rp); + + if (pendingOpcode == OPCODE_BINARY) { + webSocket.onBinaryFragment(rp.getBodyPartBytes(), frame.isFinalFragment()); + } else { + webSocket.onTextFragment(frame.content().toString(Constants.UTF8), frame.isFinalFragment()); + } + } + } + } else { + NettyChannelHandler.LOGGER.debug("UpgradeHandler returned a null NettyWebSocket "); + } + } else if (e instanceof LastHttpContent) { + // FIXME what to do with this kind of messages? + } else { + NettyChannelHandler.LOGGER.error("Invalid message {}", e); + } + } + + @Override + public void onError(ChannelHandlerContext ctx, Throwable e) { + try { + Object attribute = Channels.getDefaultAttribute(ctx); + NettyChannelHandler.LOGGER.warn("onError {}", e); + if (!(attribute instanceof NettyResponseFuture)) { + return; + } + + NettyResponseFuture nettyResponse = (NettyResponseFuture) attribute; + WebSocketUpgradeHandler h = WebSocketUpgradeHandler.class.cast(nettyResponse.getAsyncHandler()); + + NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted()); + if (webSocket != null) { + webSocket.onError(e.getCause()); + webSocket.close(); + } + } catch (Throwable t) { + NettyChannelHandler.LOGGER.error("onError", t); + } + } + + @Override + public void onClose(ChannelHandlerContext ctx) { + NettyChannelHandler.LOGGER.trace("onClose {}"); + Object attribute = Channels.getDefaultAttribute(ctx); + if (!(attribute instanceof NettyResponseFuture)) { + return; + } + + try { + NettyResponseFuture nettyResponse = NettyResponseFuture.class.cast(attribute); + WebSocketUpgradeHandler h = WebSocketUpgradeHandler.class.cast(nettyResponse.getAsyncHandler()); + NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted()); + + // FIXME How could this test not succeed, attachment is a + // NettyResponseFuture???? + if (attribute != DiscardEvent.INSTANCE) + webSocket.close(1006, "Connection was closed abnormally (that is, with no close frame being sent)."); + } catch (Throwable t) { + NettyChannelHandler.LOGGER.error("onError", t); + } + } +} \ No newline at end of file diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/BodyChunkedInput.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/BodyChunkedInput.java similarity index 97% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/BodyChunkedInput.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/BodyChunkedInput.java index 22d6b15e92..0bfb24ca2f 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/BodyChunkedInput.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/BodyChunkedInput.java @@ -10,7 +10,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. */ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty.request; import org.asynchttpclient.Body; diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/BodyFileRegion.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/BodyFileRegion.java similarity index 97% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/BodyFileRegion.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/BodyFileRegion.java index 7a2db0bd79..3cd6664ebb 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/BodyFileRegion.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/BodyFileRegion.java @@ -10,7 +10,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. */ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty.request; import io.netty.channel.FileRegion; import io.netty.util.AbstractReferenceCounted; diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/FeedableBodyGenerator.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/FeedableBodyGenerator.java similarity index 98% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/FeedableBodyGenerator.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/FeedableBodyGenerator.java index 7bcd37442d..95f3e61024 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/FeedableBodyGenerator.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/FeedableBodyGenerator.java @@ -10,7 +10,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. */ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty.request; import java.io.IOException; import java.nio.ByteBuffer; diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyConnectListener.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java similarity index 87% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyConnectListener.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java index 052cf816a3..1135547e2d 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyConnectListener.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java @@ -14,7 +14,7 @@ * under the License. * */ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty.request; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -27,12 +27,13 @@ import java.net.URI; import java.nio.channels.ClosedChannelException; -import javax.net.ssl.HostnameVerifier; - import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.ProxyServer; import org.asynchttpclient.Request; +import org.asynchttpclient.providers.netty.channel.Channels; +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; +import org.asynchttpclient.providers.netty.future.NettyResponseFutures; import org.asynchttpclient.util.ProxyUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,24 +59,20 @@ public NettyResponseFuture future() { return future; } - private void onFutureSuccess(final Channel channel) throws Exception { + public void onFutureSuccess(final Channel channel) throws ConnectException { Channels.setDefaultAttribute(channel, future); SslHandler sslHandler = Channels.getSslHandler(channel); - if (sslHandler != null) { - // FIXME done on connect or on every request? - HostnameVerifier v = config.getHostnameVerifier(); - if (!v.verify(future.getURI().getHost(), sslHandler.engine().getSession())) { - ConnectException exception = new ConnectException("HostnameVerifier exception."); - future.abort(exception); - throw exception; - } + if (sslHandler != null && !config.getHostnameVerifier().verify(future.getURI().getHost(), sslHandler.engine().getSession())) { + ConnectException exception = new ConnectException("HostnameVerifier exception"); + future.abort(exception); + throw exception; } requestSender.writeRequest(channel, config, future); } - private void onFutureFailure(Channel channel, Throwable cause) throws Exception { + public void onFutureFailure(Channel channel, Throwable cause) { logger.debug("Trying to recover a dead cached channel {} with a retry value of {} ", channel, future.canRetry()); if (future.canRetry() && cause != null diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyRequestSender.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java similarity index 64% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyRequestSender.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java index 2f0ce718c1..8dd47cbae2 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyRequestSender.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java @@ -1,12 +1,27 @@ -package org.asynchttpclient.providers.netty4; - -import static org.asynchttpclient.providers.netty4.util.HttpUtil.WEBSOCKET; -import static org.asynchttpclient.providers.netty4.util.HttpUtil.isSecure; +/* + * Copyright 2010-2013 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package org.asynchttpclient.providers.netty.request; + +import static org.asynchttpclient.providers.netty.util.HttpUtil.*; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelProgressiveFuture; +import io.netty.channel.DefaultFileRegion; import io.netty.channel.FileRegion; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; @@ -33,17 +48,24 @@ import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.Body; import org.asynchttpclient.BodyGenerator; +import org.asynchttpclient.ConnectionPoolKeyStrategy; import org.asynchttpclient.FluentCaseInsensitiveStringsMap; import org.asynchttpclient.ListenableFuture; import org.asynchttpclient.ProxyServer; import org.asynchttpclient.RandomAccessBody; import org.asynchttpclient.Request; import org.asynchttpclient.filter.FilterContext; +import org.asynchttpclient.filter.FilterException; +import org.asynchttpclient.filter.IOExceptionFilter; import org.asynchttpclient.generators.InputStreamBodyGenerator; import org.asynchttpclient.listener.TransferCompletionHandler; -import org.asynchttpclient.listener.TransferCompletionHandler.TransferAdapter; import org.asynchttpclient.multipart.MultipartBody; -import org.asynchttpclient.providers.netty4.FeedableBodyGenerator.FeedListener; +import org.asynchttpclient.providers.netty.Constants; +import org.asynchttpclient.providers.netty.channel.Channels; +import org.asynchttpclient.providers.netty.future.FutureReaper; +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; +import org.asynchttpclient.providers.netty.future.NettyResponseFutures; +import org.asynchttpclient.providers.netty.request.FeedableBodyGenerator.FeedListener; import org.asynchttpclient.util.AsyncHttpProviderUtils; import org.asynchttpclient.util.ProxyUtils; import org.asynchttpclient.websocket.WebSocketUpgradeHandler; @@ -84,7 +106,7 @@ public boolean retry(Channel channel, NettyResponseFuture future) { LOGGER.debug("Trying to recover request {}\n", future.getNettyRequest()); try { - execute(future.getRequest(), future); + sendNextRequest(future.getRequest(), future); success = true; } catch (IOException iox) { @@ -99,10 +121,32 @@ public boolean retry(Channel channel, NettyResponseFuture future) { return success; } - // FIXME Netty 3: only called from nextRequest, useCache, asyncConnect and - // reclaimCache always passed as true - public void execute(final Request request, final NettyResponseFuture f) throws IOException { - doConnect(request, f.getAsyncHandler(), f, true, true, true); + public boolean applyIoExceptionFiltersAndReplayRequest(ChannelHandlerContext ctx, NettyResponseFuture future, IOException e) throws IOException { + + boolean replayed = false; + + FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(future.getAsyncHandler()).request(future.getRequest()).ioException(e).build(); + for (IOExceptionFilter asyncFilter : config.getIOExceptionFilters()) { + try { + fc = asyncFilter.filter(fc); + if (fc == null) { + throw new NullPointerException("FilterContext is null"); + } + } catch (FilterException efe) { + channels.abort(future, efe); + } + } + + if (fc.replayRequest()) { + replayRequest(future, fc, ctx); + replayed = true; + } + return replayed; + } + + public void sendNextRequest(final Request request, final NettyResponseFuture f) throws IOException { + // FIXME Why is sendNextRequest always asyncConnect? + sendRequest(request, f.getAsyncHandler(), f, true, true); } // FIXME is this useful? Can't we do that when building the request? @@ -110,105 +154,124 @@ private final boolean validateWebSocketRequest(Request request, AsyncHandler return request.getMethod().equals(HttpMethod.GET.name()) && asyncHandler instanceof WebSocketUpgradeHandler; } - public ListenableFuture doConnect(final Request request, final AsyncHandler asyncHandler, NettyResponseFuture future, boolean useCache, boolean asyncConnect, - boolean reclaimCache) throws IOException { + private Channel getCachedChannel(NettyResponseFuture future, URI uri, ConnectionPoolKeyStrategy poolKeyGen, ProxyServer proxyServer) { - if (closed.get()) { - throw new IOException("Closed"); - } - - if (request.getUrl().startsWith(WEBSOCKET) && !validateWebSocketRequest(request, asyncHandler)) { - throw new IOException("WebSocket method must be a GET"); + if (future != null && future.reuseChannel() && future.channel() != null) { + return future.channel(); + } else { + URI connectionKeyUri = proxyServer != null ? proxyServer.getURI() : uri; + return channels.lookupInCache(connectionKeyUri, poolKeyGen); } + } - ProxyServer proxyServer = ProxyUtils.getProxyServer(config, request); - boolean useProxy = proxyServer != null; + private ListenableFuture sendRequestWithCachedChannel(Channel channel, Request request, URI uri, ProxyServer proxy, NettyResponseFuture future, + AsyncHandler asyncHandler) throws IOException { + HttpRequest nettyRequest = null; - URI uri; - if (config.isUseRawUrl()) { - uri = request.getRawURI(); + if (future == null) { + nettyRequest = NettyRequests.newNettyRequest(config, request, uri, false, proxy); + future = NettyResponseFutures.newNettyResponseFuture(uri, request, asyncHandler, nettyRequest, config, proxy); } else { - uri = request.getURI(); + nettyRequest = NettyRequests.newNettyRequest(config, request, uri, future.isConnectAllowed(), proxy); + future.setNettyRequest(nettyRequest); } - Channel channel = null; + future.setState(NettyResponseFuture.STATE.POOLED); + future.attachChannel(channel, false); + + LOGGER.debug("\nUsing cached Channel {}\n for request \n{}\n", channel, nettyRequest); + Channels.setDefaultAttribute(channel, future); - if (useCache) { - if (future != null && future.reuseChannel() && future.channel() != null) { - channel = future.channel(); + try { + writeRequest(channel, config, future); + } catch (Exception ex) { + LOGGER.debug("writeRequest failure", ex); + if (ex.getMessage() != null && ex.getMessage().contains("SSLEngine")) { + LOGGER.debug("SSLEngine failure", ex); + future = null; } else { - URI connectionKeyUri = useProxy ? proxyServer.getURI() : uri; - channel = channels.lookupInCache(connectionKeyUri, request.getConnectionPoolKeyStrategy()); + try { + asyncHandler.onThrowable(ex); + } catch (Throwable t) { + LOGGER.warn("doConnect.writeRequest()", t); + } + IOException ioe = new IOException(ex.getMessage()); + ioe.initCause(ex); + throw ioe; } } + return future; + } - boolean useSSl = isSecure(uri) && !useProxy; - if (channel != null && channel.isOpen() && channel.isActive()) { - HttpRequest nettyRequest = null; + private ChannelFuture connect(Request request, URI uri, ProxyServer proxy, Bootstrap bootstrap) { + InetSocketAddress remoteAddress; + if (request.getInetAddress() != null) { + remoteAddress = new InetSocketAddress(request.getInetAddress(), AsyncHttpProviderUtils.getPort(uri)); + } else if (proxy == null || ProxyUtils.avoidProxy(proxy, uri.getHost())) { + remoteAddress = new InetSocketAddress(AsyncHttpProviderUtils.getHost(uri), AsyncHttpProviderUtils.getPort(uri)); + } else { + remoteAddress = new InetSocketAddress(proxy.getHost(), proxy.getPort()); + } - if (future == null) { - nettyRequest = NettyRequests.newNettyRequest(config, request, uri, false, proxyServer); - future = NettyResponseFutures.newNettyResponseFuture(uri, request, asyncHandler, nettyRequest, config, proxyServer); - } else { - nettyRequest = NettyRequests.newNettyRequest(config, request, uri, future.isConnectAllowed(), proxyServer); - future.setNettyRequest(nettyRequest); - } - future.setState(NettyResponseFuture.STATE.POOLED); - future.attachChannel(channel, false); + if (request.getLocalAddress() != null) { + return bootstrap.connect(remoteAddress, new InetSocketAddress(request.getLocalAddress(), 0)); + } else { + return bootstrap.connect(remoteAddress); + } + } - LOGGER.debug("\nUsing cached Channel {}\n for request \n{}\n", channel, nettyRequest); - Channels.setDefaultAttribute(channel, future); + private void performSyncConnect(ChannelFuture channelFuture, URI uri, boolean acquiredConnection, NettyConnectListener cl, AsyncHandler asyncHandler) throws IOException { - try { - writeRequest(channel, config, future); - } catch (Exception ex) { - LOGGER.debug("writeRequest failure", ex); - if (useSSl && ex.getMessage() != null && ex.getMessage().contains("SSLEngine")) { - LOGGER.debug("SSLEngine failure", ex); - future = null; - } else { - try { - asyncHandler.onThrowable(ex); - } catch (Throwable t) { - LOGGER.warn("doConnect.writeRequest()", t); - } - IOException ioe = new IOException(ex.getMessage()); - ioe.initCause(ex); - throw ioe; - } + try { + channelFuture.syncUninterruptibly(); + } catch (Throwable t) { + if (t.getCause() != null) + t = t.getCause(); + + ConnectException ce = null; + if (t instanceof ConnectException) + ce = ConnectException.class.cast(t); + else + ce = new ConnectException(t.getMessage()); + + if (acquiredConnection) { + channels.releaseFreeConnections(); } - return future; + channelFuture.cancel(false); + channels.abort(cl.future(), ce); } - // Do not throw an exception when we need an extra connection for a - // redirect. - boolean acquiredConnection = !reclaimCache && channels.acquireConnection(asyncHandler); - - NettyConnectListener cl = new NettyConnectListener.Builder(config, this, request, asyncHandler, future).build(uri); + try { + cl.operationComplete(channelFuture); + } catch (Exception e) { + if (acquiredConnection) { + channels.releaseFreeConnections(); + } + IOException ioe = new IOException(e.getMessage()); + ioe.initCause(e); + try { + asyncHandler.onThrowable(ioe); + } catch (Throwable t) { + LOGGER.warn("c.operationComplete()", t); + } + throw ioe; + } + } - boolean avoidProxy = ProxyUtils.avoidProxy(proxyServer, uri.getHost()); + private ListenableFuture sendRequestWithNewChannel(Request request, URI uri, ProxyServer proxy, NettyResponseFuture future, AsyncHandler asyncHandler, + boolean asyncConnect, boolean reclaimCache) throws IOException { - if (useSSl) { - channels.constructSSLPipeline(cl.future()); - } + boolean useSSl = isSecure(uri) && proxy == null; + // Do not throw an exception when we need an extra connection for a redirect + // FIXME why? This violate the max connection per host handling, right? + boolean acquiredConnection = !reclaimCache && channels.acquireConnection(asyncHandler); Bootstrap bootstrap = channels.getBootstrap(request.getUrl(), useSSl); + NettyConnectListener cl = new NettyConnectListener.Builder(config, this, request, asyncHandler, future).build(uri); + ChannelFuture channelFuture; try { - InetSocketAddress remoteAddress; - if (request.getInetAddress() != null) { - remoteAddress = new InetSocketAddress(request.getInetAddress(), AsyncHttpProviderUtils.getPort(uri)); - } else if (proxyServer == null || avoidProxy) { - remoteAddress = new InetSocketAddress(AsyncHttpProviderUtils.getHost(uri), AsyncHttpProviderUtils.getPort(uri)); - } else { - remoteAddress = new InetSocketAddress(proxyServer.getHost(), proxyServer.getPort()); - } - - if (request.getLocalAddress() != null) { - channelFuture = bootstrap.connect(remoteAddress, new InetSocketAddress(request.getLocalAddress(), 0)); - } else { - channelFuture = bootstrap.connect(remoteAddress); - } + channelFuture = connect(request, uri, proxy, bootstrap); } catch (Throwable t) { if (acquiredConnection) { @@ -220,36 +283,11 @@ public ListenableFuture doConnect(final Request request, final AsyncHandl // FIXME what does it have to do with the presence of a file? if (!asyncConnect && request.getFile() == null) { - int timeOut = config.getConnectionTimeoutInMs() > 0 ? config.getConnectionTimeoutInMs() : Integer.MAX_VALUE; - if (!channelFuture.awaitUninterruptibly(timeOut, TimeUnit.MILLISECONDS)) { - if (acquiredConnection) { - channels.releaseFreeConnections(); - } - // FIXME false or true? - channelFuture.cancel(false); - channels.abort(cl.future(), new ConnectException(String.format("Connect operation to %s timeout %s", uri, timeOut))); - } - - try { - cl.operationComplete(channelFuture); - } catch (Exception e) { - if (acquiredConnection) { - channels.releaseFreeConnections(); - } - IOException ioe = new IOException(e.getMessage()); - ioe.initCause(e); - try { - asyncHandler.onThrowable(ioe); - } catch (Throwable t) { - LOGGER.warn("c.operationComplete()", t); - } - throw ioe; - } + performSyncConnect(channelFuture, uri, acquiredConnection, cl, asyncHandler); } else { channelFuture.addListener(cl); } - // FIXME Why non cached??? LOGGER.debug("\nNon cached request \n{}\n\nusing Channel \n{}\n", cl.future().getNettyRequest(), channelFuture.channel()); if (!cl.future().isCancelled() || !cl.future().isDone()) { @@ -259,6 +297,29 @@ public ListenableFuture doConnect(final Request request, final AsyncHandl return cl.future(); } + public ListenableFuture sendRequest(final Request request, final AsyncHandler asyncHandler, NettyResponseFuture future, boolean asyncConnect, boolean reclaimCache) + throws IOException { + + if (closed.get()) { + throw new IOException("Closed"); + } + + // FIXME really useful? Why not do this check when building the request? + if (request.getUrl().startsWith(WEBSOCKET) && !validateWebSocketRequest(request, asyncHandler)) { + throw new IOException("WebSocket method must be a GET"); + } + + URI uri = config.isUseRawUrl() ? request.getRawURI() : request.getURI(); + ProxyServer proxy = ProxyUtils.getProxyServer(config, request); + Channel channel = getCachedChannel(future, uri, request.getConnectionPoolKeyStrategy(), proxy); + + if (channel != null && channel.isOpen() && channel.isActive()) { + return sendRequestWithCachedChannel(channel, request, uri, proxy, future, asyncHandler); + } else { + return sendRequestWithNewChannel(request, uri, proxy, future, asyncHandler, asyncConnect, reclaimCache); + } + } + private void sendFileBody(Channel channel, File file, NettyResponseFuture future) throws IOException { final RandomAccessFile raf = new RandomAccessFile(file, "r"); @@ -269,10 +330,10 @@ private void sendFileBody(Channel channel, File file, NettyResponseFuture fut if (Channels.getSslHandler(channel) != null) { writeFuture = channel.write(new ChunkedFile(raf, 0, fileLength, Constants.MAX_BUFFERED_BYTES), channel.newProgressivePromise()); } else { - // FIXME why not use io.netty.channel.DefaultFileRegion? - FileRegion region = new OptimizedFileRegion(raf, 0, fileLength); + FileRegion region = new DefaultFileRegion(raf.getChannel(), 0, fileLength); writeFuture = channel.write(region, channel.newProgressivePromise()); } + // FIXME probably useless in Netty 4 writeFuture.addListener(new ProgressListener(config, false, future.getAsyncHandler(), future) { public void operationComplete(ChannelProgressiveFuture cf) { try { @@ -389,7 +450,7 @@ private void configureTransferAdapter(AsyncHandler handler, HttpRequest netty h.add(entries.getKey(), entries.getValue()); } - TransferCompletionHandler.class.cast(handler).transferAdapter(new TransferAdapter(h)); + TransferCompletionHandler.class.cast(handler).headers(h); } private void scheduleReaper(NettyResponseFuture future) { @@ -410,7 +471,7 @@ private void scheduleReaper(NettyResponseFuture future) { } } - protected final void writeRequest(final Channel channel, final AsyncHttpClientConfig config, final NettyResponseFuture future) { + public final void writeRequest(final Channel channel, final AsyncHttpClientConfig config, final NettyResponseFuture future) { try { // If the channel is dead because it was pooled and the remote // server decided to close it, we just let it go and the @@ -474,8 +535,7 @@ protected final void writeRequest(final Channel channel, final AsyncHttpClie scheduleReaper(future); } - // FIXME Clean up Netty 3: replayRequest's response parameter is unused + - // WTF return??? + // FIXME Clean up Netty 3: replayRequest's response parameter is unused + WTF return??? public void replayRequest(final NettyResponseFuture future, FilterContext fc, ChannelHandlerContext ctx) throws IOException { Request newRequest = fc.getRequest(); future.setAsyncHandler(fc.getAsyncHandler()); @@ -484,6 +544,6 @@ public void replayRequest(final NettyResponseFuture future, FilterContext fc, LOGGER.debug("\n\nReplaying Request {}\n for Future {}\n", newRequest, future); channels.drainChannel(ctx, future); - execute(newRequest, future); + sendNextRequest(newRequest, future); } } diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyRequests.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequests.java similarity index 92% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyRequests.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequests.java index e41b387115..ad2436db13 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyRequests.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequests.java @@ -1,8 +1,21 @@ -package org.asynchttpclient.providers.netty4; - -import static org.asynchttpclient.providers.netty4.util.HttpUtil.isNTLM; -import static org.asynchttpclient.providers.netty4.util.HttpUtil.isSecure; -import static org.asynchttpclient.providers.netty4.util.HttpUtil.isWebSocket; +/* + * Copyright 2010-2013 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package org.asynchttpclient.providers.netty.request; + +import static org.asynchttpclient.providers.netty.util.HttpUtil.*; import static org.asynchttpclient.util.AsyncHttpProviderUtils.DEFAULT_CHARSET; import static org.asynchttpclient.util.MiscUtil.isNonEmpty; import io.netty.buffer.ByteBuf; @@ -32,7 +45,9 @@ import org.asynchttpclient.ntlm.NTLMEngine; import org.asynchttpclient.ntlm.NTLMEngineException; import org.asynchttpclient.org.jboss.netty.handler.codec.http.CookieEncoder; -import org.asynchttpclient.providers.netty4.spnego.SpnegoEngine; +import org.asynchttpclient.providers.netty.NettyAsyncHttpProvider; +import org.asynchttpclient.providers.netty.ws.WebSocketUtil; +import org.asynchttpclient.spnego.SpnegoEngine; import org.asynchttpclient.util.AsyncHttpProviderUtils; import org.asynchttpclient.util.AuthenticatorUtils; import org.asynchttpclient.util.UTF8UrlEncoder; @@ -145,9 +160,7 @@ else if (uri.getRawQuery() != null) String msg = NTLMEngine.INSTANCE.generateType1Msg("NTLM " + domain, authHost); headers.put(HttpHeaders.Names.AUTHORIZATION, "NTLM " + msg); } catch (NTLMEngineException e) { - IOException ie = new IOException(); - ie.initCause(e); - throw ie; + throw new IOException(e); } break; case KERBEROS: @@ -157,9 +170,7 @@ else if (uri.getRawQuery() != null) try { challengeHeader = SpnegoEngine.instance().generateToken(server); } catch (Throwable e) { - IOException ie = new IOException(); - ie.initCause(e); - throw ie; + throw new IOException(e); } headers.put(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader); break; @@ -257,11 +268,11 @@ else if (uri.getRawQuery() != null) } } else if (request.getParts() != null) { + // FIXME use Netty multipart MultipartRequestEntity mre = AsyncHttpProviderUtils.createMultipartRequestEntity(request.getParts(), request.getHeaders()); headers.put(HttpHeaders.Names.CONTENT_TYPE, mre.getContentType()); headers.put(HttpHeaders.Names.CONTENT_LENGTH, mre.getContentLength()); - hasDeferredContent = true; } else if (request.getFile() != null) { diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/ProgressListener.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/ProgressListener.java similarity index 70% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/ProgressListener.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/ProgressListener.java index 10739dac52..823bbc3a29 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/ProgressListener.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/ProgressListener.java @@ -1,4 +1,19 @@ -package org.asynchttpclient.providers.netty4; +/* + * Copyright 2010-2013 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package org.asynchttpclient.providers.netty.request; import io.netty.channel.ChannelProgressiveFuture; import io.netty.channel.ChannelProgressiveFutureListener; @@ -10,8 +25,14 @@ import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.ProgressAsyncHandler; import org.asynchttpclient.Realm; +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; +import org.asynchttpclient.providers.netty.future.NettyResponseFutures; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ProgressListener implements ChannelProgressiveFutureListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(ProgressListener.class); private final AsyncHttpClientConfig config; private final boolean notifyHeaders; @@ -28,32 +49,31 @@ public ProgressListener(AsyncHttpClientConfig config, boolean notifyHeaders, Asy @Override public void operationComplete(ChannelProgressiveFuture cf) { - // FIXME remove this with next 4.0.9: https://github.com/netty/netty/issues/1809 // The write operation failed. If the channel was cached, it means it got asynchronously closed. // Let's retry a second time. Throwable cause = cf.cause(); if (cause != null && future.getState() != NettyResponseFuture.STATE.NEW) { if (cause instanceof IllegalStateException) { - NettyAsyncHttpProvider.LOGGER.debug(cause.getMessage(), cause); + LOGGER.debug(cause.getMessage(), cause); try { cf.channel().close(); } catch (RuntimeException ex) { - NettyAsyncHttpProvider.LOGGER.debug(ex.getMessage(), ex); + LOGGER.debug(ex.getMessage(), ex); } return; } if (cause instanceof ClosedChannelException || NettyResponseFutures.abortOnReadCloseException(cause) || NettyResponseFutures.abortOnWriteCloseException(cause)) { - if (NettyAsyncHttpProvider.LOGGER.isDebugEnabled()) { - NettyAsyncHttpProvider.LOGGER.debug(cf.cause() == null ? "" : cf.cause().getMessage(), cf.cause()); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(cf.cause() == null ? "" : cf.cause().getMessage(), cf.cause()); } try { cf.channel().close(); } catch (RuntimeException ex) { - NettyAsyncHttpProvider.LOGGER.debug(ex.getMessage(), ex); + LOGGER.debug(ex.getMessage(), ex); } return; } else { @@ -82,7 +102,7 @@ public void operationComplete(ChannelProgressiveFuture cf) { @Override public void operationProgressed(ChannelProgressiveFuture f, long progress, long total) { future.touch(); - if (asyncHandler instanceof ProgressAsyncHandler) { + if (!notifyHeaders && asyncHandler instanceof ProgressAsyncHandler) { long lastProgressValue = lastProgress.getAndSet(progress); ProgressAsyncHandler.class.cast(asyncHandler).onContentWriteProgress(progress - lastProgressValue, progress, total); } diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyResponse.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponse.java similarity index 98% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyResponse.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponse.java index acf9a994c8..61b5f13671 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyResponse.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponse.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty.response; import java.io.ByteArrayInputStream; import java.io.IOException; diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/ResponseBodyPart.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseBodyPart.java similarity index 95% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/ResponseBodyPart.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseBodyPart.java index 2206317f25..86289d146d 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/ResponseBodyPart.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseBodyPart.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty.response; import io.netty.buffer.ByteBuf; @@ -25,7 +25,7 @@ import java.nio.ByteBuffer; import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.providers.netty4.util.ByteBufUtil; +import org.asynchttpclient.providers.netty.util.ByteBufUtil; /** * A callback class used when an HTTP response body is received. diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/ResponseHeaders.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseHeaders.java similarity index 97% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/ResponseHeaders.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseHeaders.java index f3c02e262d..a024e8a624 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/ResponseHeaders.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseHeaders.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty.response; import io.netty.handler.codec.http.HttpHeaders; diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/ResponseStatus.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseStatus.java similarity index 98% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/ResponseStatus.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseStatus.java index ff8f3cd77e..45c7274245 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/ResponseStatus.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseStatus.java @@ -14,7 +14,7 @@ * under the License. * */ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty.response; import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpProvider; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/spnego/SpnegoEngine.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/spnego/SpnegoEngine.java deleted file mode 100644 index b0d653391c..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/spnego/SpnegoEngine.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -/* - * ==================================================================== - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - */ - -package org.asynchttpclient.providers.netty.spnego; - -import org.asynchttpclient.util.Base64; -import org.ietf.jgss.GSSContext; -import org.ietf.jgss.GSSException; -import org.ietf.jgss.GSSManager; -import org.ietf.jgss.GSSName; -import org.ietf.jgss.Oid; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; - -/** - * SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication - * scheme. - * - * @since 4.1 - */ -public class SpnegoEngine { - private static final String SPNEGO_OID = "1.3.6.1.5.5.2"; - private static final String KERBEROS_OID = "1.2.840.113554.1.2.2"; - - private final Logger log = LoggerFactory.getLogger(getClass()); - - private final SpnegoTokenGenerator spnegoGenerator; - - public SpnegoEngine(final SpnegoTokenGenerator spnegoGenerator) { - this.spnegoGenerator = spnegoGenerator; - } - - public SpnegoEngine() { - this(null); - } - - public String generateToken(String server) throws Throwable { - GSSContext gssContext = null; - byte[] token = null; // base64 decoded challenge - Oid negotiationOid = null; - - try { - log.debug("init {}", server); - /* Using the SPNEGO OID is the correct method. - * Kerberos v5 works for IIS but not JBoss. Unwrapping - * the initial token when using SPNEGO OID looks like what is - * described here... - * - * http://msdn.microsoft.com/en-us/library/ms995330.aspx - * - * Another helpful URL... - * - * http://publib.boulder.ibm.com/infocenter/wasinfo/v7r0/index.jsp?topic=/com.ibm.websphere.express.doc/info/exp/ae/tsec_SPNEGO_token.html - * - * Unfortunately SPNEGO is JRE >=1.6. - */ - - /** Try SPNEGO by default, fall back to Kerberos later if error */ - negotiationOid = new Oid(SPNEGO_OID); - - boolean tryKerberos = false; - try { - GSSManager manager = GSSManager.getInstance(); - GSSName serverName = manager.createName("HTTP@" + server, GSSName.NT_HOSTBASED_SERVICE); - gssContext = manager.createContext( - serverName.canonicalize(negotiationOid), negotiationOid, null, - GSSContext.DEFAULT_LIFETIME); - gssContext.requestMutualAuth(true); - gssContext.requestCredDeleg(true); - } catch (GSSException ex) { - log.error("generateToken", ex); - // BAD MECH means we are likely to be using 1.5, fall back to Kerberos MECH. - // Rethrow any other exception. - if (ex.getMajor() == GSSException.BAD_MECH) { - log.debug("GSSException BAD_MECH, retry with Kerberos MECH"); - tryKerberos = true; - } else { - throw ex; - } - - } - if (tryKerberos) { - /* Kerberos v5 GSS-API mechanism defined in RFC 1964.*/ - log.debug("Using Kerberos MECH {}", KERBEROS_OID); - negotiationOid = new Oid(KERBEROS_OID); - GSSManager manager = GSSManager.getInstance(); - GSSName serverName = manager.createName("HTTP@" + server, GSSName.NT_HOSTBASED_SERVICE); - gssContext = manager.createContext( - serverName.canonicalize(negotiationOid), negotiationOid, null, - GSSContext.DEFAULT_LIFETIME); - gssContext.requestMutualAuth(true); - gssContext.requestCredDeleg(true); - } - - // TODO suspicious: this will always be null because no value has been assigned before. Assign directly? - if (token == null) { - token = new byte[0]; - } - - token = gssContext.initSecContext(token, 0, token.length); - if (token == null) { - throw new Exception("GSS security context initialization failed"); - } - - /* - * IIS accepts Kerberos and SPNEGO tokens. Some other servers Jboss, Glassfish? - * seem to only accept SPNEGO. Below wraps Kerberos into SPNEGO token. - */ - if (spnegoGenerator != null && negotiationOid.toString().equals(KERBEROS_OID)) { - token = spnegoGenerator.generateSpnegoDERObject(token); - } - - gssContext.dispose(); - - String tokenstr = new String(Base64.encode(token)); - log.debug("Sending response '{}' back to the server", tokenstr); - - return tokenstr; - } catch (GSSException gsse) { - log.error("generateToken", gsse); - if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL - || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) - throw new Exception(gsse.getMessage(), gsse); - if (gsse.getMajor() == GSSException.NO_CRED) - throw new Exception(gsse.getMessage(), gsse); - if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN - || gsse.getMajor() == GSSException.DUPLICATE_TOKEN - || gsse.getMajor() == GSSException.OLD_TOKEN) - throw new Exception(gsse.getMessage(), gsse); - // other error - throw new Exception(gsse.getMessage()); - } catch (IOException ex) { - throw new Exception(ex.getMessage()); - } - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/util/ByteBufUtil.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ByteBufUtil.java similarity index 95% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/util/ByteBufUtil.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ByteBufUtil.java index 952d787828..fa64563e70 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/util/ByteBufUtil.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ByteBufUtil.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.asynchttpclient.providers.netty4.util; +package org.asynchttpclient.providers.netty.util; import io.netty.buffer.ByteBuf; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ChannelBufferUtil.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ChannelBufferUtil.java deleted file mode 100644 index e577d54735..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ChannelBufferUtil.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file to you under the Apache License, version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package org.asynchttpclient.providers.netty.util; - -import org.jboss.netty.buffer.ChannelBuffer; - -public class ChannelBufferUtil { - - public static byte[] channelBuffer2bytes(ChannelBuffer b) { - int readable = b.readableBytes(); - int readerIndex = b.readerIndex(); - if (b.hasArray()) { - byte[] array = b.array(); - if (b.arrayOffset() == 0 && readerIndex == 0 && array.length == readable) { - return array; - } - } - byte[] array = new byte[readable]; - b.getBytes(readerIndex, array); - return array; - } -} \ No newline at end of file diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/CleanupChannelGroup.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/CleanupChannelGroup.java index 486c61ef32..671da09988 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/CleanupChannelGroup.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/CleanupChannelGroup.java @@ -28,17 +28,11 @@ package org.asynchttpclient.providers.netty.util; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelFuture; -import org.jboss.netty.channel.group.ChannelGroup; -import org.jboss.netty.channel.group.ChannelGroupFuture; -import org.jboss.netty.channel.group.DefaultChannelGroup; -import org.jboss.netty.channel.group.DefaultChannelGroupFuture; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import io.netty.channel.Channel; +import io.netty.channel.group.ChannelGroupFuture; +import io.netty.channel.group.DefaultChannelGroup; +import io.netty.util.concurrent.GlobalEventExecutor; -import java.util.ArrayList; -import java.util.Collection; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -50,24 +44,20 @@ */ public class CleanupChannelGroup extends DefaultChannelGroup { - private final static Logger logger = LoggerFactory.getLogger(CleanupChannelGroup.class); // internal vars -------------------------------------------------------------------------------------------------- - private final AtomicBoolean closed; - private final ReentrantReadWriteLock lock; + private final AtomicBoolean closed = new AtomicBoolean(false); + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); // constructors --------------------------------------------------------------------------------------------------- public CleanupChannelGroup() { - this.closed = new AtomicBoolean(false); - this.lock = new ReentrantReadWriteLock(); + super(GlobalEventExecutor.INSTANCE); } public CleanupChannelGroup(String name) { - super(name); - this.closed = new AtomicBoolean(false); - this.lock = new ReentrantReadWriteLock(); + super(name, GlobalEventExecutor.INSTANCE); } // DefaultChannelGroup -------------------------------------------------------------------------------------------- @@ -80,9 +70,11 @@ public ChannelGroupFuture close() { // First time close() is called. return super.close(); } else { - Collection futures = new ArrayList(); - logger.debug("CleanupChannelGroup Already closed"); - return new DefaultChannelGroupFuture(ChannelGroup.class.cast(this), futures); + // FIXME DefaultChannelGroupFuture is package protected +// Collection futures = new ArrayList(); +// logger.debug("CleanupChannelGroup already closed"); +// return new DefaultChannelGroupFuture(ChannelGroup.class.cast(this), futures, GlobalEventExecutor.INSTANCE); + throw new UnsupportedOperationException("CleanupChannelGroup already closed"); } } finally { this.lock.writeLock().unlock(); diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/util/HttpUtil.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtil.java similarity index 94% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/util/HttpUtil.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtil.java index cf6b49a3a6..5889de9ea5 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/util/HttpUtil.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtil.java @@ -1,4 +1,4 @@ -package org.asynchttpclient.providers.netty4.util; +package org.asynchttpclient.providers.netty.util; import static org.asynchttpclient.util.MiscUtil.isNonEmpty; diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java similarity index 71% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyWebSocket.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java index f4a4a8dd76..77e32b998f 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyWebSocket.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java @@ -10,7 +10,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. */ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty.ws; import org.asynchttpclient.websocket.WebSocket; import org.asynchttpclient.websocket.WebSocketByteListener; @@ -45,74 +45,74 @@ public NettyWebSocket(Channel channel) { this.channel = channel; } - // @Override + @Override public WebSocket sendMessage(byte[] message) { channel.writeAndFlush(new BinaryWebSocketFrame(wrappedBuffer(message))); return this; } - // @Override + @Override public WebSocket stream(byte[] fragment, boolean last) { throw new UnsupportedOperationException("Streaming currently only supported by the Grizzly provider."); } - // @Override + @Override public WebSocket stream(byte[] fragment, int offset, int len, boolean last) { throw new UnsupportedOperationException("Streaming currently only supported by the Grizzly provider."); } - // @Override + @Override public WebSocket sendTextMessage(String message) { channel.writeAndFlush(new TextWebSocketFrame(message)); return this; } - // @Override + @Override public WebSocket streamText(String fragment, boolean last) { throw new UnsupportedOperationException("Streaming currently only supported by the Grizzly provider."); } - // @Override + @Override public WebSocket sendPing(byte[] payload) { channel.writeAndFlush(new PingWebSocketFrame(wrappedBuffer(payload))); return this; } - // @Override + @Override public WebSocket sendPong(byte[] payload) { channel.writeAndFlush(new PongWebSocketFrame(wrappedBuffer(payload))); return this; } - // @Override + @Override public WebSocket addWebSocketListener(WebSocketListener l) { listeners.add(l); return this; } - // @Override + @Override public WebSocket removeWebSocketListener(WebSocketListener l) { listeners.remove(l); return this; } public int getMaxBufferSize() { - return maxBufferSize; + return maxBufferSize; } - + public void setMaxBufferSize(int bufferSize) { - maxBufferSize = bufferSize; - - if(maxBufferSize < 8192) - maxBufferSize = 8192; + maxBufferSize = bufferSize; + + if (maxBufferSize < 8192) + maxBufferSize = 8192; } - - // @Override + + @Override public boolean isOpen() { return channel.isOpen(); } - // @Override + @Override public void close() { onClose(); listeners.clear(); @@ -124,7 +124,6 @@ public void close() { } } - // @Override public void close(int statusCode, String reason) { onClose(statusCode, reason); listeners.clear(); @@ -136,31 +135,30 @@ public void close(int statusCode, String reason) { } } - protected void onBinaryFragment(byte[] message, boolean last) { + public void onBinaryFragment(byte[] message, boolean last) { for (WebSocketListener l : listeners) { if (l instanceof WebSocketByteListener) { try { - WebSocketByteListener.class.cast(l).onFragment(message,last); - - if(byteBuffer == null) { - byteBuffer = new ByteArrayOutputStream(); - } - - byteBuffer.write(message); - - if(byteBuffer.size() > maxBufferSize) { - Exception e = new Exception("Exceeded Netty Web Socket maximum buffer size of " + getMaxBufferSize()); + WebSocketByteListener.class.cast(l).onFragment(message, last); + + if (byteBuffer == null) { + byteBuffer = new ByteArrayOutputStream(); + } + + byteBuffer.write(message); + + if (byteBuffer.size() > maxBufferSize) { + Exception e = new Exception("Exceeded Netty Web Socket maximum buffer size of " + getMaxBufferSize()); l.onError(e); - this.close(); - return; - } - - - if(last) { - WebSocketByteListener.class.cast(l).onMessage(byteBuffer.toByteArray()); - byteBuffer = null; - textBuffer = null; - } + this.close(); + return; + } + + if (last) { + WebSocketByteListener.class.cast(l).onMessage(byteBuffer.toByteArray()); + byteBuffer = null; + textBuffer = null; + } } catch (Exception ex) { l.onError(ex); } @@ -168,30 +166,30 @@ protected void onBinaryFragment(byte[] message, boolean last) { } } - protected void onTextFragment(String message, boolean last) { + public void onTextFragment(String message, boolean last) { for (WebSocketListener l : listeners) { if (l instanceof WebSocketTextListener) { try { - WebSocketTextListener.class.cast(l).onFragment(message,last); - - if(textBuffer == null) { - textBuffer = new StringBuilder(); - } - - textBuffer.append(message); - - if(textBuffer.length() > maxBufferSize) { - Exception e = new Exception("Exceeded Netty Web Socket maximum buffer size of " + getMaxBufferSize()); + WebSocketTextListener.class.cast(l).onFragment(message, last); + + if (textBuffer == null) { + textBuffer = new StringBuilder(); + } + + textBuffer.append(message); + + if (textBuffer.length() > maxBufferSize) { + Exception e = new Exception("Exceeded Netty Web Socket maximum buffer size of " + getMaxBufferSize()); l.onError(e); - this.close(); - return; - } - - if(last) { - WebSocketTextListener.class.cast(l).onMessage(textBuffer.toString()); - byteBuffer = null; - textBuffer = null; - } + this.close(); + return; + } + + if (last) { + WebSocketTextListener.class.cast(l).onMessage(textBuffer.toString()); + byteBuffer = null; + textBuffer = null; + } } catch (Exception ex) { l.onError(ex); } @@ -199,7 +197,7 @@ protected void onTextFragment(String message, boolean last) { } } - protected void onError(Throwable t) { + public void onError(Throwable t) { for (WebSocketListener l : listeners) { try { l.onError(t); @@ -210,11 +208,11 @@ protected void onError(Throwable t) { } } - protected void onClose() { + public void onClose() { onClose(1000, "Normal closure; the connection successfully completed whatever purpose for which it was created."); } - protected void onClose(int code, String reason) { + public void onClose(int code, String reason) { for (WebSocketListener l : listeners) { try { if (l instanceof WebSocketCloseCodeReasonListener) { @@ -229,8 +227,6 @@ protected void onClose(int code, String reason) { @Override public String toString() { - return "NettyWebSocket{" + - "channel=" + channel + - '}'; + return "NettyWebSocket{" + "channel=" + channel + '}'; } } diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/WebSocketUtil.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/WebSocketUtil.java similarity index 98% rename from providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/WebSocketUtil.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/WebSocketUtil.java index 5c92684365..3321677f48 100644 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/WebSocketUtil.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/WebSocketUtil.java @@ -10,7 +10,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. */ -package org.asynchttpclient.providers.netty4; +package org.asynchttpclient.providers.netty.ws; import org.asynchttpclient.util.Base64; diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderTest.java index c55b13996c..4ceea15708 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderTest.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderTest.java @@ -12,16 +12,8 @@ */ package org.asynchttpclient.providers.netty; -import static org.testng.Assert.assertEquals; - -import java.util.concurrent.Executors; - -import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; -import org.testng.annotations.Test; - import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Response; import org.asynchttpclient.async.AbstractBasicTest; public class NettyAsyncHttpProviderTest extends AbstractBasicTest { @@ -30,19 +22,4 @@ public class NettyAsyncHttpProviderTest extends AbstractBasicTest { public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { return NettyProviderUtil.nettyProvider(config); } - - @Test - public void bossThreadPoolExecutor() throws Exception { - NettyAsyncHttpProviderConfig conf = new NettyAsyncHttpProviderConfig(); - conf.setBossExecutorService(Executors.newSingleThreadExecutor()); - - AsyncHttpClientConfig cf = new AsyncHttpClientConfig.Builder().setAsyncHttpClientProviderConfig(conf).build(); - AsyncHttpClient c = getAsyncHttpClient(cf); - try { - Response r = c.prepareGet(getTargetUrl()).execute().get(); - assertEquals(r.getStatusCode(), 200); - } finally { - c.close(); - } - } } diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncProviderBasicTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncProviderBasicTest.java index d422b202ac..3ef9a5b966 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncProviderBasicTest.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncProviderBasicTest.java @@ -12,15 +12,11 @@ */ package org.asynchttpclient.providers.netty; -import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; -import org.testng.annotations.Test; - import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.AsyncHttpProviderConfig; import org.asynchttpclient.async.AsyncProvidersBasicTest; -@Test public class NettyAsyncProviderBasicTest extends AsyncProvidersBasicTest { @Override @@ -29,9 +25,9 @@ public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { } @Override - protected AsyncHttpProviderConfig getProviderConfig() { + protected AsyncHttpProviderConfig getProviderConfig() { final NettyAsyncHttpProviderConfig config = new NettyAsyncHttpProviderConfig(); - config.addProperty("tcpNoDelay", true); + config.addProperty("TCP_NODELAY", true); return config; } } diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncProviderPipelineTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncProviderPipelineTest.java index 87c313345d..ab0a698ad6 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncProviderPipelineTest.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncProviderPipelineTest.java @@ -14,6 +14,10 @@ package org.asynchttpclient.providers.netty; import static org.testng.Assert.*; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.http.HttpMessage; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -24,24 +28,28 @@ import org.asynchttpclient.RequestBuilder; import org.asynchttpclient.Response; import org.asynchttpclient.async.AbstractBasicTest; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.ChannelPipelineFactory; -import org.jboss.netty.channel.MessageEvent; -import org.jboss.netty.channel.SimpleChannelHandler; -import org.jboss.netty.handler.codec.http.HttpMessage; +import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig.AdditionalChannelInitializer; import org.testng.annotations.Test; public class NettyAsyncProviderPipelineTest extends AbstractBasicTest { @Override public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return new AsyncHttpClient(new CopyEncodingNettyAsyncHttpProvider(config), config); + return NettyProviderUtil.nettyProvider(config); } @Test(groups = { "standalone", "netty_provider" }) public void asyncPipelineTest() throws Exception { - AsyncHttpClient p = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setCompressionEnabled(true).build()); + + NettyAsyncHttpProviderConfig nettyConfig = new NettyAsyncHttpProviderConfig(); + nettyConfig.setHttpAdditionalChannelInitializer(new AdditionalChannelInitializer() { + public void initChannel(Channel ch) throws Exception { + // super.initPlainChannel(ch); + ch.pipeline().addBefore("inflater", "copyEncodingHeader", new CopyEncodingHandler()); + } + }); + AsyncHttpClient p = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setCompressionEnabled(true).setAsyncHttpClientProviderConfig(nettyConfig).build()); + try { final CountDownLatch l = new CountDownLatch(1); Request request = new RequestBuilder("GET").setUrl(getTargetUrl()).build(); @@ -65,35 +73,17 @@ public Response onCompleted(Response response) throws Exception { } } - private static class CopyEncodingNettyAsyncHttpProvider extends - NettyAsyncHttpProvider { - public CopyEncodingNettyAsyncHttpProvider(AsyncHttpClientConfig config) { - super(config); - } - - protected ChannelPipelineFactory createPlainPipelineFactory() { - final ChannelPipelineFactory pipelineFactory = super.createPlainPipelineFactory(); - return new ChannelPipelineFactory() { - public ChannelPipeline getPipeline() throws Exception { - ChannelPipeline pipeline = pipelineFactory.getPipeline(); - pipeline.addBefore("inflater", "copyEncodingHeader", new CopyEncodingHandler()); - return pipeline; - } - }; - } - } - - private static class CopyEncodingHandler extends SimpleChannelHandler { + private static class CopyEncodingHandler extends ChannelInboundHandlerAdapter { @Override - public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) { - Object msg = e.getMessage(); - if (msg instanceof HttpMessage) { - HttpMessage m = (HttpMessage) msg; - // for test there is no Content-Encoding header so just hard coding value + public void channelRead(ChannelHandlerContext ctx, Object e) { + if (e instanceof HttpMessage) { + HttpMessage m = (HttpMessage) e; + // for test there is no Content-Encoding header so just hard + // coding value // for verification - m.setHeader("X-Original-Content-Encoding", ""); + m.headers().set("X-Original-Content-Encoding", ""); } - ctx.sendUpstream(e); + ctx.fireChannelRead(e); } } } diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncResponseTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncResponseTest.java index 812a04e0fe..e679303540 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncResponseTest.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncResponseTest.java @@ -13,12 +13,7 @@ package org.asynchttpclient.providers.netty; -import org.asynchttpclient.Cookie; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.providers.netty.NettyResponse; -import org.asynchttpclient.providers.netty.ResponseStatus; -import org.testng.annotations.Test; +import static org.testng.Assert.*; import java.text.SimpleDateFormat; import java.util.Date; @@ -26,8 +21,12 @@ import java.util.Locale; import java.util.TimeZone; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import org.asynchttpclient.Cookie; +import org.asynchttpclient.FluentCaseInsensitiveStringsMap; +import org.asynchttpclient.HttpResponseHeaders; +import org.asynchttpclient.providers.netty.response.NettyResponse; +import org.asynchttpclient.providers.netty.response.ResponseStatus; +import org.testng.annotations.Test; /** * @author Benjamin Hanzelmann @@ -44,7 +43,7 @@ public void testCookieParseExpires() { final String cookieDef = String.format("efmembercheck=true; expires=%s; path=/; domain=.eclipse.org", sdf.format(date)); NettyResponse - response = new NettyResponse(new ResponseStatus(null, null, null), new HttpResponseHeaders(null, null, false) { + response = new NettyResponse(new ResponseStatus(null, null), new HttpResponseHeaders(null, null, false) { @Override public FluentCaseInsensitiveStringsMap getHeaders() { return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); @@ -61,7 +60,7 @@ public FluentCaseInsensitiveStringsMap getHeaders() { @Test(groups = "standalone") public void testCookieParseMaxAge() { final String cookieDef = "efmembercheck=true; max-age=60; path=/; domain=.eclipse.org"; - NettyResponse response = new NettyResponse(new ResponseStatus(null, null, null), new HttpResponseHeaders(null, null, false) { + NettyResponse response = new NettyResponse(new ResponseStatus(null, null), new HttpResponseHeaders(null, null, false) { @Override public FluentCaseInsensitiveStringsMap getHeaders() { return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); @@ -77,7 +76,7 @@ public FluentCaseInsensitiveStringsMap getHeaders() { @Test(groups = "standalone") public void testCookieParseWeirdExpiresValue() { final String cookieDef = "efmembercheck=true; expires=60; path=/; domain=.eclipse.org"; - NettyResponse response = new NettyResponse(new ResponseStatus(null, null, null), new HttpResponseHeaders(null, null, false) { + NettyResponse response = new NettyResponse(new ResponseStatus(null, null), new HttpResponseHeaders(null, null, false) { @Override public FluentCaseInsensitiveStringsMap getHeaders() { return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAuthTimeoutTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAuthTimeoutTest.java index 61a633e002..1056d502fa 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAuthTimeoutTest.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAuthTimeoutTest.java @@ -22,5 +22,4 @@ public class NettyAuthTimeoutTest extends AuthTimeoutTest { public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { return NettyProviderUtil.nettyProvider(config); } - } diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyBasicAuthTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyBasicAuthTest.java index 2e0a4091fc..69dfb34b78 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyBasicAuthTest.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyBasicAuthTest.java @@ -15,9 +15,7 @@ import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.async.BasicAuthTest; -import org.testng.annotations.Test; -@Test public class NettyBasicAuthTest extends BasicAuthTest { @Override @@ -29,9 +27,4 @@ public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { public String getProviderClass() { return NettyAsyncHttpProvider.class.getName(); } - - @Test(enabled = false) - public void stringBuilderBodyConsumerTest() throws Exception { - // FIXME - } } diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyConnectionPoolTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyConnectionPoolTest.java index 945761d5e1..11e6d709b6 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyConnectionPoolTest.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyConnectionPoolTest.java @@ -12,14 +12,11 @@ */ package org.asynchttpclient.providers.netty; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; +import static org.testng.Assert.*; +import io.netty.channel.Channel; import java.util.concurrent.TimeUnit; -import org.jboss.netty.channel.Channel; - import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.ConnectionsPool; diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyMultipartUploadTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyMultipartUploadTest.java index 4989481508..fdbfb52d13 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyMultipartUploadTest.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyMultipartUploadTest.java @@ -15,12 +15,10 @@ import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.async.MultipartUploadTest; -import org.testng.annotations.Test; /** * @author dominict */ -@Test public class NettyMultipartUploadTest extends MultipartUploadTest { @Override diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPerRequestTimeoutTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPerRequestTimeoutTest.java index c4692cb476..ff4eef3f1c 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPerRequestTimeoutTest.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyPerRequestTimeoutTest.java @@ -12,7 +12,7 @@ */ package org.asynchttpclient.providers.netty; -import static org.testng.Assert.assertTrue; +import static org.testng.Assert.*; import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyProviderUtil.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyProviderUtil.java index 499e0025c4..983076d728 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyProviderUtil.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyProviderUtil.java @@ -21,16 +21,9 @@ public class NettyProviderUtil { public static AsyncHttpClient nettyProvider(AsyncHttpClientConfig config) { - // FIXME why do tests fail with this set up? Seems like we have a race condition - // if (config == null) { - // config = new AsyncHttpClientConfig.Builder().build(); - // } - // return new AsyncHttpClient(new NettyAsyncHttpProvider(config), config); - if (config == null) { - return new AsyncHttpClient(); - } else { - return new AsyncHttpClient(config); + config = new AsyncHttpClientConfig.Builder().build(); } + return new AsyncHttpClient(new NettyAsyncHttpProvider(config), config); } } diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRequestThrottleTimeoutTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRequestThrottleTimeoutTest.java index 6ca05960ab..01f53bffa9 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRequestThrottleTimeoutTest.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyRequestThrottleTimeoutTest.java @@ -12,11 +12,11 @@ */ package org.asynchttpclient.providers.netty; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.testng.Assert.*; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; @@ -27,17 +27,16 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.continuation.Continuation; -import org.eclipse.jetty.continuation.ContinuationSupport; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - import org.asynchttpclient.AsyncCompletionHandler; import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.Response; import org.asynchttpclient.async.AbstractBasicTest; +import org.eclipse.jetty.continuation.Continuation; +import org.eclipse.jetty.continuation.ContinuationSupport; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.testng.annotations.Test; public class NettyRequestThrottleTimeoutTest extends AbstractBasicTest { private static final String MSG = "Enough is enough."; @@ -80,11 +79,12 @@ public void run() { public void testRequestTimeout() throws IOException { final Semaphore requestThrottle = new Semaphore(1); - final AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setCompressionEnabled(true).setAllowPoolingConnection(true).setMaximumConnectionsTotal(1).build()); + final AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setCompressionEnabled(true).setAllowPoolingConnection(true) + .setMaximumConnectionsTotal(1).build()); try { final CountDownLatch latch = new CountDownLatch(2); + final List tooManyConnections = Collections.synchronizedList(new ArrayList(2)); - final List tooManyConnections = new ArrayList(2); for (int i = 0; i < 2; i++) { new Thread(new Runnable() { @@ -119,7 +119,6 @@ public void onThrowable(Throwable t) { } }).start(); - } try { @@ -128,7 +127,7 @@ public void onThrowable(Throwable t) { fail("failed to wait for requests to complete"); } - assertTrue(tooManyConnections.size() == 0, "Should not have any connection errors where too many connections have been attempted"); + assertTrue(tooManyConnections.isEmpty(), "Should not have any connection errors where too many connections have been attempted"); } finally { client.close(); } diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/RetryNonBlockingIssue.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/RetryNonBlockingIssue.java index 6169dc0fe3..09f89010cd 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/RetryNonBlockingIssue.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/RetryNonBlockingIssue.java @@ -40,7 +40,7 @@ import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -// FIXME there's no retry actually +//FIXME there's no retry actually public class RetryNonBlockingIssue extends AbstractBasicTest { @Override @@ -52,9 +52,11 @@ public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { public void setUpGlobal() throws Exception { port1 = findFreePort(); server = newJettyHttpServer(port1); + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); context.setContextPath("/"); context.addServlet(new ServletHolder(new MockExceptionServlet()), "/*"); + server.setHandler(context); server.start(); } @@ -138,7 +140,7 @@ public void testRetryNonBlockingAsyncConnect() throws IOException, InterruptedEx StringBuilder b = new StringBuilder(); for (ListenableFuture r : res) { Response theres = r.get(); - assertEquals(200, theres.getStatusCode()); + assertEquals(theres.getStatusCode(), 200); b.append("==============\r\n"); b.append("Response Headers\r\n"); Map> heads = theres.getHeaders(); @@ -179,7 +181,7 @@ public void testRetryBlocking() throws IOException, InterruptedException, Execut StringBuilder b = new StringBuilder(); for (ListenableFuture r : res) { Response theres = r.get(); - assertEquals(200, theres.getStatusCode()); + assertEquals(theres.getStatusCode(), 200); b.append("==============\r\n"); b.append("Response Headers\r\n"); Map> heads = theres.getHeaders(); @@ -188,7 +190,8 @@ public void testRetryBlocking() throws IOException, InterruptedException, Execut assertTrue(heads.size() > 0); } - logger.debug(b.toString()); + System.out.println(b.toString()); + System.out.flush(); } finally { client.close(); diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyRedirectTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyRedirectTest.java index 63b875480c..185ffb1806 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyRedirectTest.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyRedirectTest.java @@ -15,7 +15,6 @@ import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.providers.netty.NettyProviderUtil; -import org.asynchttpclient.providers.netty.NettyProviderUtil; import org.asynchttpclient.websocket.RedirectTest; public class NettyRedirectTest extends RedirectTest { @@ -24,5 +23,4 @@ public class NettyRedirectTest extends RedirectTest { public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { return NettyProviderUtil.nettyProvider(config); } - } diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyTextMessageTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyTextMessageTest.java index cb35f53be2..c1286255ad 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyTextMessageTest.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/websocket/NettyTextMessageTest.java @@ -16,9 +16,7 @@ import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.providers.netty.NettyProviderUtil; import org.asynchttpclient.websocket.TextMessageTest; -import org.testng.annotations.Test; -@Test public class NettyTextMessageTest extends TextMessageTest { @Override public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { diff --git a/providers/netty4/pom.xml b/providers/netty4/pom.xml deleted file mode 100644 index 90a29ea432..0000000000 --- a/providers/netty4/pom.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - org.asynchttpclient - async-http-client-providers-parent - 2.0.0-SNAPSHOT - - 4.0.0 - async-http-client-netty4-provider - Asynchronous Http Client Netty 4 Provider - - The Async Http Client Netty 4 Provider. - - - - - sonatype-releases - https://oss.sonatype.org/content/repositories/releases - - true - - - false - - - - sonatype-snapshots - https://oss.sonatype.org/content/repositories/snapshots - - false - - - true - - - - - - - io.netty - netty-all - 4.0.9.Final - - - org.javassist - javassist - 3.18.0-GA - - - - \ No newline at end of file diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyAsyncHttpProvider.java b/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyAsyncHttpProvider.java deleted file mode 100644 index a17fc9e1e5..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyAsyncHttpProvider.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2010-2013 Ning, Inc. - * - * Ning licenses this file to you under the Apache License, version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package org.asynchttpclient.providers.netty4; - -import java.io.IOException; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.Request; -import org.asynchttpclient.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class NettyAsyncHttpProvider implements AsyncHttpProvider { - - static final Logger LOGGER = LoggerFactory.getLogger(NettyAsyncHttpProvider.class); - - private final AsyncHttpClientConfig config; - private final NettyAsyncHttpProviderConfig asyncHttpProviderConfig; - private final AtomicBoolean closed = new AtomicBoolean(false); - private final Channels channels; - private final NettyRequestSender requestSender; - private final NettyChannelHandler channelHandler; - private final boolean executeConnectAsync; - - public NettyAsyncHttpProvider(AsyncHttpClientConfig config) { - - this.config = config; - if (config.getAsyncHttpProviderConfig() instanceof NettyAsyncHttpProviderConfig) { - asyncHttpProviderConfig = NettyAsyncHttpProviderConfig.class.cast(config.getAsyncHttpProviderConfig()); - } else { - asyncHttpProviderConfig = new NettyAsyncHttpProviderConfig(); - } - - channels = new Channels(config, asyncHttpProviderConfig); - requestSender = new NettyRequestSender(closed, config, channels); - channelHandler = new NettyChannelHandler(config, requestSender, channels, closed); - channels.configure(channelHandler); - - executeConnectAsync = asyncHttpProviderConfig.isAsyncConnect(); - // FIXME - // if (!executeConnectAsync) { - // DefaultChannelFuture.setUseDeadLockChecker(true); - // } - } - - @Override - public String toString() { - return String.format("NettyAsyncHttpProvider4:\n\t- maxConnections: %d\n\t- openChannels: %s\n\t- connectionPools: %s", config.getMaxTotalConnections() - - channels.freeConnections.availablePermits(), channels.openChannels.toString(), channels.connectionsPool.toString()); - } - - @Override - public void close() { - closed.set(true); - try { - channels.close(); -// config.executorService().shutdown(); - config.reaper().shutdown(); - } catch (Throwable t) { - LOGGER.warn("Unexpected error on close", t); - } - } - - @Override - public Response prepareResponse(final HttpResponseStatus status, final HttpResponseHeaders headers, final List bodyParts) { - throw new UnsupportedOperationException("Mocked, should be refactored"); - } - - @Override - public ListenableFuture execute(Request request, final AsyncHandler asyncHandler) throws IOException { - return requestSender.doConnect(request, asyncHandler, null, true, executeConnectAsync, false); - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyAsyncHttpProviderConfig.java b/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyAsyncHttpProviderConfig.java deleted file mode 100644 index be8aa75d44..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyAsyncHttpProviderConfig.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file to you under the Apache License, version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - */ -package org.asynchttpclient.providers.netty4; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * This class can be used to pass Netty's internal configuration options. See Netty documentation for more information. - */ -public class NettyAsyncHttpProviderConfig implements AsyncHttpProviderConfig { - - private final static Logger LOGGER = LoggerFactory.getLogger(NettyAsyncHttpProviderConfig.class); - - /** - * Use Netty's blocking IO stategy. - */ - private boolean useBlockingIO; - - /** - * Allow configuring the Netty's event loop. - */ - private EventLoopGroup eventLoopGroup; - - private AdditionalChannelInitializer httpAdditionalChannelInitializer; - private AdditionalChannelInitializer wsAdditionalChannelInitializer; - private AdditionalChannelInitializer httpsAdditionalChannelInitializer; - private AdditionalChannelInitializer wssAdditionalChannelInitializer; - - /** - * Execute the connect operation asynchronously. - */ - private boolean asyncConnect; - - /** - * HttpClientCodec's maxInitialLineLength - */ - private int maxInitialLineLength = 4096; - - /** - * HttpClientCodec's maxHeaderSize - */ - private int maxHeaderSize = 8192; - - /** - * HttpClientCodec's maxChunkSize - */ - private int maxChunkSize = 8192; - - /** - * Use direct {@link java.nio.ByteBuffer} - */ - public final static String USE_DIRECT_BYTEBUFFER = "bufferFactory"; - - /** - * Allow nested request from any {@link org.asynchttpclient.AsyncHandler} - */ - public final static String DISABLE_NESTED_REQUEST = "disableNestedRequest"; - - /** - * See {@link java.net.Socket#setReuseAddress(boolean)} - */ - public final static String REUSE_ADDRESS = ChannelOption.SO_REUSEADDR.name(); - - private final Map properties = new HashMap(); - - public NettyAsyncHttpProviderConfig() { - properties.put(REUSE_ADDRESS, Boolean.FALSE); - } - - /** - * Add a property that will be used when the AsyncHttpClient initialize its {@link org.asynchttpclient.AsyncHttpProvider} - * - * @param name - * the name of the property - * @param value - * the value of the property - * @return this instance of AsyncHttpProviderConfig - */ - public NettyAsyncHttpProviderConfig addProperty(String name, Object value) { - - if (name.equals(REUSE_ADDRESS) && value == Boolean.TRUE && System.getProperty("os.name").toLowerCase().contains("win")) { - LOGGER.warn("Can't enable {} on Windows", REUSE_ADDRESS); - } else { - properties.put(name, value); - } - - return this; - } - - /** - * Return the value associated with the property's name - * - * @param name - * @return this instance of AsyncHttpProviderConfig - */ - public Object getProperty(String name) { - return properties.get(name); - } - - /** - * Remove the value associated with the property's name - * - * @param name - * @return true if removed - */ - public Object removeProperty(String name) { - return properties.remove(name); - } - - /** - * Return the curent entry set. - * - * @return a the curent entry set. - */ - public Set> propertiesSet() { - return properties.entrySet(); - } - - public boolean isUseBlockingIO() { - return useBlockingIO; - } - - public void setUseBlockingIO(boolean useBlockingIO) { - this.useBlockingIO = useBlockingIO; - } - - public EventLoopGroup getEventLoopGroup() { - return eventLoopGroup; - } - - public void setEventLoopGroup(EventLoopGroup eventLoopGroup) { - this.eventLoopGroup = eventLoopGroup; - } - - public boolean isAsyncConnect() { - return asyncConnect; - } - - public void setAsyncConnect(boolean asyncConnect) { - this.asyncConnect = asyncConnect; - } - - public int getMaxInitialLineLength() { - return maxInitialLineLength; - } - - public void setMaxInitialLineLength(int maxInitialLineLength) { - this.maxInitialLineLength = maxInitialLineLength; - } - - public int getMaxHeaderSize() { - return maxHeaderSize; - } - - public void setMaxHeaderSize(int maxHeaderSize) { - this.maxHeaderSize = maxHeaderSize; - } - - public int getMaxChunkSize() { - return maxChunkSize; - } - - public void setMaxChunkSize(int maxChunkSize) { - this.maxChunkSize = maxChunkSize; - } - - public AdditionalChannelInitializer getHttpAdditionalChannelInitializer() { - return httpAdditionalChannelInitializer; - } - - public void setHttpAdditionalChannelInitializer(AdditionalChannelInitializer httpAdditionalChannelInitializer) { - this.httpAdditionalChannelInitializer = httpAdditionalChannelInitializer; - } - - public AdditionalChannelInitializer getWsAdditionalChannelInitializer() { - return wsAdditionalChannelInitializer; - } - - public void setWsAdditionalChannelInitializer(AdditionalChannelInitializer wsAdditionalChannelInitializer) { - this.wsAdditionalChannelInitializer = wsAdditionalChannelInitializer; - } - - public AdditionalChannelInitializer getHttpsAdditionalChannelInitializer() { - return httpsAdditionalChannelInitializer; - } - - public void setHttpsAdditionalChannelInitializer(AdditionalChannelInitializer httpsAdditionalChannelInitializer) { - this.httpsAdditionalChannelInitializer = httpsAdditionalChannelInitializer; - } - - public AdditionalChannelInitializer getWssAdditionalChannelInitializer() { - return wssAdditionalChannelInitializer; - } - - public void setWssAdditionalChannelInitializer(AdditionalChannelInitializer wssAdditionalChannelInitializer) { - this.wssAdditionalChannelInitializer = wssAdditionalChannelInitializer; - } - - public static interface AdditionalChannelInitializer { - - void initChannel(Channel ch) throws Exception; - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyChannelHandler.java b/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyChannelHandler.java deleted file mode 100644 index 484a8b694b..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/NettyChannelHandler.java +++ /dev/null @@ -1,873 +0,0 @@ -package org.asynchttpclient.providers.netty4; - -import static io.netty.handler.codec.http.HttpResponseStatus.*; -import static org.asynchttpclient.providers.netty4.util.HttpUtil.*; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandler.Sharable; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.handler.codec.PrematureChannelClosureException; -import io.netty.handler.codec.http.HttpClientCodec; -import io.netty.handler.codec.http.HttpContent; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; -import io.netty.handler.codec.http.LastHttpContent; -import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; -import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; -import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; -import io.netty.handler.codec.http.websocketx.WebSocketFrame; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.nio.channels.ClosedChannelException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map.Entry; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHandler.STATE; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Cookie; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.MaxRedirectException; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterException; -import org.asynchttpclient.filter.IOExceptionFilter; -import org.asynchttpclient.filter.ResponseFilter; -import org.asynchttpclient.ntlm.NTLMEngine; -import org.asynchttpclient.ntlm.NTLMEngineException; -import org.asynchttpclient.org.jboss.netty.handler.codec.http.CookieDecoder; -import org.asynchttpclient.providers.netty4.spnego.SpnegoEngine; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.asynchttpclient.websocket.WebSocketUpgradeHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Sharable -public class NettyChannelHandler extends ChannelInboundHandlerAdapter { - - private static final Logger LOGGER = LoggerFactory.getLogger(NettyChannelHandler.class); - - private final AsyncHttpClientConfig config; - private final NettyRequestSender requestSender; - private final Channels channels; - private final AtomicBoolean closed; - private final Protocol httpProtocol = new HttpProtocol(); - private final Protocol webSocketProtocol = new WebSocketProtocol(); - - public NettyChannelHandler(AsyncHttpClientConfig config, NettyRequestSender requestSender, Channels channels, AtomicBoolean isClose) { - this.config = config; - this.requestSender = requestSender; - this.channels = channels; - this.closed = isClose; - } - - @Override - public void channelRead(final ChannelHandlerContext ctx, Object e) throws Exception { - - Object attribute = Channels.getDefaultAttribute(ctx); - - if (attribute instanceof Callback) { - Callback ac = (Callback) attribute; - if (e instanceof LastHttpContent || !(e instanceof HttpContent)) { - ac.call(); - Channels.setDefaultAttribute(ctx, DiscardEvent.INSTANCE); - } - - } else if (attribute instanceof NettyResponseFuture) { - Protocol p = (ctx.pipeline().get(HttpClientCodec.class) != null ? httpProtocol : webSocketProtocol); - NettyResponseFuture future = (NettyResponseFuture) attribute; - - p.handle(ctx, future, e); - - } else if (attribute != DiscardEvent.INSTANCE) { - try { - LOGGER.trace("Closing an orphan channel {}", ctx.channel()); - ctx.channel().close(); - } catch (Throwable t) { - } - } - } - - public void channelInactive(ChannelHandlerContext ctx) throws Exception { - - if (closed.get()) { - return; - } - - try { - super.channelInactive(ctx); - } catch (Exception ex) { - LOGGER.trace("super.channelClosed", ex); - } - - channels.removeFromPool(ctx); - Object attachment = Channels.getDefaultAttribute(ctx); - LOGGER.debug("Channel Closed: {} with attachment {}", ctx.channel(), attachment); - - if (attachment instanceof Callback) { - Callback callback = (Callback) attachment; - Channels.setDefaultAttribute(ctx, callback.future()); - callback.call(); - - } else if (attachment instanceof NettyResponseFuture) { - NettyResponseFuture future = NettyResponseFuture.class.cast(attachment); - future.touch(); - - if (!config.getIOExceptionFilters().isEmpty() && applyIoExceptionFiltersAndReplayRequest(ctx, future, new IOException("Channel Closed"))) { - return; - } - - Protocol p = (ctx.pipeline().get(HttpClientCodec.class) != null ? httpProtocol : webSocketProtocol); - p.onClose(ctx); - - if (future != null && !future.isDone() && !future.isCancelled()) { - if (!requestSender.retry(ctx.channel(), future)) { - channels.abort(future, new IOException("Remotely Closed")); - } - } else { - channels.closeChannel(ctx); - } - } - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception { - Channel channel = ctx.channel(); - Throwable cause = e.getCause() != null ? e.getCause() : e; - NettyResponseFuture future = null; - - if (cause instanceof PrematureChannelClosureException) { - return; - } - - LOGGER.debug("Unexpected I/O exception on channel {}", channel, cause); - - try { - if (cause instanceof ClosedChannelException) { - return; - } - - Object attribute = Channels.getDefaultAttribute(ctx); - if (attribute instanceof NettyResponseFuture) { - future = (NettyResponseFuture) attribute; - future.attachChannel(null, false); - future.touch(); - - if (cause instanceof IOException) { - - // FIXME why drop the original exception and create a new - // one? - if (!config.getIOExceptionFilters().isEmpty()) { - if (applyIoExceptionFiltersAndReplayRequest(ctx, future, new IOException("Channel Closed"))) { - return; - } - } else { - // Close the channel so the recovering can occurs. - try { - ctx.channel().close(); - } catch (Throwable t) { - // Swallow. - } - return; - } - } - - if (NettyResponseFutures.abortOnReadCloseException(cause) || NettyResponseFutures.abortOnWriteCloseException(cause)) { - LOGGER.debug("Trying to recover from dead Channel: {}", channel); - return; - } - } else if (attribute instanceof Callback) { - future = Callback.class.cast(attribute).future(); - } - } catch (Throwable t) { - cause = t; - } - - if (future != null) { - try { - LOGGER.debug("Was unable to recover Future: {}", future); - channels.abort(future, cause); - } catch (Throwable t) { - LOGGER.error(t.getMessage(), t); - } - } - - Protocol protocol = ctx.pipeline().get(HttpClientCodec.class) != null ? httpProtocol : webSocketProtocol; - protocol.onError(ctx, e); - - channels.closeChannel(ctx); - // FIXME not really sure - // ctx.fireChannelRead(e); - ctx.close(); - } - - private boolean applyIoExceptionFiltersAndReplayRequest(ChannelHandlerContext ctx, NettyResponseFuture future, IOException e) throws IOException { - - boolean replayed = false; - - FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(future.getAsyncHandler()).request(future.getRequest()).ioException(e).build(); - for (IOExceptionFilter asyncFilter : config.getIOExceptionFilters()) { - try { - fc = asyncFilter.filter(fc); - if (fc == null) { - throw new NullPointerException("FilterContext is null"); - } - } catch (FilterException efe) { - channels.abort(future, efe); - } - } - - if (fc.replayRequest()) { - requestSender.replayRequest(future, fc, ctx); - replayed = true; - } - return replayed; - } - - private boolean redirect(Request request, NettyResponseFuture future, HttpResponse response, final ChannelHandlerContext ctx) throws Exception { - - io.netty.handler.codec.http.HttpResponseStatus status = response.getStatus(); - boolean redirectEnabled = request.isRedirectOverrideSet() ? request.isRedirectEnabled() : config.isRedirectEnabled(); - boolean isRedirectStatus = status.equals(MOVED_PERMANENTLY) || status.equals(FOUND) || status.equals(SEE_OTHER) || status.equals(TEMPORARY_REDIRECT); - if (redirectEnabled && isRedirectStatus) { - - if (future.incrementAndGetCurrentRedirectCount() < config.getMaxRedirects()) { - // We must allow 401 handling again. - future.getAndSetAuth(false); - - String location = response.headers().get(HttpHeaders.Names.LOCATION); - URI uri = AsyncHttpProviderUtils.getRedirectUri(future.getURI(), location); - - if (!uri.toString().equals(future.getURI().toString())) { - final RequestBuilder nBuilder = new RequestBuilder(future.getRequest()); - if (config.isRemoveQueryParamOnRedirect()) { - nBuilder.setQueryParameters(null); - } - - // FIXME why not do that for 301 and 307 too? - if ((status.equals(FOUND) || status.equals(SEE_OTHER)) && !(status.equals(FOUND) && config.isStrict302Handling())) { - nBuilder.setMethod(HttpMethod.GET.name()); - } - - // in case of a redirect from HTTP to HTTPS, future attributes might change - final boolean initialConnectionKeepAlive = future.isKeepAlive(); - final String initialPoolKey = channels.getPoolKey(future); - - future.setURI(uri); - String newUrl = uri.toString(); - if (request.getUrl().startsWith(WEBSOCKET)) { - newUrl = newUrl.replace(HTTP, WEBSOCKET); - } - LOGGER.debug("Redirecting to {}", newUrl); - - for (String cookieStr : future.getHttpResponse().headers().getAll(HttpHeaders.Names.SET_COOKIE)) { - for (Cookie c : CookieDecoder.decode(cookieStr)) { - nBuilder.addOrReplaceCookie(c); - } - } - - for (String cookieStr : future.getHttpResponse().headers().getAll(HttpHeaders.Names.SET_COOKIE2)) { - for (Cookie c : CookieDecoder.decode(cookieStr)) { - nBuilder.addOrReplaceCookie(c); - } - } - - Callback callback = new Callback(future) { - public void call() throws Exception { - if (!(initialConnectionKeepAlive && ctx.channel().isActive() && channels.offerToPool(initialPoolKey, ctx.channel()))) { - channels.finishChannel(ctx); - } - } - }; - - if (HttpHeaders.isTransferEncodingChunked(response)) { - // We must make sure there is no bytes left before - // executing the next request. - // FIXME investigate this - Channels.setDefaultAttribute(ctx, callback); - } else { - // FIXME don't understand: this offers the connection to the pool, or even closes it, while the request has not been sent, right? - callback.call(); - } - - Request target = nBuilder.setUrl(newUrl).build(); - future.setRequest(target); - requestSender.execute(target, future); - return true; - } - } else { - throw new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects()); - } - } - return false; - } - - private final class HttpProtocol implements Protocol { - - private Realm kerberosChallenge(List proxyAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, - NettyResponseFuture future) throws NTLMEngineException { - - URI uri = request.getURI(); - String host = request.getVirtualHost() == null ? AsyncHttpProviderUtils.getHost(uri) : request.getVirtualHost(); - String server = proxyServer == null ? host : proxyServer.getHost(); - try { - String challengeHeader = SpnegoEngine.instance().generateToken(server); - headers.remove(HttpHeaders.Names.AUTHORIZATION); - headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader); - - Realm.RealmBuilder realmBuilder; - if (realm != null) { - realmBuilder = new Realm.RealmBuilder().clone(realm); - } else { - realmBuilder = new Realm.RealmBuilder(); - } - return realmBuilder.setUri(uri.getRawPath()).setMethodName(request.getMethod()).setScheme(Realm.AuthScheme.KERBEROS).build(); - } catch (Throwable throwable) { - if (isNTLM(proxyAuth)) { - return ntlmChallenge(proxyAuth, request, proxyServer, headers, realm, future); - } - channels.abort(future, throwable); - return null; - } - } - - private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, - NettyResponseFuture future) throws NTLMEngineException { - - boolean useRealm = (proxyServer == null && realm != null); - - String ntlmDomain = useRealm ? realm.getNtlmDomain() : proxyServer.getNtlmDomain(); - String ntlmHost = useRealm ? realm.getNtlmHost() : proxyServer.getHost(); - String principal = useRealm ? realm.getPrincipal() : proxyServer.getPrincipal(); - String password = useRealm ? realm.getPassword() : proxyServer.getPassword(); - - Realm newRealm; - if (realm != null && !realm.isNtlmMessageType2Received()) { - String challengeHeader = NTLMEngine.INSTANCE.generateType1Msg(ntlmDomain, ntlmHost); - - URI uri = request.getURI(); - headers.add(HttpHeaders.Names.AUTHORIZATION, "NTLM " + challengeHeader); - newRealm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(uri.getRawPath()).setMethodName(request.getMethod()) - .setNtlmMessageType2Received(true).build(); - future.getAndSetAuth(false); - } else { - addType3NTLMAuthorizationHeader(wwwAuth, headers, principal, password, ntlmDomain, ntlmHost); - - Realm.RealmBuilder realmBuilder; - Realm.AuthScheme authScheme; - if (realm != null) { - realmBuilder = new Realm.RealmBuilder().clone(realm); - authScheme = realm.getAuthScheme(); - } else { - realmBuilder = new Realm.RealmBuilder(); - authScheme = Realm.AuthScheme.NTLM; - } - newRealm = realmBuilder.setScheme(authScheme).setUri(request.getURI().getPath()).setMethodName(request.getMethod()).build(); - } - - return newRealm; - } - - private Realm ntlmProxyChallenge(List wwwAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, - NettyResponseFuture future) throws NTLMEngineException { - future.getAndSetAuth(false); - headers.remove(HttpHeaders.Names.PROXY_AUTHORIZATION); - - addType3NTLMAuthorizationHeader(wwwAuth, headers, proxyServer.getPrincipal(), proxyServer.getPassword(), proxyServer.getNtlmDomain(), proxyServer.getHost()); - - Realm newRealm; - Realm.RealmBuilder realmBuilder; - if (realm != null) { - realmBuilder = new Realm.RealmBuilder().clone(realm); - } else { - realmBuilder = new Realm.RealmBuilder(); - } - newRealm = realmBuilder// .setScheme(realm.getAuthScheme()) - .setUri(request.getURI().getPath()).setMethodName(request.getMethod()).build(); - - return newRealm; - } - - private void addType3NTLMAuthorizationHeader(List auth, FluentCaseInsensitiveStringsMap headers, String username, String password, String domain, String workstation) - throws NTLMEngineException { - headers.remove(HttpHeaders.Names.AUTHORIZATION); - - if (isNTLM(auth)) { - String serverChallenge = auth.get(0).trim().substring("NTLM ".length()); - String challengeHeader = NTLMEngine.INSTANCE.generateType3Msg(username, password, domain, workstation, serverChallenge); - - headers.add(HttpHeaders.Names.AUTHORIZATION, "NTLM " + challengeHeader); - } - } - - private List getAuthorizationToken(Iterable> list, String headerAuth) { - ArrayList l = new ArrayList(); - for (Entry e : list) { - if (e.getKey().equalsIgnoreCase(headerAuth)) { - l.add(e.getValue().trim()); - } - } - return l; - } - - private void finishUpdate(final NettyResponseFuture future, final ChannelHandlerContext ctx, boolean lastValidChunk) throws IOException { - if (lastValidChunk && future.isKeepAlive()) { - channels.drainChannel(ctx, future); - } else { - if (future.isKeepAlive() && ctx.channel().isActive() && channels.offerToPool(channels.getPoolKey(future), ctx.channel())) { - markAsDone(future, ctx); - return; - } - channels.finishChannel(ctx); - } - markAsDone(future, ctx); - } - - private final boolean updateBodyAndInterrupt(final NettyResponseFuture future, AsyncHandler handler, HttpResponseBodyPart c) throws Exception { - boolean state = handler.onBodyPartReceived(c) != STATE.CONTINUE; - if (c.closeUnderlyingConnection()) { - future.setKeepAlive(false); - } - return state; - } - - private void markAsDone(final NettyResponseFuture future, final ChannelHandlerContext ctx) throws MalformedURLException { - // We need to make sure everything is OK before adding the - // connection back to the pool. - try { - future.done(); - } catch (Throwable t) { - // Never propagate exception once we know we are done. - LOGGER.debug(t.getMessage(), t); - } - - if (!future.isKeepAlive() || !ctx.channel().isActive()) { - channels.closeChannel(ctx); - } - } - - private boolean applyResponseFiltersAndReplayRequest(ChannelHandlerContext ctx, NettyResponseFuture future, HttpResponseStatus status, HttpResponseHeaders responseHeaders) - throws IOException { - - boolean replayed = false; - - AsyncHandler handler = future.getAsyncHandler(); - FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(handler).request(future.getRequest()).responseStatus(status).responseHeaders(responseHeaders) - .build(); - - for (ResponseFilter asyncFilter : config.getResponseFilters()) { - try { - fc = asyncFilter.filter(fc); - // FIXME Is it work protecting against this? - if (fc == null) { - throw new NullPointerException("FilterContext is null"); - } - } catch (FilterException efe) { - channels.abort(future, efe); - } - } - - // The handler may have been wrapped. - handler = fc.getAsyncHandler(); - future.setAsyncHandler(handler); - - // The request has changed - if (fc.replayRequest()) { - requestSender.replayRequest(future, fc, ctx); - replayed = true; - } - return replayed; - } - - private boolean handleResponseAndExit(final ChannelHandlerContext ctx, final NettyResponseFuture future, AsyncHandler handler, HttpRequest nettyRequest, - ProxyServer proxyServer, HttpResponse response) throws Exception { - Request request = future.getRequest(); - int statusCode = response.getStatus().code(); - HttpResponseStatus status = new ResponseStatus(future.getURI(), response); - HttpResponseHeaders responseHeaders = new ResponseHeaders(future.getURI(), response.headers()); - final FluentCaseInsensitiveStringsMap headers = request.getHeaders(); - final RequestBuilder builder = new RequestBuilder(future.getRequest()); - Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - - // store the original headers so we can re-send all them to - // the handler in case of trailing headers - future.setHttpResponse(response); - - future.setKeepAlive(!HttpHeaders.Values.CLOSE.equalsIgnoreCase(response.headers().get(HttpHeaders.Names.CONNECTION))); - - if (!config.getResponseFilters().isEmpty() && applyResponseFiltersAndReplayRequest(ctx, future, status, responseHeaders)) { - return true; - } - - // FIXME handle without returns - if (statusCode == UNAUTHORIZED.code() && realm != null) { - List wwwAuth = getAuthorizationToken(response.headers(), HttpHeaders.Names.WWW_AUTHENTICATE); - if (!wwwAuth.isEmpty() && !future.getAndSetAuth(true)) { - future.setState(NettyResponseFuture.STATE.NEW); - Realm newRealm = null; - // NTLM - boolean negociate = wwwAuth.contains("Negotiate"); - if (!wwwAuth.contains("Kerberos") && (isNTLM(wwwAuth) || negociate)) { - newRealm = ntlmChallenge(wwwAuth, request, proxyServer, headers, realm, future); - // SPNEGO KERBEROS - } else if (negociate) { - newRealm = kerberosChallenge(wwwAuth, request, proxyServer, headers, realm, future); - if (newRealm == null) { - return true; - } - } else { - newRealm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(request.getURI().getPath()).setMethodName(request.getMethod()) - .setUsePreemptiveAuth(true).parseWWWAuthenticateHeader(wwwAuth.get(0)).build(); - } - - final Realm nr = new Realm.RealmBuilder().clone(newRealm).setUri(URI.create(request.getUrl()).getPath()).build(); - - LOGGER.debug("Sending authentication to {}", request.getUrl()); - Callback callback = new Callback(future) { - public void call() throws Exception { - channels.drainChannel(ctx, future); - requestSender.execute(builder.setHeaders(headers).setRealm(nr).build(), future); - } - }; - - if (future.isKeepAlive() && HttpHeaders.isTransferEncodingChunked(response)) { - // We must make sure there is no bytes left - // before executing the next request. - Channels.setDefaultAttribute(ctx, callback); - } else { - callback.call(); - } - - return true; - } - - } else if (statusCode == CONTINUE.code()) { - future.getAndSetWriteHeaders(false); - future.getAndSetWriteBody(true); - // FIXME is this necessary - requestSender.writeRequest(ctx.channel(), config, future); - return true; - - } else if (statusCode == PROXY_AUTHENTICATION_REQUIRED.code()) { - List proxyAuth = getAuthorizationToken(response.headers(), HttpHeaders.Names.PROXY_AUTHENTICATE); - if (realm != null && !proxyAuth.isEmpty() && !future.getAndSetAuth(true)) { - LOGGER.debug("Sending proxy authentication to {}", request.getUrl()); - - future.setState(NettyResponseFuture.STATE.NEW); - Realm newRealm = null; - - boolean negociate = proxyAuth.contains("Negotiate"); - if (!proxyAuth.contains("Kerberos") && (isNTLM(proxyAuth) || negociate)) { - newRealm = ntlmProxyChallenge(proxyAuth, request, proxyServer, headers, realm, future); - // SPNEGO KERBEROS - } else if (negociate) { - newRealm = kerberosChallenge(proxyAuth, request, proxyServer, headers, realm, future); - if (newRealm == null) { - return true; - } - } else { - newRealm = future.getRequest().getRealm(); - } - - future.setReuseChannel(true); - future.setConnectAllowed(true); - requestSender.execute(builder.setHeaders(headers).setRealm(newRealm).build(), future); - return true; - } - - } else if (statusCode == OK.code() && nettyRequest.getMethod() == HttpMethod.CONNECT) { - - LOGGER.debug("Connected to {}:{}", proxyServer.getHost(), proxyServer.getPort()); - - if (future.isKeepAlive()) { - future.attachChannel(ctx.channel(), true); - } - - try { - LOGGER.debug("Connecting to proxy {} for scheme {}", proxyServer, request.getUrl()); - channels.upgradeProtocol(ctx.channel().pipeline(), request.getURI().getScheme()); - } catch (Throwable ex) { - channels.abort(future, ex); - } - future.setReuseChannel(true); - future.setConnectAllowed(false); - requestSender.execute(builder.build(), future); - return true; - - } - - if (redirect(request, future, response, ctx)) { - return true; - } - - if (!future.getAndSetStatusReceived(true) && (handler.onStatusReceived(status) != STATE.CONTINUE || handler.onHeadersReceived(responseHeaders) != STATE.CONTINUE)) { - finishUpdate(future, ctx, HttpHeaders.isTransferEncodingChunked(response)); - return true; - } - - return false; - } - - @Override - public void handle(final ChannelHandlerContext ctx, final NettyResponseFuture future, final Object e) throws Exception { - future.touch(); - - // The connect timeout occurred. - if (future.isCancelled() || future.isDone()) { - channels.finishChannel(ctx); - return; - } - - HttpRequest nettyRequest = future.getNettyRequest(); - AsyncHandler handler = future.getAsyncHandler(); - Request request = future.getRequest(); - ProxyServer proxyServer = future.getProxyServer(); - try { - if (e instanceof HttpResponse) { - HttpResponse response = (HttpResponse) e; - LOGGER.debug("\n\nRequest {}\n\nResponse {}\n", nettyRequest, response); - future.getPendingResponse().set(response); - return; - } - - if (e instanceof HttpContent) { - - AtomicReference responseRef = future.getPendingResponse(); - HttpResponse response = responseRef.getAndSet(null); - if (handler != null) { - if (response != null && handleResponseAndExit(ctx, future, handler, nettyRequest, proxyServer, response)) { - return; - } - - HttpContent chunk = (HttpContent) e; - - boolean interrupt = false; - boolean last = chunk instanceof LastHttpContent; - - // FIXME - // Netty 3 provider is broken: in case of trailing headers, - // onHeadersReceived should be called before - // updateBodyAndInterrupt - if (last) { - LastHttpContent lastChunk = (LastHttpContent) chunk; - HttpHeaders trailingHeaders = lastChunk.trailingHeaders(); - if (!trailingHeaders.isEmpty()) { - interrupt = handler.onHeadersReceived(new ResponseHeaders(future.getURI(), future.getHttpResponse().headers(), trailingHeaders)) != STATE.CONTINUE; - } - } - - if (!interrupt && chunk.content().readableBytes() > 0) { - // FIXME why - interrupt = updateBodyAndInterrupt(future, handler, new ResponseBodyPart(future.getURI(), chunk.content(), last)); - } - - if (interrupt || last) { - finishUpdate(future, ctx, !last); - } - } - } - } catch (Exception t) { - if (t instanceof IOException && !config.getIOExceptionFilters().isEmpty() && applyIoExceptionFiltersAndReplayRequest(ctx, future, IOException.class.cast(t))) { - return; - } - - try { - channels.abort(future, t); - } finally { - finishUpdate(future, ctx, false); - throw t; - } - } - } - - @Override - public void onError(ChannelHandlerContext ctx, Throwable error) { - } - - @Override - public void onClose(ChannelHandlerContext ctx) { - } - } - - private final class WebSocketProtocol implements Protocol { - - private static final byte OPCODE_TEXT = 0x1; - private static final byte OPCODE_BINARY = 0x2; - private static final byte OPCODE_UNKNOWN = -1; - protected byte pendingOpcode = OPCODE_UNKNOWN; - - // We don't need to synchronize as replacing the "ws-decoder" will - // process using the same thread. - private void invokeOnSucces(ChannelHandlerContext ctx, WebSocketUpgradeHandler h) { - if (!h.touchSuccess()) { - try { - h.onSuccess(new NettyWebSocket(ctx.channel())); - } catch (Exception ex) { - LOGGER.warn("onSuccess unexpected exception", ex); - } - } - } - - @Override - public void handle(ChannelHandlerContext ctx, NettyResponseFuture future, Object e) throws Exception { - WebSocketUpgradeHandler h = WebSocketUpgradeHandler.class.cast(future.getAsyncHandler()); - Request request = future.getRequest(); - - if (e instanceof HttpResponse) { - HttpResponse response = (HttpResponse) e; - - HttpResponseStatus s = new ResponseStatus(future.getURI(), response); - HttpResponseHeaders responseHeaders = new ResponseHeaders(future.getURI(), response.headers()); - - // FIXME there's a method for that IIRC - FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(h).request(request).responseStatus(s).responseHeaders(responseHeaders).build(); - for (ResponseFilter asyncFilter : config.getResponseFilters()) { - try { - fc = asyncFilter.filter(fc); - if (fc == null) { - throw new NullPointerException("FilterContext is null"); - } - } catch (FilterException efe) { - channels.abort(future, efe); - } - } - - // The handler may have been wrapped. - future.setAsyncHandler(fc.getAsyncHandler()); - - // The request has changed - if (fc.replayRequest()) { - requestSender.replayRequest(future, fc, ctx); - return; - } - - future.setHttpResponse(response); - if (redirect(request, future, response, ctx)) - return; - - boolean validStatus = response.getStatus().equals(SWITCHING_PROTOCOLS); - boolean validUpgrade = response.headers().get(HttpHeaders.Names.UPGRADE) != null; - String c = response.headers().get(HttpHeaders.Names.CONNECTION); - if (c == null) { - c = response.headers().get(HttpHeaders.Names.CONNECTION.toLowerCase()); - } - - boolean validConnection = c == null ? false : c.equalsIgnoreCase(HttpHeaders.Values.UPGRADE); - - s = new ResponseStatus(future.getURI(), response); - final boolean statusReceived = h.onStatusReceived(s) == STATE.UPGRADE; - - final boolean headerOK = h.onHeadersReceived(responseHeaders) == STATE.CONTINUE; - if (!headerOK || !validStatus || !validUpgrade || !validConnection || !statusReceived) { - channels.abort(future, new IOException("Invalid handshake response")); - return; - } - - String accept = response.headers().get(HttpHeaders.Names.SEC_WEBSOCKET_ACCEPT); - String key = WebSocketUtil.getAcceptKey(future.getNettyRequest().headers().get(HttpHeaders.Names.SEC_WEBSOCKET_KEY)); - if (accept == null || !accept.equals(key)) { - throw new IOException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, key)); - } - - Channels.upgradePipelineForWebSockets(ctx); - - invokeOnSucces(ctx, h); - future.done(); - - } else if (e instanceof WebSocketFrame) { - - final WebSocketFrame frame = (WebSocketFrame) e; - NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted()); - invokeOnSucces(ctx, h); - - if (webSocket != null) { - if (frame instanceof CloseWebSocketFrame) { - Channels.setDefaultAttribute(ctx, DiscardEvent.INSTANCE); - CloseWebSocketFrame closeFrame = CloseWebSocketFrame.class.cast(frame); - webSocket.onClose(closeFrame.statusCode(), closeFrame.reasonText()); - } else { - if (frame instanceof TextWebSocketFrame) { - pendingOpcode = OPCODE_TEXT; - } else if (frame instanceof BinaryWebSocketFrame) { - pendingOpcode = OPCODE_BINARY; - } - - if (frame.content() != null && frame.content().readableBytes() > 0) { - ResponseBodyPart rp = new ResponseBodyPart(future.getURI(), frame.content(), frame.isFinalFragment()); - h.onBodyPartReceived(rp); - - if (pendingOpcode == OPCODE_BINARY) { - webSocket.onBinaryFragment(rp.getBodyPartBytes(), frame.isFinalFragment()); - } else { - webSocket.onTextFragment(frame.content().toString(Constants.UTF8), frame.isFinalFragment()); - } - } - } - } else { - LOGGER.debug("UpgradeHandler returned a null NettyWebSocket "); - } - } else if (e instanceof LastHttpContent) { - // FIXME what to do with this kind of messages? - } else { - LOGGER.error("Invalid message {}", e); - } - } - - @Override - public void onError(ChannelHandlerContext ctx, Throwable e) { - try { - Object attribute = Channels.getDefaultAttribute(ctx); - LOGGER.warn("onError {}", e); - if (!(attribute instanceof NettyResponseFuture)) { - return; - } - - NettyResponseFuture nettyResponse = (NettyResponseFuture) attribute; - WebSocketUpgradeHandler h = WebSocketUpgradeHandler.class.cast(nettyResponse.getAsyncHandler()); - - NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted()); - if (webSocket != null) { - webSocket.onError(e.getCause()); - webSocket.close(); - } - } catch (Throwable t) { - LOGGER.error("onError", t); - } - } - - @Override - public void onClose(ChannelHandlerContext ctx) { - LOGGER.trace("onClose {}"); - Object attribute = Channels.getDefaultAttribute(ctx); - if (!(attribute instanceof NettyResponseFuture)) { - return; - } - - try { - NettyResponseFuture nettyResponse = NettyResponseFuture.class.cast(attribute); - WebSocketUpgradeHandler h = WebSocketUpgradeHandler.class.cast(nettyResponse.getAsyncHandler()); - NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted()); - - // FIXME How could this test not succeed, attachment is a - // NettyResponseFuture???? - if (attribute != DiscardEvent.INSTANCE) - webSocket.close(1006, "Connection was closed abnormally (that is, with no close frame being sent)."); - } catch (Throwable t) { - LOGGER.error("onError", t); - } - } - } -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/OptimizedFileRegion.java b/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/OptimizedFileRegion.java deleted file mode 100644 index ee7f9f98fb..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/OptimizedFileRegion.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.asynchttpclient.providers.netty4; - -import io.netty.channel.FileRegion; -import io.netty.util.AbstractReferenceCounted; - -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; -import java.nio.channels.WritableByteChannel; - -public class OptimizedFileRegion extends AbstractReferenceCounted implements FileRegion { - - private final FileChannel file; - private final RandomAccessFile raf; - private final long position; - private final long count; - private long byteWritten; - - public OptimizedFileRegion(RandomAccessFile raf, long position, long count) { - this.raf = raf; - this.file = raf.getChannel(); - this.position = position; - this.count = count; - } - - public long position() { - return position; - } - - public long count() { - return count; - } - - public long transfered() { - return byteWritten; - } - - public long transferTo(WritableByteChannel target, long position) throws IOException { - long count = this.count - position; - if (count < 0 || position < 0) { - throw new IllegalArgumentException("position out of range: " + position + " (expected: 0 - " + (this.count - 1) + ")"); - } - if (count == 0) { - return 0L; - } - - long bw = file.transferTo(this.position + position, count, target); - byteWritten += bw; - if (byteWritten == raf.length()) { - deallocate(); - } - return bw; - } - - public void deallocate() { - try { - file.close(); - } catch (IOException e) { - NettyAsyncHttpProvider.LOGGER.warn("Failed to close a file.", e); - } - - try { - raf.close(); - } catch (IOException e) { - NettyAsyncHttpProvider.LOGGER.warn("Failed to close a file.", e); - } - } -} \ No newline at end of file diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/Protocol.java b/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/Protocol.java deleted file mode 100644 index 57445877e8..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/Protocol.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import io.netty.channel.ChannelHandlerContext; - -public interface Protocol{ - - void handle(ChannelHandlerContext ctx, NettyResponseFuture future, Object message) throws Exception; - - void onError(ChannelHandlerContext ctx, Throwable error); - - void onClose(ChannelHandlerContext ctx); -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/ThreadLocalBoolean.java b/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/ThreadLocalBoolean.java deleted file mode 100644 index 90b67893ee..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/ThreadLocalBoolean.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.asynchttpclient.providers.netty4; - -public class ThreadLocalBoolean extends ThreadLocal { - - private final boolean defaultValue; - - public ThreadLocalBoolean() { - this(false); - } - - public ThreadLocalBoolean(boolean defaultValue) { - this.defaultValue = defaultValue; - } - - @Override - protected Boolean initialValue() { - return defaultValue ? Boolean.TRUE : Boolean.FALSE; - } -} \ No newline at end of file diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/spnego/SpnegoTokenGenerator.java b/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/spnego/SpnegoTokenGenerator.java deleted file mode 100644 index be2d720842..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/spnego/SpnegoTokenGenerator.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -/* - * ==================================================================== - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package org.asynchttpclient.providers.netty4.spnego; - -import java.io.IOException; - -/** - * Abstract SPNEGO token generator. Implementations should take an Kerberos ticket and transform - * into a SPNEGO token. - *

- * Implementations of this interface are expected to be thread-safe. - * - * @since 4.1 - */ -public interface SpnegoTokenGenerator { - - byte[] generateSpnegoDERObject(byte[] kerberosTicket) throws IOException; - -} diff --git a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/util/CleanupChannelGroup.java b/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/util/CleanupChannelGroup.java deleted file mode 100644 index 2de4e4d7aa..0000000000 --- a/providers/netty4/src/main/java/org/asynchttpclient/providers/netty4/util/CleanupChannelGroup.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -/* - * Copyright 2010 Bruno de Carvalho - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.asynchttpclient.providers.netty4.util; - -import io.netty.channel.Channel; -//import io.netty.channel.ChannelFuture; -//import io.netty.channel.group.ChannelGroup; -import io.netty.channel.group.ChannelGroupFuture; -import io.netty.channel.group.DefaultChannelGroup; -import io.netty.util.concurrent.GlobalEventExecutor; - -//import java.util.ArrayList; -//import java.util.Collection; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; - -/** - * Extension of {@link DefaultChannelGroup} that's used mainly as a cleanup container, where {@link #close()} is only - * supposed to be called once. - * - * @author Bruno de Carvalho - */ -public class CleanupChannelGroup extends DefaultChannelGroup { - -// private final static Logger logger = LoggerFactory.getLogger(CleanupChannelGroup.class); - - // internal vars -------------------------------------------------------------------------------------------------- - - private final AtomicBoolean closed = new AtomicBoolean(false); - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - - // constructors --------------------------------------------------------------------------------------------------- - - public CleanupChannelGroup() { - super(GlobalEventExecutor.INSTANCE); - } - - public CleanupChannelGroup(String name) { - super(name, GlobalEventExecutor.INSTANCE); - } - - // DefaultChannelGroup -------------------------------------------------------------------------------------------- - - @Override - public ChannelGroupFuture close() { - this.lock.writeLock().lock(); - try { - if (!this.closed.getAndSet(true)) { - // First time close() is called. - return super.close(); - } else { - // FIXME DefaultChannelGroupFuture is package protected -// Collection futures = new ArrayList(); -// logger.debug("CleanupChannelGroup already closed"); -// return new DefaultChannelGroupFuture(ChannelGroup.class.cast(this), futures, GlobalEventExecutor.INSTANCE); - throw new UnsupportedOperationException("CleanupChannelGroup already closed"); - } - } finally { - this.lock.writeLock().unlock(); - } - } - - @Override - public boolean add(Channel channel) { - // Synchronization must occur to avoid add() and close() overlap (thus potentially leaving one channel open). - // This could also be done by synchronizing the method itself but using a read lock here (rather than a - // synchronized() block) allows multiple concurrent calls to add(). - this.lock.readLock().lock(); - try { - if (this.closed.get()) { - // Immediately close channel, as close() was already called. - channel.close(); - return false; - } - - return super.add(channel); - } finally { - this.lock.readLock().unlock(); - } - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncHttpProviderTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncHttpProviderTest.java deleted file mode 100644 index 177e28611a..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncHttpProviderTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.AbstractBasicTest; - -public class NettyAsyncHttpProviderTest extends AbstractBasicTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncProviderBasicTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncProviderBasicTest.java deleted file mode 100644 index 113366e944..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncProviderBasicTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.providers.netty4.NettyAsyncHttpProviderConfig; -import org.testng.annotations.Test; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.asynchttpclient.async.AsyncProvidersBasicTest; - -@Test -public class NettyAsyncProviderBasicTest extends AsyncProvidersBasicTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - - @Override - protected AsyncHttpProviderConfig getProviderConfig() { - final NettyAsyncHttpProviderConfig config = new NettyAsyncHttpProviderConfig(); - config.addProperty("TCP_NODELAY", true); - return config; - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncProviderPipelineTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncProviderPipelineTest.java deleted file mode 100644 index c670f5dfa6..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncProviderPipelineTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ - -package org.asynchttpclient.providers.netty4; - -import static org.testng.Assert.*; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.handler.codec.http.HttpMessage; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.async.AbstractBasicTest; -import org.asynchttpclient.providers.netty4.NettyAsyncHttpProviderConfig.AdditionalChannelInitializer; -import org.testng.annotations.Test; - -public class NettyAsyncProviderPipelineTest extends AbstractBasicTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - - @Test(groups = { "standalone", "netty_provider" }) - public void asyncPipelineTest() throws Exception { - - NettyAsyncHttpProviderConfig nettyConfig = new NettyAsyncHttpProviderConfig(); - nettyConfig.setHttpAdditionalChannelInitializer(new AdditionalChannelInitializer() { - public void initChannel(Channel ch) throws Exception { - // super.initPlainChannel(ch); - ch.pipeline().addBefore("inflater", "copyEncodingHeader", new CopyEncodingHandler()); - } - }); - AsyncHttpClient p = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setCompressionEnabled(true).setAsyncHttpClientProviderConfig(nettyConfig).build()); - - try { - final CountDownLatch l = new CountDownLatch(1); - Request request = new RequestBuilder("GET").setUrl(getTargetUrl()).build(); - p.executeRequest(request, new AsyncCompletionHandlerAdapter() { - @Override - public Response onCompleted(Response response) throws Exception { - try { - assertEquals(response.getStatusCode(), 200); - assertEquals(response.getHeader("X-Original-Content-Encoding"), ""); - } finally { - l.countDown(); - } - return response; - } - }).get(); - if (!l.await(TIMEOUT, TimeUnit.SECONDS)) { - fail("Timeout out"); - } - } finally { - p.close(); - } - } - - private static class CopyEncodingHandler extends ChannelInboundHandlerAdapter { - @Override - public void channelRead(ChannelHandlerContext ctx, Object e) { - if (e instanceof HttpMessage) { - HttpMessage m = (HttpMessage) e; - // for test there is no Content-Encoding header so just hard - // coding value - // for verification - m.headers().set("X-Original-Content-Encoding", ""); - } - ctx.fireChannelRead(e); - } - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncResponseTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncResponseTest.java deleted file mode 100644 index 653a89ceef..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncResponseTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ - -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.Cookie; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.providers.netty4.NettyResponse; -import org.asynchttpclient.providers.netty4.ResponseStatus; -import org.testng.annotations.Test; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.TimeZone; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; - -/** - * @author Benjamin Hanzelmann - */ -public class NettyAsyncResponseTest { - - @Test(groups = "standalone") - public void testCookieParseExpires() { - // e.g. "Sun, 06-Feb-2012 03:45:24 GMT"; - SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss z", Locale.US); - sdf.setTimeZone(TimeZone.getTimeZone("GMT")); - - Date date = new Date(System.currentTimeMillis() + 60000); // sdf.parse( dateString ); - final String cookieDef = String.format("efmembercheck=true; expires=%s; path=/; domain=.eclipse.org", sdf.format(date)); - - NettyResponse - response = new NettyResponse(new ResponseStatus(null, null), new HttpResponseHeaders(null, null, false) { - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); - } - }, null); - - List cookies = response.getCookies(); - assertEquals(cookies.size(), 1); - - Cookie cookie = cookies.get(0); - assertTrue(cookie.getMaxAge() > 55 && cookie.getMaxAge() < 61, ""); - } - - @Test(groups = "standalone") - public void testCookieParseMaxAge() { - final String cookieDef = "efmembercheck=true; max-age=60; path=/; domain=.eclipse.org"; - NettyResponse response = new NettyResponse(new ResponseStatus(null, null), new HttpResponseHeaders(null, null, false) { - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); - } - }, null); - List cookies = response.getCookies(); - assertEquals(cookies.size(), 1); - - Cookie cookie = cookies.get(0); - assertEquals(cookie.getMaxAge(), 60); - } - - @Test(groups = "standalone") - public void testCookieParseWeirdExpiresValue() { - final String cookieDef = "efmembercheck=true; expires=60; path=/; domain=.eclipse.org"; - NettyResponse response = new NettyResponse(new ResponseStatus(null, null), new HttpResponseHeaders(null, null, false) { - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); - } - }, null); - - List cookies = response.getCookies(); - assertEquals(cookies.size(), 1); - - Cookie cookie = cookies.get(0); - assertEquals(cookie.getMaxAge(), 60); - } - -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncStreamHandlerTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncStreamHandlerTest.java deleted file mode 100644 index cd292b06c1..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncStreamHandlerTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.AsyncStreamHandlerTest; - -public class NettyAsyncStreamHandlerTest extends AsyncStreamHandlerTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncStreamLifecycleTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncStreamLifecycleTest.java deleted file mode 100644 index acba9b815f..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAsyncStreamLifecycleTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.AsyncStreamLifecycleTest; - -public class NettyAsyncStreamLifecycleTest extends AsyncStreamLifecycleTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAuthTimeoutTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAuthTimeoutTest.java deleted file mode 100644 index d49325760f..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyAuthTimeoutTest.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.AuthTimeoutTest; -import org.testng.annotations.Test; - -@Test -public class NettyAuthTimeoutTest extends AuthTimeoutTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyBasicAuthTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyBasicAuthTest.java deleted file mode 100644 index 971fe6b107..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyBasicAuthTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.BasicAuthTest; -import org.asynchttpclient.providers.netty4.NettyAsyncHttpProvider; - -public class NettyBasicAuthTest extends BasicAuthTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - - @Override - public String getProviderClass() { - return NettyAsyncHttpProvider.class.getName(); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyBasicHttpsTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyBasicHttpsTest.java deleted file mode 100644 index 31a20c5278..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyBasicHttpsTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.BasicHttpsTest; - -public class NettyBasicHttpsTest extends BasicHttpsTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyBodyChunkTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyBodyChunkTest.java deleted file mode 100644 index 4cd124390f..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyBodyChunkTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.BodyChunkTest; - -public class NettyBodyChunkTest extends BodyChunkTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyBodyDeferringAsyncHandlerTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyBodyDeferringAsyncHandlerTest.java deleted file mode 100644 index 0a8e231014..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyBodyDeferringAsyncHandlerTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.BodyDeferringAsyncHandlerTest; - -public class NettyBodyDeferringAsyncHandlerTest extends BodyDeferringAsyncHandlerTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyByteBufferCapacityTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyByteBufferCapacityTest.java deleted file mode 100644 index b87ff9d130..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyByteBufferCapacityTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ByteBufferCapacityTest; - -public class NettyByteBufferCapacityTest extends ByteBufferCapacityTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyChunkingTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyChunkingTest.java deleted file mode 100644 index bc36fc0546..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyChunkingTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ChunkingTest; - -public class NettyChunkingTest extends ChunkingTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyComplexClientTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyComplexClientTest.java deleted file mode 100644 index f36fa3f814..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyComplexClientTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ComplexClientTest; - -public class NettyComplexClientTest extends ComplexClientTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyConnectionPoolTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyConnectionPoolTest.java deleted file mode 100644 index 1db87da20b..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyConnectionPoolTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; - -import java.util.concurrent.TimeUnit; - -import io.netty.channel.Channel; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ConnectionsPool; -import org.asynchttpclient.async.ConnectionPoolTest; - -public class NettyConnectionPoolTest extends ConnectionPoolTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - - @Override - public void testInvalidConnectionsPool() { - ConnectionsPool cp = new ConnectionsPool() { - - public boolean offer(String key, Channel connection) { - return false; - } - - public Channel poll(String connection) { - return null; - } - - public boolean removeAll(Channel connection) { - return false; - } - - public boolean canCacheConnection() { - return false; - } - - public void destroy() { - - } - }; - - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionsPool(cp).build()); - try { - Exception exception = null; - try { - client.prepareGet(getTargetUrl()).execute().get(TIMEOUT, TimeUnit.SECONDS); - } catch (Exception ex) { - ex.printStackTrace(); - exception = ex; - } - assertNotNull(exception); - assertEquals(exception.getMessage(), "Too many connections -1"); - } finally { - client.close(); - } - } - - @Override - public void testValidConnectionsPool() { - ConnectionsPool cp = new ConnectionsPool() { - - public boolean offer(String key, Channel connection) { - return true; - } - - public Channel poll(String connection) { - return null; - } - - public boolean removeAll(Channel connection) { - return false; - } - - public boolean canCacheConnection() { - return true; - } - - public void destroy() { - - } - }; - - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionsPool(cp).build()); - try { - Exception exception = null; - try { - client.prepareGet(getTargetUrl()).execute().get(TIMEOUT, TimeUnit.SECONDS); - } catch (Exception ex) { - ex.printStackTrace(); - exception = ex; - } - assertNull(exception); - } finally { - client.close(); - } - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyDigestAuthTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyDigestAuthTest.java deleted file mode 100644 index 25d0771169..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyDigestAuthTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.DigestAuthTest; - -public class NettyDigestAuthTest extends DigestAuthTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyEmptyBodyTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyEmptyBodyTest.java deleted file mode 100644 index 5c7f89ef2e..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyEmptyBodyTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.EmptyBodyTest; - -public class NettyEmptyBodyTest extends EmptyBodyTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyErrorResponseTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyErrorResponseTest.java deleted file mode 100644 index ed3a02549b..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyErrorResponseTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ErrorResponseTest; - -public class NettyErrorResponseTest extends ErrorResponseTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyExpect100ContinueTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyExpect100ContinueTest.java deleted file mode 100644 index 62d52429e8..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyExpect100ContinueTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.Expect100ContinueTest; - -public class NettyExpect100ContinueTest extends Expect100ContinueTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyFilePartLargeFileTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyFilePartLargeFileTest.java deleted file mode 100644 index 02c459157e..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyFilePartLargeFileTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.FilePartLargeFileTest; - -public class NettyFilePartLargeFileTest extends FilePartLargeFileTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyFilterTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyFilterTest.java deleted file mode 100644 index bb0ea1fed4..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyFilterTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.FilterTest; - -public class NettyFilterTest extends FilterTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyFollowingThreadTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyFollowingThreadTest.java deleted file mode 100644 index 4461d5ef06..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyFollowingThreadTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.FollowingThreadTest; - -public class NettyFollowingThreadTest extends FollowingThreadTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyHead302Test.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyHead302Test.java deleted file mode 100644 index d4c13d3206..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyHead302Test.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.Head302Test; - -public class NettyHead302Test extends Head302Test { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyHostnameVerifierTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyHostnameVerifierTest.java deleted file mode 100644 index f5051ec05c..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyHostnameVerifierTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.HostnameVerifierTest; - -public class NettyHostnameVerifierTest extends HostnameVerifierTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyHttpToHttpsRedirectTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyHttpToHttpsRedirectTest.java deleted file mode 100644 index 89cf0d322a..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyHttpToHttpsRedirectTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.HttpToHttpsRedirectTest; - -public class NettyHttpToHttpsRedirectTest extends HttpToHttpsRedirectTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyIdleStateHandlerTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyIdleStateHandlerTest.java deleted file mode 100644 index 107cb8b0d5..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyIdleStateHandlerTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.IdleStateHandlerTest; - -public class NettyIdleStateHandlerTest extends IdleStateHandlerTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyInputStreamTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyInputStreamTest.java deleted file mode 100644 index 1d271ee4ff..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyInputStreamTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.InputStreamTest; - -public class NettyInputStreamTest extends InputStreamTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyListenableFutureTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyListenableFutureTest.java deleted file mode 100644 index cce436950d..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyListenableFutureTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ListenableFutureTest; - -public class NettyListenableFutureTest extends ListenableFutureTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyMaxConnectionsInThreads.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyMaxConnectionsInThreads.java deleted file mode 100644 index 593bb71bb8..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyMaxConnectionsInThreads.java +++ /dev/null @@ -1,23 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010-2012 Sonatype, Inc. - * 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.apache.org/licenses/LICENSE-2.0.html - * You may elect to redistribute this code under either of these licenses. - *******************************************************************************/ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.MaxConnectionsInThreads; - -public class NettyMaxConnectionsInThreads extends MaxConnectionsInThreads { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyMaxTotalConnectionTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyMaxTotalConnectionTest.java deleted file mode 100644 index 5aef28ba4c..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyMaxTotalConnectionTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.MaxTotalConnectionTest; - -public class NettyMaxTotalConnectionTest extends MaxTotalConnectionTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyMultipartUploadTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyMultipartUploadTest.java deleted file mode 100644 index ff5a2028bb..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyMultipartUploadTest.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.MultipartUploadTest; - -/** - * @author dominict - */ -public class NettyMultipartUploadTest extends MultipartUploadTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyMultipleHeaderTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyMultipleHeaderTest.java deleted file mode 100644 index 8cb91eae98..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyMultipleHeaderTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.MultipleHeaderTest; - -public class NettyMultipleHeaderTest extends MultipleHeaderTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyNoNullResponseTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyNoNullResponseTest.java deleted file mode 100644 index d74dc2d284..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyNoNullResponseTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.NoNullResponseTest; - -public class NettyNoNullResponseTest extends NoNullResponseTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyNonAsciiContentLengthTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyNonAsciiContentLengthTest.java deleted file mode 100644 index 098f63c3cb..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyNonAsciiContentLengthTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.NonAsciiContentLengthTest; - -public class NettyNonAsciiContentLengthTest extends NonAsciiContentLengthTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyParamEncodingTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyParamEncodingTest.java deleted file mode 100644 index 380dbeb710..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyParamEncodingTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ParamEncodingTest; - -public class NettyParamEncodingTest extends ParamEncodingTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPerRequestRelative302Test.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPerRequestRelative302Test.java deleted file mode 100644 index c8a45f3c34..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPerRequestRelative302Test.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.PerRequestRelative302Test; - -public class NettyPerRequestRelative302Test extends PerRequestRelative302Test { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPerRequestTimeoutTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPerRequestTimeoutTest.java deleted file mode 100644 index 4b669e81f9..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPerRequestTimeoutTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import static org.testng.Assert.assertTrue; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.PerRequestTimeoutTest; - -public class NettyPerRequestTimeoutTest extends PerRequestTimeoutTest { - - @Override - protected void checkTimeoutMessage(String message) { - assertTrue(message - .startsWith("Request reached time out of 100 ms after ")); - } - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPostRedirectGetTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPostRedirectGetTest.java deleted file mode 100644 index 6c434178b4..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPostRedirectGetTest.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ - -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.PostRedirectGetTest; - -public class NettyPostRedirectGetTest extends PostRedirectGetTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPostWithQSTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPostWithQSTest.java deleted file mode 100644 index 44850262f6..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPostWithQSTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.PostWithQSTest; - -public class NettyPostWithQSTest extends PostWithQSTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyProxyTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyProxyTest.java deleted file mode 100644 index b5cae8cefa..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyProxyTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ProxyTest; - -public class NettyProxyTest extends ProxyTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyProxyTunnellingTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyProxyTunnellingTest.java deleted file mode 100644 index 736b9d3699..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyProxyTunnellingTest.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ProxyTunnellingTest; -import org.asynchttpclient.providers.netty4.NettyAsyncHttpProvider; - -public class NettyProxyTunnellingTest extends ProxyTunnellingTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - - public String getProviderClass() { - return NettyAsyncHttpProvider.class.getName(); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPutLargeFileTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPutLargeFileTest.java deleted file mode 100644 index f71d57cea5..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyPutLargeFileTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.PutLargeFileTest; - -public class NettyPutLargeFileTest extends PutLargeFileTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyQueryParametersTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyQueryParametersTest.java deleted file mode 100644 index cc7fe3628a..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyQueryParametersTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.QueryParametersTest; - -public class NettyQueryParametersTest extends QueryParametersTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRC10KTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRC10KTest.java deleted file mode 100644 index 2c63c75b9d..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRC10KTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.RC10KTest; - -public class NettyRC10KTest extends RC10KTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRedirectConnectionUsageTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRedirectConnectionUsageTest.java deleted file mode 100644 index 91d782bd20..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRedirectConnectionUsageTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.RedirectConnectionUsageTest; - -public class NettyRedirectConnectionUsageTest extends RedirectConnectionUsageTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRelative302Test.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRelative302Test.java deleted file mode 100644 index b9880e79c5..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRelative302Test.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.Relative302Test; - -public class NettyRelative302Test extends Relative302Test { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRemoteSiteTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRemoteSiteTest.java deleted file mode 100644 index 7a65ad5bca..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRemoteSiteTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file to you under the Apache License, version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.RemoteSiteTest; - -public class NettyRemoteSiteTest extends RemoteSiteTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRequestThrottleTimeoutTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRequestThrottleTimeoutTest.java deleted file mode 100644 index 766a8f1d97..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRequestThrottleTimeoutTest.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import static org.testng.Assert.*; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Future; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Response; -import org.asynchttpclient.async.AbstractBasicTest; -import org.eclipse.jetty.continuation.Continuation; -import org.eclipse.jetty.continuation.ContinuationSupport; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -public class NettyRequestThrottleTimeoutTest extends AbstractBasicTest { - private static final String MSG = "Enough is enough."; - private static final int SLEEPTIME_MS = 1000; - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - - @Override - public AbstractHandler configureHandler() throws Exception { - return new SlowHandler(); - } - - private class SlowHandler extends AbstractHandler { - public void handle(String target, Request baseRequest, HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException { - response.setStatus(HttpServletResponse.SC_OK); - final Continuation continuation = ContinuationSupport.getContinuation(request); - continuation.suspend(); - new Thread(new Runnable() { - public void run() { - try { - Thread.sleep(SLEEPTIME_MS); - response.getOutputStream().print(MSG); - response.getOutputStream().flush(); - continuation.complete(); - } catch (InterruptedException e) { - logger.error(e.getMessage(), e); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - }).start(); - baseRequest.setHandled(true); - } - } - - @Test(groups = { "standalone", "netty_provider" }) - public void testRequestTimeout() throws IOException { - final Semaphore requestThrottle = new Semaphore(1); - - final AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setCompressionEnabled(true).setAllowPoolingConnection(true) - .setMaximumConnectionsTotal(1).build()); - try { - final CountDownLatch latch = new CountDownLatch(2); - final List tooManyConnections = Collections.synchronizedList(new ArrayList(2)); - - for (int i = 0; i < 2; i++) { - new Thread(new Runnable() { - - public void run() { - try { - requestThrottle.acquire(); - Future responseFuture = null; - try { - responseFuture = client.prepareGet(getTargetUrl()).setRequestTimeoutInMs(SLEEPTIME_MS / 2).execute(new AsyncCompletionHandler() { - - @Override - public Response onCompleted(Response response) throws Exception { - requestThrottle.release(); - return response; - } - - @Override - public void onThrowable(Throwable t) { - requestThrottle.release(); - } - }); - } catch (Exception e) { - tooManyConnections.add(e); - } - - if (responseFuture != null) - responseFuture.get(); - } catch (Exception e) { - } finally { - latch.countDown(); - } - - } - }).start(); - } - - try { - latch.await(30, TimeUnit.SECONDS); - } catch (Exception e) { - fail("failed to wait for requests to complete"); - } - - assertTrue(tooManyConnections.isEmpty(), "Should not have any connection errors where too many connections have been attempted"); - } finally { - client.close(); - } - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRetryRequestTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRetryRequestTest.java deleted file mode 100644 index ed148663e6..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyRetryRequestTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file to you under the Apache License, version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ - -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.RetryRequestTest; - -public class NettyRetryRequestTest extends RetryRequestTest{ - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettySimpleAsyncHttpClientTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettySimpleAsyncHttpClientTest.java deleted file mode 100644 index ad60dd6125..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettySimpleAsyncHttpClientTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.SimpleAsyncHttpClientTest; -import org.asynchttpclient.providers.netty4.NettyAsyncHttpProvider; - -public class NettySimpleAsyncHttpClientTest extends SimpleAsyncHttpClientTest { - - /** - * Not Used with {@link org.asynchttpclient.SimpleAsyncHttpClient} - * @param config - * @return - */ - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return null; - } - - public String getProviderClass() { - return NettyAsyncHttpProvider.class.getName(); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyTransferListenerTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyTransferListenerTest.java deleted file mode 100644 index deeb92e9c1..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyTransferListenerTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.TransferListenerTest; - -public class NettyTransferListenerTest extends TransferListenerTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyWebDavBasicTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyWebDavBasicTest.java deleted file mode 100644 index 5a26dbeac3..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyWebDavBasicTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.WebDavBasicTest; - -public class NettyWebDavBasicTest extends WebDavBasicTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyZeroCopyFileTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyZeroCopyFileTest.java deleted file mode 100644 index 04ee3c7a22..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/NettyZeroCopyFileTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.async.ZeroCopyFileTest; - -public class NettyZeroCopyFileTest extends ZeroCopyFileTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/RetryNonBlockingIssue.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/RetryNonBlockingIssue.java deleted file mode 100644 index d1b2dc45ed..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/RetryNonBlockingIssue.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4; - -import static org.asynchttpclient.async.util.TestUtils.*; -import static org.testng.Assert.*; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.async.AbstractBasicTest; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -//FIXME there's no retry actually -public class RetryNonBlockingIssue extends AbstractBasicTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } - - @BeforeClass(alwaysRun = true) - public void setUpGlobal() throws Exception { - port1 = findFreePort(); - server = newJettyHttpServer(port1); - - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/"); - context.addServlet(new ServletHolder(new MockExceptionServlet()), "/*"); - - server.setHandler(context); - server.start(); - } - - protected String getTargetUrl() { - return String.format("http://127.0.0.1:%d/", port1); - } - - private ListenableFuture testMethodRequest(AsyncHttpClient client, int requests, String action, String id) throws IOException { - Request r = new RequestBuilder("GET")// - .setUrl(getTargetUrl())// - .addQueryParameter(action, "1")// - .addQueryParameter("maxRequests", "" + requests)// - .addQueryParameter("id", id)// - .build(); - return client.executeRequest(r); - } - - /** - * Tests that a head request can be made - * - * @throws IOException - * @throws ExecutionException - * @throws InterruptedException - */ - @Test - public void testRetryNonBlocking() throws IOException, InterruptedException, ExecutionException { - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// - .setAllowPoolingConnection(true)// - .setMaximumConnectionsTotal(100)// - .setConnectionTimeoutInMs(60000)// - .setRequestTimeoutInMs(30000)// - .build(); - - AsyncHttpClient client = getAsyncHttpClient(config); - try { - List> res = new ArrayList>(); - for (int i = 0; i < 32; i++) { - res.add(testMethodRequest(client, 3, "servlet", UUID.randomUUID().toString())); - } - - StringBuilder b = new StringBuilder(); - for (ListenableFuture r : res) { - Response theres = r.get(); - assertEquals(200, theres.getStatusCode()); - b.append("==============\r\n"); - b.append("Response Headers\r\n"); - Map> heads = theres.getHeaders(); - b.append(heads + "\r\n"); - b.append("==============\r\n"); - assertTrue(heads.size() > 0); - } - System.out.println(b.toString()); - System.out.flush(); - - } finally { - client.close(); - } - } - - @Test - public void testRetryNonBlockingAsyncConnect() throws IOException, InterruptedException, ExecutionException { - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// - .setAllowPoolingConnection(true)// - .setMaximumConnectionsTotal(100)// - .setConnectionTimeoutInMs(60000)// - .setRequestTimeoutInMs(30000)// - .setAsyncConnectMode(true) // - .build(); - - AsyncHttpClient client = getAsyncHttpClient(config); - - try { - List> res = new ArrayList>(); - for (int i = 0; i < 32; i++) { - res.add(testMethodRequest(client, 3, "servlet", UUID.randomUUID().toString())); - } - - StringBuilder b = new StringBuilder(); - for (ListenableFuture r : res) { - Response theres = r.get(); - assertEquals(theres.getStatusCode(), 200); - b.append("==============\r\n"); - b.append("Response Headers\r\n"); - Map> heads = theres.getHeaders(); - b.append(heads + "\r\n"); - b.append("==============\r\n"); - assertTrue(heads.size() > 0); - } - System.out.println(b.toString()); - System.out.flush(); - - } finally { - client.close(); - } - } - - @Test - public void testRetryBlocking() throws IOException, InterruptedException, ExecutionException { - - NettyAsyncHttpProviderConfig nettyConfig = new NettyAsyncHttpProviderConfig(); - nettyConfig.setUseBlockingIO(true); - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// - .setAllowPoolingConnection(true)// - .setMaximumConnectionsTotal(100)// - .setConnectionTimeoutInMs(60000)// - .setRequestTimeoutInMs(30000)// - .setAsyncHttpClientProviderConfig(nettyConfig)// - .build(); - - AsyncHttpClient client = getAsyncHttpClient(config); - - try { - List> res = new ArrayList>(); - for (int i = 0; i < 32; i++) { - res.add(testMethodRequest(client, 3, "servlet", UUID.randomUUID().toString())); - } - - StringBuilder b = new StringBuilder(); - for (ListenableFuture r : res) { - Response theres = r.get(); - assertEquals(theres.getStatusCode(), 200); - b.append("==============\r\n"); - b.append("Response Headers\r\n"); - Map> heads = theres.getHeaders(); - b.append(heads + "\r\n"); - b.append("==============\r\n"); - assertTrue(heads.size() > 0); - - } - System.out.println(b.toString()); - System.out.flush(); - - } finally { - client.close(); - } - } - - @SuppressWarnings("serial") - public class MockExceptionServlet extends HttpServlet { - - private Map requests = new ConcurrentHashMap(); - - private synchronized int increment(String id) { - int val = 0; - if (requests.containsKey(id)) { - Integer i = requests.get(id); - val = i + 1; - requests.put(id, val); - } else { - requests.put(id, 1); - val = 1; - } - System.out.println("REQUESTS: " + requests); - return val; - } - - public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { - String maxRequests = req.getParameter("maxRequests"); - int max = 0; - try { - max = Integer.parseInt(maxRequests); - } catch (NumberFormatException e) { - max = 3; - } - String id = req.getParameter("id"); - int requestNo = increment(id); - String servlet = req.getParameter("servlet"); - String io = req.getParameter("io"); - String error = req.getParameter("500"); - - if (requestNo >= max) { - res.setHeader("Success-On-Attempt", "" + requestNo); - res.setHeader("id", id); - if (servlet != null && servlet.trim().length() > 0) - res.setHeader("type", "servlet"); - if (error != null && error.trim().length() > 0) - res.setHeader("type", "500"); - if (io != null && io.trim().length() > 0) - res.setHeader("type", "io"); - res.setStatus(200); - res.setContentLength(0); - res.flushBuffer(); - return; - } - - res.setStatus(200); - res.setContentLength(100); - res.setContentType("application/octet-stream"); - res.flushBuffer(); - - // error after flushing the status - if (servlet != null && servlet.trim().length() > 0) - throw new ServletException("Servlet Exception"); - - if (io != null && io.trim().length() > 0) - throw new IOException("IO Exception"); - - if (error != null && error.trim().length() > 0) { - res.sendError(500, "servlet process was 500"); - } - } - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/websocket/NettyByteMessageTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/websocket/NettyByteMessageTest.java deleted file mode 100644 index 5474f7f9f2..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/websocket/NettyByteMessageTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4.websocket; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty4.NettyProviderUtil; -import org.asynchttpclient.websocket.ByteMessageTest; - -public class NettyByteMessageTest extends ByteMessageTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/websocket/NettyCloseCodeReasonMsgTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/websocket/NettyCloseCodeReasonMsgTest.java deleted file mode 100644 index f44962885b..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/websocket/NettyCloseCodeReasonMsgTest.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ - -package org.asynchttpclient.providers.netty4.websocket; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty4.NettyProviderUtil; -import org.asynchttpclient.websocket.CloseCodeReasonMessageTest; - -public class NettyCloseCodeReasonMsgTest extends CloseCodeReasonMessageTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/websocket/NettyRedirectTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/websocket/NettyRedirectTest.java deleted file mode 100644 index a5e9d8cec7..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/websocket/NettyRedirectTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4.websocket; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty4.NettyProviderUtil; -import org.asynchttpclient.websocket.RedirectTest; - -public class NettyRedirectTest extends RedirectTest { - - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/websocket/NettyTextMessageTest.java b/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/websocket/NettyTextMessageTest.java deleted file mode 100644 index 10832e370b..0000000000 --- a/providers/netty4/src/test/java/org/asynchttpclient/providers/netty4/websocket/NettyTextMessageTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.netty4.websocket; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty4.NettyProviderUtil; -import org.asynchttpclient.websocket.TextMessageTest; - -public class NettyTextMessageTest extends TextMessageTest { - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - return NettyProviderUtil.nettyProvider(config); - } -} diff --git a/providers/netty4/src/test/resources/300k.png b/providers/netty4/src/test/resources/300k.png deleted file mode 100644 index bff4a8598918ed4945bc27db894230d8b5b7cc80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 265495 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

PaQ)q~!Z}nw5+`R(>f%iv2-Q(|v{Kn?y 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

E;dnj|F+k_IVjB&&;(F4ZJK@Lu=C z+$Z{b3AAxde*FIn6jAaZWZ~ol_bg*jcY>Yj(@eD-IZzd4rlGRT#qQ^jY~8D)&#KC-#FxgirxygvWeM!!-saq*L0ViyzP`Pa1f z@ih0)kpUod<^;f)QCWEZKi(AD$jb7s(HfsFew{)yiN@k*WGYvZ=ueUw=$m+>ZfXDxU?1JB;=Kl%V zK{5RAkX<(sMIi=6lk@U5Gnp!r9j+d^lkff^O;lLz^_aP7Kq%m^5ay+;BhwA;! zP^QZD*dpmS=aNQd1_pj+W@d&$F>MS?E(MKMe%?iu6bPQpu*Dt>G>?jN^vv}1oK%dG z-*E4}#}BeE)457mD?B8$O(AJvj(3y|3_b7{HnVmVNoI}1J7++CP3bu~exH6{UHO!j z=GAyMt;y*`jDIJ}#Qycu4bQ=4PblA$_ZZDu=TxtW&;@CZ7gO*v4NXbX^JM5#fliM< z8G^Ad4<9dpmzP&=@7}2QtBt3EJw!M=8(Bv3DbIO1K)Gt2-_653!3+Pbu4h<#2 zA<0V0kGL5hdTIW7R@%~%`~hB4}l<<~Jxiz(3+Go^Dy5t+5Aw7nyGBt8D`fLGt-JMdyW5&Um~m&ev^3qmPC z+$7O|wY_buwNB*uo3rhPrNwuz)iVc8Dt*leqsUbdoa`*QmFxE#l$Eg6p`bGWGD)Lp zx5@rx7C|YBy)BP--p+I6ne+L4?tf%<&RyL9o6L^$|3PMFdKU5-9;zHD8P^)C@A+yb zKX9?tQOtFO16yg`m7bO6a<#MNiNEov>zwX#Gphze*~ulw#@AF>WaQm$dql%_^O#At z8hj?5loo&3m5&fPhtOT1Jm83qi7Giht8(tdx!T)bPPt78oFVva0irF!O%1(m_z0+L zG@%MfjSFz>vgw~5x$m5)^kNZ&Z|~=1v9tgY+dt0(N3YxPB3?FSQ5A@m5u1!4aPQ-m zu(Jm(in7#AeR>OU3I0uf7OA=v)aF-J6`PQ3oa?-EPUHq@x(QbF;PQ6|1j>maH*;5> z2EMztrJ;0l`57#9q4caz2@k}yx$?X^Oz^QzskV9t`-9WljJ5-6Y*RvW`}C7v!=&-% zBHOf8PVlSWJTaD)q%=ERF{nkAP47NF)H@zQ(hOmdo+&DG1`k{Cz25 zg0$uU2T*}Hm*`Eh-aomIC1tWxGS zz?*X9-Dud!vQIyrSD7QRAXRw0j~U0!KAUcrr1D#5ouw@KifA=e@O?0*fOd5^AG6_D zRf5Lt@rokL?B%b_Gpq4}cjH0+kx{;^gMBkDK3rZG5>@v4CePh(=g6$@8O6_iVLuHQ z@R&)5URzE#r@Fnl>MhrRC3cKu_QSL8H$lg^Jl?W$CtCnJ!ER)|6_mx(IR~+{6IWy5R$rm>a)#pE&~Ku(l7ZqJgac9#;x=XPn?Fhra(h76b<{;yf}ONBcbl1ZzWZ;XWjeL3M<3MrCpI_>mx8MqAPZ;0`fTNWOQcoO(E97eKIs%H zZ_fUIp*V>;#^%4-G0B~fq68;4($5;>0gIHnv#vq3N>20!!!y*SjbUjQQ#a2$P>RNF z!BZG4jnfD0#;5)Q7)OnBlQbIX+|qP-_cXUCt8@~>neLkxGwbM}x1~aJbk_BpL3z4> zC(H`j2q4%^8~H-RatEk`3-SLHF>=#sPbVrnx`4#nY zeDBXh@8|ZBEoC|ku^qJ?QG;%?Z^4C|37cc*!bV$Z94ILz1!+Swpu( zyF-R24O1pJpgP&Gt0qn*w6s#Q(O$0AwI%4mL1OgdDA#$7cutBW=;}hXW7Z?#4rm8%Z0|!t0Q2}fVY}ooG3&3tsZS*p{ zhYo`$rM#{6&)9Wp`uBQ=&=Np7w|gl*)NL)Xnb=$GHDZX$XX{~Bp5;shmw>Vex}Rzf z-&Y+wBI;gmSM+b^%71G}Vhd>h0K{^hQs$j&JjFAj!n+jo>r55mZLq{WZDZ(ta0=F+j*@UG#siQ~Fw zo-*^C&$UH`i;4!cJ0*DVc|Ro=D`2tp-?2wpaFiEE>A5J9!iR02bN<|kY=aW6NvEi( zUY3Avm1K^%%;BW#YvbDI(?~zg?UYZzz_r-T4oE(OE6rEyct6nE!sR&%$Vh&G`Xsci z!z+kPxY@_k;>;&49*zbB7A;X%ro+3xr;r$DG6xhGwQ^Nk@j6__mv<}}nT*@pwxKsO zPMsmd4iJ>y%}w~0$H8b*S$}}~D?BZx_c~zwc4-MEi&wSX!qoL-vY|)*@;A7oP*Ii{vFy9n#TNA9^>i&e?kuWKu@oB>|^1f1r)lDDqLjUm&Kw1`t@V0 z>ewK(okB`KR{B10nLaCGQU~=NDr!CagLvXVEsc$SE&SrhuU1o3I9v*q%Q>Q!n-AL) zzYp;Z#-B018TNG9h(w(aArKs;?uPiR6Vwg1cl-ZA1alvY<3!zZ!<%@wK6p1L=na0& z#dPFUhdLVp@RWjN@Mqt1L$*(u63`b?ZaXPew z!LPHuLpo;#v$!17`vX~IT)vD|Ki7Yra}w@llF~CtvnpFix6xaydymH-pJ>X%2&NZ6CH+=W!=Z?aHiNfi7058QM!XcAWI+k_pSKH zmvt&DB8ZEB1a||*?da}q;i5_{Dmpr!!f&}ypnS$A?>l|?i(rWyZjF~F<$`%m0iHdi z0%H@(Qo`b4mmC$5q}`8_nu{6sgynKBlw5mP(2|N`%~Qyf3~CfJ5Sb5GtNHh{pAU*_ zOp7(!)j#)_9huLD90e6Cqg_6DWJeFhH{BQN8S~Zl^E5EJDQEkP?@@tFf;oEiYyn?CJu}DSkOtul2si;mu;9lM+n7ftg2}ztNW_7;#|J? z2(~ifg%XuNNjiX=8xvm8TuFAK3$(w^a@@*cEoShq42N&2`1)F>*e>QG!te@OD7jc}?KDvTkz?Q|3`>4M^ zvJ|ZWvvy(07e|?WCp>y@x*nu|2Z0(ypJ#^j-}6>-K@>hEJSpqMa-uLv27C2(tnmK` z!OL8bb$jldV;r@;??0MH3Yx?qUqh?M@d*9~Hh|+Qjw&fj`vCUW&nFW~!rj%k^;!ad z#%GhIe&rkBqZFR+#u=lci{Z2F|Q~?Y*@%##&iXiq3yhi>=uq7b3#7%D*HF{H^BJ!@>lG-JBSnSK>d& z>|U4urnTngZq(lk?Kc``Sg60Wu5ejyhPN@nI0E!J{nL-)kJx;UCF0{*zfC*0PN)5! z4*#FD;`jeD22KAx?=)CG;9&~8{BHK4T?!a4P-M*QIKE1Z+>$;_f0v?dZ434pFIrVd zVE+iwQXNNoG_6L=@wWMD*t!B&L2PSZ1rc3)ldU1aIqOzBU?Dcde7sgHmnY_0trfI0 zHY@Fq)q-*23}HR$|DJ5|WIV?dR$BnlBb}Z?QIi{mB=hz5F()10WzSYf@@3ycf{8Hs zzYwOu(S6^ANI(BILC2tiv=LWUJMM&A-Fq6b>l}7Ao*7-ymGII!6dg&;BPv3kU5IYn zcd@#xxELj=|M9Uu`#S|gEa?kV0@`Gfr&#LVt19ig-CZ5%w&vy6l{||NV{GD09Zh2R1+qNpUZL{K3DzF(3-IPVz!L;nF8;~rVxwbr%fHRopz1K%|?qz4OzEgzGYEB}@BIQuK<(Rh)8 zUrv>u24p_~g^`PU+_HSf+l>V6_?AAFzDX6%$J7Wif59Uy>s9bB;?oMGU})MLVZSt> zUtU&bd6@+qb)x;$eSn(bSyy;*Ae4 zZ{y%LmfXL#wuj-5%txuXMpFSW$$S+4D&cJLu;1Ta4e!s%PEUp^Ef3A}t>85hHxt|% zENp1#F%&m@ZIu#h4h!9@8~;wMib^I#|0;_9>1t)Q=y8pu*|=?Bu9MI3iW=@x2*t8d z4GG{C-=xmk=;YdBk&}NvKR>gxj<%j)Pfm`zi9W3)Aj58s zVBX~dIJtyNmdrDi>Dq|b^U89*U)UbvWoFtr1Ta*}7q;!eJFMxLoh?7htE*<|Hh6m- z22XIy-0V2UUDMTB$aE`GxEFea&{l(MP58OS{8_Ww)s?-;}M?S-L>o= zTCZm&Lq>i$e5$H`lU_Qq)^=D*_Hi*wi8$?i2=~S*TYqnV@wR{qJi|VnRO|*^Ywd0G zCx`x7kaqV!^r*SKK?&c!@xb|O0}!qlr}+o@Q?6W8jW2tQY|O_ns2q;%eNLXJaj387oRhs#?KB&b>(t5falC9uTN>tp0vmazag1g z3A<42a^|9KN&(cR{ltS{m1X>8Mp=Wv584b!dIk@OyrqSW;u&A$1F0ddYUN)Yo#!Di zE&vdRkiz0uVeNz@++!3>vyHhzl(gtOCKRP}CTv|%&HxlTV#C%%iKL(~QPO%k56)g; ze%vq{&f}+f2PP{&yctttnfl%4kg+aG0y^Bbwi?>wE0Lr(N8xIol-$G(zu zq;98xgli%LzKdgINf?Tgg78jUSV|lK+rtqZ#a>@&U2&fe5P=a0urB?Hw8pQPFQ%&E z&&H^~B#H)p7r*v3_;JXyx2aLNc=bQGZl zZSXM4^HxItg#^EdML#Coma39K`x@q?nhM5`O`qvB`QkZthoFK>X6`s_YfaKV<&6d0 z0{_?IbRK;I1t3m<{#)#XX!{?rlduwYo1TKQ5*+*rZC|XsYobXN6e!7M1|B9#qygAv zs3X!n+cgY~979f_@|-QQ4d|KDUf7~d?UTp8UL5y$^N6v3arjYgVBTFi>2A$GW?P8f5`By7XOdS z@RCb@d3x{PeNjE5Y`(87Aq(z5*PNxFc(7x?4rLJL^R>db{RC&arf6Xhid(c5&_{y7 zfvi`Ie3XyG91sXKMzzQw*;$X-Cs>=8XaI6V`iCY@r$Yf{I%Lc%pv#)Ap&%C-8g1CP z4EDd~NnQqipzTCo)&wfu?d7N;0+aZX_Y#yGJXrHVrlBJ{g~XOJrfdg)*?A1e(;J7& z13@1TcO5VYu4a53_te0vdGmHRFAdkAy?-=;ghL5|hXCcy%rh`g@crDK3vM!EP*>#^ zKlj-JWmiuC@VZb>_yK2_r|fYtE@+&G#GB=p2*-MIWMSc8Yks=H&XvG=s!n34y^o~P-Wks@p{%z3LV~lFIy!- z&6~U{s#jhoWQJ9>SLfyBwZkej&7xsN)E15~g-&|n_a!HnCJSBrp~9Sjzc~U$*Vd1& z!^y82APMP2I1vB3vOO!WRn1_!zaRQtM0OfoME25=H;}^F>t8^-Mor;1PkFO&T6kIh z1<755>9cqXM9S0X91`cwFO^=rnq?`(Jz*B#jlju`)AExxYa9COU}~%Z(#_`GL6> zhbH!cgoyyi0HPrl4!P^cI?J_hf<6XE#B2Oo;N@6ZUY=)fli)-VMVyDKV*o0;3Htom1H;UBm258-r;0Vf9Pn%%|r0)tkAoAej8%TjKu^?lXHlBkNo z;n_$X?;Q%~2@eMbe8=3Q@}*`Ta#W+rdonp%KQ(f&nk09CbIXRxsZo|EhQA}_tUdYb zfjjEM`cRc?ssM4~I85P)J3dXVY*z< z(D@WOIZyc95k50Fgj(g39DX>lnl5F`JWwRRvfsG-OXu<%!Fe&+Y+4ayWMFn~eTKZz znSnjL-?vppKTtNKb^s_{Z?t&k0&0kcLbKEDk`$rj60Z|35r%ooWm-RWIv%H@zbJeBH;f!{T zN7ybuK?gtsbK27v&3NVeTPLJj zH_vX-)|$HOlYEHNw;~v+!ms^52c*U_v{Ov+pv#$A_*wC@n=U=hk_ra3S|vNR6vNIm z5GFkSkm3F*`hWWk^P?~SmVcK5yMwT(zWOiS|N40qJxz_ExeR~?L$Q-QacHc9ThAfJ zgOG2_?E)V7RALt>cNnx7`XaNHl+@Vs436b`;G&$py7L&>Px8I0bxD0*>`?Y;UE<7U z3+~6jZzr|6Aq0%l^!=y~C@Rk-z5|#*;OsKHl=~#@)`@+---8&wct{?Ju4xp|EbY85 ze2BL)2#bdoEo?l(Hr2gHT(3V-F1>T#hK#ho-3VL|?0leEXQ}7+GaJgN{qM34?&WlU zXC2xrN(95QmmPduZScFDRx0~ZSPQ`epRj`9;gpBMnmrQ9lZjBP)0LIym4C-)XImz3 zuqxCC^C%ZoUw>12OUb3M)U9A{ZRU9s-d&kzl#_2jindZSmT~J3IUZS3#s<6^E$;0@fbAL8hrLa;$QcVdwJf#21V!PetALig!@-ms={W z!h_iVDGpI;ps`&RFE3~=d<;0UoqlzC+ESFIS&q@IF2&@%8OEc}E>QB%E7j?e~Q zyjs(O0IyIL3J&Y%U%@d6??#Znlpt%1$Y?l;f=>W?5i4SAdl6ZG=7a?BzKJNXK5o_( zHR`u5*iUcA8cQvXje!~@fiRwJ5^;Rc;$|q_2w`Y*(Mu-*HwqJCCcq?1_Udv^w) zud2J=50cf;n$yoyxz20cWitPE&_P z5CRnAtwE$p!^+ML$e^_V&29Chw<8ND$6*v zklCqI#)+=7b@OT{JHVobB!&i3iVA#CfJ$0T6I2odDfHizgFc(q?q3^{lltb_F||BI8v(3ae5ZhWz6Om;K`gPofs$%V9ueN<+r@ypO0fS#tgPC>leJ zdj0#2lySB4t~S#gV*W$K!7wxxSw>lh%!S=DqIB_jv;*bZ^{rUMHMQC``SPw;1vY_m zOxV+ywm;dXlXDa~6{h=(-T))jecTt0U`L?|u1ks=*u|1_jiy$aqzEt+2*m#$u57rJ zXzMN0hDt(-Y3`xKI#9K5=8-OL(CK6p`aBY={Rcf7VQIH1<<7{SR~Q^9XD8rvw5_0> za@>X@F4+$22yrSB8){8Z&(7OuAl{+Ohf$yx(4W9-fXb^-NDaQhny3iZk=(kbLXs#t zh~W%@LZQqo$5#=4hUs@E^i{lh4nt38RkdRxN!nuX()~8av#T3fm-=V-8)V z<*Rak3E7A!z*vDa6H-Vb+Xa`e_QuP;Qlf_ojt!RnUG^n@bn{te*K5$mv!{VHdydS9 zAmr65Vl5v4LOcX@^xgypog8>X9P_b1>}}AoubK}!=WJ~6o!_~Z7J@TdzorF8bmG3| zHq9=ks}=#&?h5D>n#sWTDzGCl|Aa**S1S#iMzxuTS;}SqNtjr$Z``;L$k;UobhY5? z*Q8$~SxK0Urk}67PqQyi`1NAI1HXS)#e-LVe^B4b(Np!>(9zLBONmVkPFk|3k3!CJ z)=5j>ZMUWtaa|AJjld%w#rCG6i(lxey#=>VDB8c(apmLds4pxJ@APV16~xZ$fklWm zJ>QQQHgK^9&0bIPFW`pO_G5iCfE4yHn6JZ|udbixRxzkzJ+$Ua8v=gsF|&c zC#w~svZJ!37MRJ8Q|OQTxsH4WY42JW4R&OAybnflSU?}Gs(nwMmr04##FnmxK^?jp zHaZ9*-AI1o{O6pIOAVe6l2DI@A=$AP>_jd^6j(diK~X7gvK>1)v+}~n_?WY83MWFE zsNCZ?*#zWGn8Nxci2~gD8k#(5EWy22egZ1YOx7@@sR`7eZ}Ubr4p*J8CwMsB>0Vcd zqz?}5;U2~a0h=eYRQ0^V+Py}`g&iU8;F|y>{?7E}Mp{xcGI4+r?@{n!Pv%`O{g7b- z4be;S$TLsHb1_FNu@s+tqK3Fe9QAi0YY$MqX&nA&or8E>i1@=4?2 zKSq6k-chTPTx&(m%6V&;(-N($hbtX4#3%fLncNY-b`&8#b48KP%Wjh>Svz9%^)j8N zd4uv|cC-rsK{G+#3kL^MvzyHwbp6*Bhfo;sAB*+ts|WnBUTEU&gUYa-g^4yOyQjuA zXeMTZ$(>H(^%+akd8ZY#ZsHhv5$*5XFfvN^F;1(Nk5D}#xmbV5Y6ysFeWSxly0S7q zg1TUwhe9(O!;4?P!IY*kqCI|CBrUCly+*J4+Jcw7o>f#><*C>-EI!Fo+r@#Nbd=qP z(m!HVXTPQctp5FK!o%DPBL(=%-w71gP|n@gOedN>k`{0r_X#}uFl}&KgPA_h?;DE^ z6?UD{!^Zvst7&=MXP(B>IG$&n*U;z20T~XQFbb@08MWNdb>kzmyq$)hRzrpJ%{9Fz zRPjhRRc(5=Q>Xw@GIDsu+ppy^tE#KBILrD9thKqOPcFGS>l52AFRio@e^k)WyXSq3 z(tk5bnc43}%BXT;st0+=ereKUD`cb#EpflvQQLs1lm|d?kR7FzG zR4-h}aoJHR!x51$GD5)@H^i=_to)nJk|cusmu!_}F^Gwf>K>Nm7s(0$*KAb#{AR*s zIlYaBIt7li$ZNw2LA+xb3|f6*uytW$BWubkF3BwulQ;>_bmLELaOx9q43g^l(Rx1Oe(_r`FPS;pKY%V2Q7 zX!XFsDG7`Az4E(XTMD(s$l`^Yg7lphphts?pDyZH?Gse9(|4nme+#Ldm4b>e|V@0DDq z1h!H7XMZ`dfkP&R9X6M6c_Hd?;o)1Bi&to5lU^*724l@| z5b#xvh+^^E?V-i50R6rAt;7bUzbh*$Iw-@~3h9}NqI>@JZ`6MEiv=~T4L^j_Dt79W2X|70{0%OcL z>NGw5%XZ&}_7NI8EoDbR^YrvSwY$kK3z+hH0}C4JM`+&GRE)S?;q)^sH#ez!K~L9(5!LmkyM7g~+< zh9ZN3m4CXAgk-dI-rYuPfIERCkn(726AOybL&G~^gPfA1fiy$2Zc~z~dNJQ+769mM zMGiBH#`Jcrfn2+kB-tu!VB~a)(K`DaF{%(r*o@xbwtIF7SV@2TV%0GH*f(AGCfT?4 zIWGHyV6M8J@OkuFH!dsGYhKLkHfj1g?#uIYgHCf_2K?+ zikwYdwcS$iL1GN`RyVi=5zUt#6rBe~#yYVAM)C%&H}Vmm_WVXC{p0VV?IJEIe{rF>NYUf1$=&N;(mWFC`|)a%eyO39re z6Abiix$)KXyAeeMwHAc?`NG1=#FC$8-noXl7r6~jXk$zuZGTfp!?{ zVKF%*=uf>pDfZTs!9%-@?eNG|G+ z2I9gdAxW_aHP4}aiSgeF1rNGdqy`lRjQj7FA$dRs2g%c863R=SB!q#mky7&uJLt5h z-CkNR@VMWF7~Vh=mx7A)hlU!Ux4`mu#;)e|4#A<6wW?V$KjZ0J!~wqX$7L%J*7& zY9Ga`-KJePTyVwXcG6<8x#&EX*GTU1F7)RQbhd2va%_G*hRltl!?*KX^EqW(YR#Bccdwp_T{U4j_NEhjdNYG=OgG1gLXJU;zS%2*Q@j58U2 zxK=-227SYT*1Cu%bt9j;y$_peRxZhA=r}%x^V;yoLhn0``==LxpZR4SL*B_D@a!XM z@1CZnbB-H&j4^-b!wbwR#33lJVtdf$5$}oT`2n>}ii$wvd1m%4&zAClF%5u+i*M+J z)xDZkx4YNctC!<2kGW$1xv%;pFQQcy(a@1R z;YIAC9w)vL$RMU7*z00!F#ocHK~s?#@YrMF%UJXrwcdQ|-;6xf&dbWTiK(o>O9%=M zID@1T-+T9XtV_1TnY_&v=h$_Dz_|Y&2>llQe5Yi0PrpWM_WiLK?I5N$VUVW86{qdY z0d57Mr34o(1Wn1RyP3--lWlTOGEXt%^ybgrz2DU?hZoHy_R7)@AdkGW;=Ooe)9jtU zCG2T=s8cO)%tv>0yX;`S?vS?p+{Uv_FfdeuY^UOu^53nRlp{J0epp24#bi)(2M)k= zQAim4e5yYlaNf)-I2`-MFQb0pM}knt!f^p%ANpxsG7m8>?%K8-9gn^@&fiye(MQ2>r~wLZHbDH9x%eaKijzB(*v<bsj? z^`^Bo)P;BJ&m$t7k?-YVKIR1-tUosH0PLn)TO#wHvi2l#J38lUc@`8z<RE1vXOS=G0-Osq+p>qKw@N)f~*hTTv0 znFME&%tD#dwi%9oe$J@0y^CIAcsLReHl^>e5xBcO>ql(qhr&d!T1cE~tqK<>>j(>a zE?+}};!UmMJJb~z-=n>4bZIX=v{I!MjBF9%4@phmKsSjTPo;d03(~5V#jWLxO*wWl5>`$5vWn&v>7$j1;q%oI9lm+E z&ZE8^%Ia;^fsA};U{YX%`)vB0u^N^kdbYkrFTlm^N|(z1lFf$#o?qu7`0+8Z^Qwn8 z+B`*W(y?bKxbC1yJoc5{5`w^6zH5)y5d)q6US>`VoM&oYk}SO!_>}bqMR2r{G5;nX z1t6gPgY&Hh0en6mzo>{X0S$QP`TF|ovcIlNh!3^+*NT%BYH~5Vp9Ne4%0W(CX`?_p zqAzzR;JD|vH~Eg8Ji6`(`ocj7raOobrh~ae+=1D|=%}qQJEZPn71H`v!{TNtovyBQ zxddr{e;X=6Q?Y5);`NdwqHAC@Ofn7Dzv`f6y5KAbJR)$SNa#vYO%AacCN28_S)8LGTX%gy| zswvd^l2HMsg6(SnKXJ5|m2e}K^ZTh}G~k0{sb+7(0P~u6V@o zgx7X@7XDe8#Fn_!81v8wVK&@*zVmJrkcE?}GO9=gHN0rH4eWK|6vb}|@Sa6Tg`$`Z zHZLQFlMDQ8XZd@U;%|94w_6!A&4u*%d2FgtKrRU_qFA6x(D-*+dR==fX(`fA=k!&a zv-am-L?X7&SH94iAd?Dy?!)-#iP3I_AYlPsZXzTg*hdfN3&ZlTWWQX2>>vN6*Zw?x z)bN1&i$N@xHmk|r@?YvPN3OJhIqWVDh!@v*hioXfu?h!pb_Q*P zeUaTVA!4==I_U~_CLtS{yc}(Ssgk%&`ngd`BMQQKAv=@=gOukgMW?$)dOLgE= z==2h-Q;VwNx}Y#in&+)m0Tn76l7C%6t}c*E@ogv>6*>h^`X&onoDL0^3jHlNbV5HV zi_p!xR>vPTxLzoUonHg3SgVK*;Qg=GE)cQY26+E9E%1oOJqySThx%-EN@@>+qMc=( zLLp}+O(*JgTy6IbqE_cfS>tUj5|J=UaP{Rw>_cE#rGV74L~FYXySlwBhm=-i2kDSHDMKV7i4OsiHbakCJg zDr*y>RRYCTQPFH!1BK|e3=ZIA=JycUG3p3wk56&|=UWLlUsy+DlRZomwx`rN5mL-3 z4#*6a%TE+urBs(OF?Yx;3gM=K;P276wGWckX*nJfbHo?srOn}JQ_B;PsfY3-{LS0{ z;I9mkFS=h&qvw~wnxVJLybyfkDIwU=>FJQSV7UGeu3S_-lOj)SY z84i)nODWoK7xg`S^q$+N0SqKtnXMuDtJHNipSv{?I{wVCNRE z$7%y&2OA@qrvkXh(apRfP9O|-@G8OUhgL{BrKp)3v$S{&q*4YD+ zk-y}TF=rBdxtCZ1qXev7a9D!qx6@0wh{cuBj6IhQFL07h8FiITfP0-0wiuG(CaD`z zd^2v?wZVt{8n)8ZKw*T$`zx$jV5;*kx!ubavP&)I9*Lj`C>nUeYk{)JW#ka-yLu1& zyDZLJ009u+v7@UP)HvdRZe|P>E@Br$F)&4G(W8)h7)(SQ&fUbM69y zm()&JibMCgtKsg>Z{>NPRMbeUsXzp=A_{hmV|pE>2v8>#dQ+MZIr6s}NfX}t`|jwf zbuXILVyX8K5C}lV9UdOWh7D%$I+3o`=O@6(PIG@pF#6 zGxg%KVR9YS9I|#g>}bTDR-!jU04@$(&p)_0ZhPs|T-`9!KTXt=RaE0zV%WGP0`5|F z`jDcsI{i8BfuOV37f%Z~;kEsOu>Vh@^;*DxTeQ}wWDyOk_HHAqJ%!Oc#M0r88}?-d z^+#4TQUPR)UPLy26wuWtUIw?8p+P3L2BJDOMh=|8Ek*;%pp&bk?mBrD*TM<{F=Mz! z{~4mZZX?d^+IrQVG#CTi1%3>$z;>Dp4D{V^L3T$GhoL9EX+t117l~A7GYiK7zqqfw z+bgcpj;=N`3%%v^%z~iZhO1&63KXkR$|*5VdK=4*eUiJleVS})-A&ZANT*Dvv0C1X z$b4Q0e3t7i8g?fg8xQ#Px~E&h`SrHaod3Q#|9ZG009z6oJuHAV1pf87{dEdCSoHmS z-`3GC_y!V@(GV-e_k#;vMITr!HxISV3ACqg>f1+}L+xU9DF3Ec|M~%0t0QgQ0Bv&l zch%s3FlqkQleg8x{@`(vEMiut5H%Jdd+UAso68o)Mk2hb&H1}?hoK_8f-{*#bO#-p zgb?pv!+uOp;uzCbc40=sFzC1zENG|v%77km(j0^SPl|Xe{-27IGr^c7W5CPe9_iYC zY0%dE@K<1~hn5vsT3YR)t!|u^DH|o~qx_@F{%lZagr$j}q|{{{ea0l;P6{MT05Tfx z8xS!w$EwCyMc8O%txI1uW?X{WtfP+w5G!+W|E5pXoEEp;79$Sg)V8CKms1m6;;B{h z%ZDVu`A_J@o`gR;#5b_$px1!wCb?oh;u?bOwqO)~P9o*uuOp8Wwd!0G?;a5SX2gKG z7X`AcaF~;oy`%yw)hWlUnD!U=#3CkJS;syox+~#*5sZaNz>1p_IH58%*H->z}D)uzIjL790 zHh0bPCmC_fQisp6Wx*F+zbfhZw`wyY>bvwJQgA1`&+jQ!jNifaEgG8S#D-Pk3rC;D z&l%IA6wahYBuOCYaY;(RLYX2?#lO$$VWFAb?XDwRASdU>dWpH#HIn8;_eB`}3*Lf(kfcd&%YWKWG6>FhYdrmaI>c zAGxhhHzt>&VyKT`@LvqKd-S z?<~yayf9-u#lq$^HNlgx?uSh9_O+{T`baRwZ++aYmDB{1L{WeL)KYdqJ93JN=3e(0 zb4F{4E*%?zkK1j%s%P4v4M!%*c@{z6l8}`>Sg(zuO}AxlA_rW&p#A^%^=wRDJF^j^ z2fn!%k>&^64@P*Q^(VH?yi?NUX3zw;3nk=`Vpsj>3eE|r9q8JsGEQg7a2=(xV@30+G%sisdwvh@Zzz;y7r+lZPPnKkzml5ZFEoWRc`ebHxT2 z#-N1UmQnkEi&e~dOXDy+S+`d-PbhwCzH@)1D7c*&2Ke;XG_wKElxIU?+cbGVPndky zGv8Z~wdE?pU*h#!Zb6tj4qgL)o})^C#b|KMgVd8??NJV1 zc2$euuiu!kHMH;V1*`K4M2P@Z8bRT1+QqKUcC!!&mxo*?skvhY;jQ2>R9EPjW}Clq z_tK(on9$;{n6R@&IYA0!i49!=RT>T`cYrF5dt2X-Zf@i)!``C;dEhlrirl&66ooXE z1SMj9>uQ`xEl~kg&p38ns(5JOCh;m!Wu9Ka2s{-)l}5p`pFO?kkNcSu+tVd(^ELa> zZo%W1tliuaZ6SSBY?FjQ%B`jfZJwW<4bibe*!0f|%p=Rz`nYV!hXa5r4Hmn29_o<+ z!H{};&zsptA)&FKO>m;Ml3Q{dHOtBQCwN>V7T?X!Hh%gI1<@7w`;C3Nqm`WFX{F_( z^2Q9_z10-A@?>AHXn{K!6_HAs#Y-ed0Bbc;h7F1CgG37{EwVFDbDCRNeB*+^Q$0iu zf$15$o(x7IEA@W((g)l``!-f6k?C*YDV+E_+O(%AD#h(`uW}|qyk-N{v~uapm&xtV zZCfuy`)40n=?n_c%A?q~JQMa>VKC@BpEfac*@8^Sp zz+!%a#Nb(q4P!WXhPw?RYHUEZe(sYVzH>qac77%&pn6*{1MDae7RNZ{heXe&{m&0& zX}x*=mO&VQNI6`+#^ITQPeBoZ`^LY>kXd?C^H-(24rTMyCGWE+o{A&7=LeA0w1+Vs zbqJ%gXrsayU1Tjv^!|`?MoU^!k~qqN>PCjptaS%S6_Y=%MAW@xWGZJ(BS$OId!TM} zswu7TFu642pePJsw1iQRI*sz=U746+%H=D5P>irXQ@lzu8DT(KytVUD$c^;4>HOYHFHF;>=B;Gf^mP^GhX8jWOZ-F_ZKR$^`Q-!ppDD{BQy4r*}S`Te|=03HuZA1e|@PF&+ z+=ke%Cyi(-Z-QWA;98^g&*DRR`N0rABd8eO)JH(yZJlDV@||RU^iJ$ zy_c;ybAw3($(T`7$A9s|0-2O9hsoNq>Z;u#DA0w<+ST(QFdz4H( zC`!WopQ}xekX}!mD~@iKonOY*nD^YDY3?n6KVZPBoF(IV zP)yujL`>Gpl&4d49Pfj>t@|Q#u--2sO4>R5oj!-{UXs*xPw>DFFlTK)yZxI7+WKcy zZ2@vmvjGq zt6w7SpZmALRJXY16ib+dwPx<(zw|u0pN_lT*T{dD)W1)Xe(H8^&OU}MaYx=F*1Lq* zH}h%P`31E#@4Ys-yo={pnAa9vU(-W;i5ef|ks5#XiQdPb_L^CmSMBlIQe2<@gg73I zNMH@*wrd4edf57Cj70R!d~EHJow{6e25n~(31;JC$|a{VlyIR-1A%Zt{z2j2c{SEw z{4l_vrp5_z;j{c@K>G%##R@ErOVN&Ir*d6+Z!d}wY!O80%K#dbS{Vo{vbXSI1qV=( zlv6FSNY#5Xn{H*x_TZ=vE4s;rb)4^8ev}}zyMjQr_!u+tpOn-$;#`LW*KdY>nSUzw zta*SN9{u;3=2)1usW&`oD6Sgu>*KYK@-{4{d-1NMF#X0@tUmpI-sxOTtXs(+6ek~o zpL^uT_;O2S+qmAv?Q66$urISM#1C}2?_s_il5~UICG!II$K}vFnIu)Buj}R|=q}@v zOXxlL?5fU?AsFuRvHj6k#5na{ZPAiGM z!&^rZfAeu7;Hm3R#bhkflAd^dv|&g$b+_Ll;lx@X*6z6XtXwdYl3?^B`uL7%i;S%6 z`9$=383!||j5f`iLDt@dMG_xL;E0NT^m+a(-)p+<<^4KIIeF7VY8B(r&0UszZR%+Mz2gso*F6&eaUz8CHC$(%;(g?p@;`mbSb2m+OPgga-EC< z-{$V412>9PN#9q}%wIR*YH(>ASnOsLW&_6u*O0M;uEPt`E9GK%e~P@VKJ^1}#59v> zdt?H1=wf>2-+rEREF3))QR&g>2s3p_o&5wK*FAA7F=C#R(Gk6z{~`N70%ZS@TsBE0 z;rJyY7)f6azLS+Q(GO_az}>spC-Lc(Xgu;Wi;mBSi6AG|e9QwI?PM>zSvh}ztI3DU zbZWB4!}~ebPtIc&lm_|>a;Z~)O)?6c8umrafbA!p#oILzpMQruPkm3V7cTZDpiP_+ zk@kzOG$i*lU^@6E25DNe+Y)y-p$hnx+&&}*Fo5F6*&030W29PGED|QWjF)tk&a@_o zHa|Fpn3kj|Qp~cdWrQvc#aLd+Yu~Icx*fMm>PjcS5R}@IgkX^fzNJin7GP4IG}Oj_L0D;q~8`Yw>M{h ze2>j^cj<#X&dxqVo0iiqF_H01`-qTyLqaCc`)I19?>FfEbYpHr&4c+Inr2Hd&|u~$ zEAWmA@$>Mt`4%C>4=gsPJ-RzB1fq5)E&M}W&wOe__ea^q$3+~EzxmIJK{NNE_fFC4 zF&%F+(NH@u{wu;0wO1D{IhKr=$?KJ!^xlG*1$RLGw~Ur9Txj+L+05cYcOW%+afpxi zsdp;6ARP>*%xK&qc03b2?pd8@2N!S8$Edij7{TrGf7%5Q{~vY%rin9I44)|h8s$nP zP5en&CtOj2>)eo%G){?y-}I>Qerf-*u;X$LZD;M_VhStXG%|PrDf=CBLgJ{e7`K2X zeq^0`ceTT-+=vn-v$468^ZNM=fI;i|RIpf>NMtl~WPY`Id5Ol$9V5sP9Qlg)_Xn$1;3o#*irq@wnj*!1=^3LFJ8n^p%G0yVL(!QCS9=(w_rAR>9>q`Tx^L0v?@|7#X zMgYwm)PltG5XX%^*$UCT+~a2dum?ilHdy72TWUp?n~D$gcSz4d#u;YQFR*9^e{cl7 z7tvo{8~2@UfF^u`du4@t`EN4btqKcvzX`4sAkWce33opTKBosJI`Slo`Rj`{qPfrvSuWu%tM+E?eKL!vyUfCdzjJk7Jstu9A@joTLJ zmc{g4uC@0DYp!sh*XKTJwB%sud%=k1n3b@XDdvhOK!bGmCxp;xWje&yq$@6KVsfap zg5Nz%)5bq?J?eYNUu%mlB>`>Y&1dIWVe9n`_p2#w$^Dz@{Jkb1tt2 z+i>j^!NWooh0oPO(kso`1W`&~Zy?qQ-jM9hEpYN>>(12W?L{ClY^~q3zDi(~DET4o zaEFY#(6|0Z1m)FIVSMtlaCWaOA@(AmqMAk}32TCDpVO1)sJ3s*tAyuPcOiw88nHOonvA|z$`Bxl^Bz6zG)8E z+D=2w#kJ0QtZMvwdw#trB=zt2rjW-1|& z2z)ZKTPm;^x2l>=yYq?hA`j=3rA$*!XI}(;2^H9>Xt>^b8bRcH`R?l{gYuE)hB5O*=`b_ex@;y93nar4cc{-;D#gGNz)HjS=dR9L|U z$)!-`{_4~2#XtcvmB&*i2UJrj?%^yPxfXDpa}5gkp%SrjMq+<(M=yb5Ip8Y+A0{ zb?*+SqrPy@bWHNFtUy(@`z1R;icj$(-^A)Gl41-TK)1tn>IlW*u?*p_RuRVpmCw;^hC#6I>}33wUMPla9mTY}n+S4#qHy`A;9$GF0dgs&u|*w8 z(@v!r8nMqcj1pk>3pH7^HGHVn8#1U&PQ8n29w`rHc?bf?jiwo1D|W=eFy1u~58#p< zahEqlWCmx%2pCJ&bT)QDAOUM|yxeuez7O!ao2J~2;uVbyO}WAWoz}0g9Sp4C2?D~8t)Z zLKPByP@>P{Sbq)#ry{hPJu#F;Pf*lV;F(w$$e9ulgpXvIL+~!kgU&i138;XqQ7#=8 z$sBy&UsGKKVeNvBw4pvB2=Vp5??O-qG=u*VUvmLX=?5UWK6HmJ2)y^PyD$%8$lOoRI%g>Ew4`}cqHXM z>NCY@P;`4IU3r0!bJFt{0?<+-kZ--8&W)#q&esMLNwe6S*O+D%^5?5*B$c(7Ws5>{ zm(Sq9;lTF*=1^%L`z(`Zh3_@~-ZjhOkx=AWDF-TCrH@#$!-R~3Smbi(R%IRRv}#5a zHql!bk64k{Ogf4;y6l}L%&EkkgX%BDxNhRudy^k&z0B7J&4NTR&wXDDkqp=DxWhWO zl3Oh%--yxZ$tyX0dqIkrbft04C&n+`CR`4Ef#pR!W-cDnkrU~XTLfs7S!l!cM38i) zRIlR7F)pjK}!coNuVW#drO-)Th z815w`sASPoiz)`|Z64^)AP{MA#z~03(wV=V?-vrOUe)tyA|-rmLGVs%@4-xyvEWz< z8>0Rhf|i7~-+te@{IdI*&T655$MuW=1#e_z1P4Bl(tS^+QA>b)-V6O&E95V$k@V!= zl3X*P1^6?4Cz4pH*0+0+zTx1$-@=;pCIUqV#f7K|&X;BJ6mxMq1p0DzJml%!cZzUgW~u}F!IdS-EJbp|xjW^?|tVTeS1u%1)|=>vo3i`OIs%vR9R zUIUGEdHKk?cm`f!-|zA3?a*O4T|wNJM&RiS^M_eni0WRUsIP%}U9~4G(PrwaCwS$x zOV|$VxV&qYX2&oc!WVtv1DMdoGGh1HO^c4O_%;r`_3%Nb6n06@J9f((@3#7s{_R{jOQWVt(E303DAjVD+hO_ih?QN{M6rSwM9%xrD!fXcfa zMBQNgja7`%*9B@)OdNyc&q6>?1GMqULr+6J&FIfCAuCEqe|9i5lwwmZRZQy+Dg!a~ zKZrZ)sJgl>%M;w4;O_43?(Xg$g1fuBySoN=cMTSTYY6VHeaZLU8}C(DRd@C1>i&xX z1IFc^z3(||uepA6(%^;V@N&s1|Hv<&g=ll#cy&_WWS-gV;FNsL!plxGb---ln~oN1 zle1W$lF5vCGhgnwR(}SsbTvC%PZFn%X2SYsQROK5JYmpFZZg*v%~wv2x#{CY$B7W{ zt8?Wa=ajx4p&6O-8q5L24}Vp^kCAS$L3fmx>bK4vFGFf>bHD9mpwwTv z)RyswM0N5~+wMm^S5seHR~hLR{`|-|-%1f8pp@;Jou)PAV@?6wC^XPM#1=JCRtDmK z%vVbYiR|jE`<>9W1E9BD3MDiWVDy4483@7~Tzl15A;O+c6>67Cb_edDXqeTQT!C8F1N~ z*lg3#Cs4e&pSyVmH7Eh)TQndZz}|C-|2l`zZWr-C)L|&@|F?A*$&MxK324;ng2T5T zz*c55!Cm|&?RtiTa%-+KK>e6mXSKC8&B01tQ)-+1<%6QgMdu}R#O*)LTRqOKQ;6xI zwBaz%6^LU!Wa7O=EamHtEejb}&s3FczsLKYfEjn%Z()tUG|bOuL;y^fEXk<;GGRKM zb*un{1AKF>?`iiF0}q2M9RV`+ja?sZdGL_B(ZcS-+V>0*zECqn4CatBz?D_RG($Jq zfv&;U^Zd)E)+fjT$SlqPhQNVRc8%L8cI?YS2(N6YE|OjCfa|*m@gO8NJ{`gQ&%%g- zw>aWApN^?%SFa7D`(+R9)FU!uQ60?!#QRZ8!Z*{;i?J83$_)yU-I|C^!8jkZp4Uu~Eg-i$sEVw~LQtpYob^ zL}-hvn3{x2*sr;2rrUiM4Ud!eB8s#vDk-_L`6>2Uwlz~L@xUlo=n9t2uSk`fcgBS7 z&TYTLdTI?Vc_JihuKTPxv~c|xIGEbXNayJ6OzwB{=y>PASb4c^#o3dh(SnD)m`({6 zqW8gqQsHlvRGs+UtwH%YSI&mmDEYS5ZgVy)%ahjmBrn&iVdw7ai)Zl$z zu>K$q=tyTdFdl;Ju=uJ5tkO~hTj4k1hHW%`+ia#JOn;3Iuk}nmX(b#*e1_yic6(Nl z)%^9I^Q#(0&PAKrdZVqLL?mTG(E)a<>3j?(==xvN7Du|M{+D%pC@kmsVOQO)qdnnF=7CI2X zK1S8Kx|n8np2#~ZmZm6UkffNVPNAGTuOk3%P$pDvL?DeIN9&n=s)0s#z$AjtA){W? zNyDXE8EYduI0yJuq&gb8r=R~k9A$w;)B^5A4oE4CQ@Nd*bc|V9SxK1|6i;p&Bm`}X zGdL`WRfnkAm^UGWW8tHVY0(HT4p!sFN8`-ORT53w!>Pv(7nLc=N(BtyEJgQX+jd^= zXWBa8xNi3c15_S_9Qw7d_luS4uRgv6h>+lcANl2_xjyZ6bz7HiTi*bI2z~^^e+t?} zB)j)9$;oF&WJvdST5`&lA7(-!SO`D9tHVtRU(5zw9xF5Z3xh2~Hvr7_h@JcQ~;k%sE;>h42R`hV%q}636!TVvv9Kb;)5iQ-0s5y_71*wnw z^HUHxb3@)!qm@g5PM~jn4-0e1cogL+dJl5|J;X_|N**H{xDyn8NnVl_Q;6@5lb(#3 zprBsU$!nYMiHV}t=@>4%oKmmH; zDGD)NKCytT=z3(+;-{p}qxlgMaUp4WH9B&-dQ>_9Gl%wK^A;c*`S_$NoZh8lTZb6BlQGjY*NGl4XpJ% zZ0E*Bl-g`M@ek|HC8f{kqSRjKVdywca#EY7Z|hN`Ev;d80bk#O?Mt&%wE?H6HIf@r znV;~Zm*MBWEVuQarUs-*lz#4A^-5Q{PjgCq`aPd#B_`I(%*+giwKvZK$}Ly{ z5Os=bS00vb{S`+wSH%UR%!gT7XQT(4LY>Nec&?R|m6@4$v&}3+kkq_78}f@lRpR@v zDk`q7uE4nzF-V`pEV)mz3Q|CaR}I1a^|W_-o}gq?;;+hN%CKk&0*I{+RL;iWRkPf> z#Px@m#+D{yxU4K^8dYwisWYOE;0RAw<_2*#4JdRj;4*U|caIBC6LXdFg@cGv5-LL4 z+gDcFzoOeXMJz3JPLjd-|6y>cX(erC@xDscQ|=i&BAzM(x+(cO#cI zZ`3?2!l^x*h32yW?J@=5NJlQOYitedEa@-on3Yldk z{VP@)0b29C1jHCS)P1-4${)H8Kv!cP@DNfmdxO2uPaSvWF?aBxHxQln5F zfVSkU(NQLU8wDT#PXn@GT*6i_Ie|VwTfYMuWoZ!Ko?nif*-r4(?{&IFG7-0;FGixR zgn`d3fY2N&4_nh>go5h}$@n$|&j=M>V;HsyzThofO4M%bEqwJLt}CZrZI{;6N*jlm zkQLk--QCsz0%7?cj(5xOc0KV zIvl-?428w@^<7=f?>YD@hL%b=QucQ9Vw<9jWD&*IG{pV59(cHb8;i?48Ie;zqw6}6 z6oL>X#`@I$6v0lDqZf;uSS?S|50KQ_Rg49ltLHCmyLb($UUXFF;kF^Osk#jeqn1O)T<-WC2-X-Q7!JOb z&>tfJI3OU(an8A90iBOmU|~s?D^5|*lrWeU_vTm&L(i&C3rlEIv4ZZYGw|qR_t+Tx zVz|>=oC8gkv*La{cNLB%+DhL!&_#TArw>7%30;#Ai7WpKh&dyy;{i?KitFQ}-M01q z$^UWUGb7mI)lAZ9&R=&dVYvZ#HGN1bq18h+O*O=vgU?7Z&@&qJa{f9slI=`z_GF2a zrjFCG@A=^@J)=;{cZCbce>4?;ARZe8^;c&m!gA}yV-wNOK@T;`0-=u0BRVG*nBH8X zr5b5oykA3#%N$(8M!qqWYe@XXSFqrxrX(hQns+qcc$GJLEMd3?s2D_(l0RhYZm|B~ z7596;`PCTt&baL5aelF)oEiTrQrCsbIF~)RC;w1X9Drd6_?>ZeCubX%_;{FCTyxKF z*G|XWn{x=hJ2i@bFXkv#Uz_m-5kwnB7K);NaEGvLUYTjevt+}ko{qP-t~cYt#YzA) zgUEix^Y!IHP$w?@LroyhoMUg`&B`Sxpxrj}V?COn82KvUNF|oLJ3n;UpZCYzsc~dhEd&tmkDF zf>Z(l7S$A2xHSrBmJh=47S|A>|Dgs!XjvSSuBj(#98>GcmBXvx@nua>FY$dr zfujsdhLRhw=Un%3Yu{A09f^??$(lZHT2tbFa3*VBlj&jrd?VuD;v4Kh-);rn=z-41 z@uZ3SY6?3z90#UZ+}a^M*7eSLqO2#qdCo(4-VXwhDbwm0trRT>eyV1K293i`%SFj3 zL|-r-{|moy`li_eV10=M=sL`WozmRlE_p6m-K3sjS>`be+F{GON^JUeHn< za$XaWQNqOCwo{(oEjk@-V+1%T{>5-8d#T+(pvs?)`M#;nJP$%7fMo}eiSe) z-ki1*gm&_0>v{z3qnx5Z>N;KX8Hu%&?Bbd3$tKjhA1|MVYwb=u5tFkGP3v`nc7krY z`FP-DEj*OE5h^n*B&ZLnm{K((+bY_5ELG$Kk-KVt>-l1LJDl07Y==`Rf%LCN35B+o z?}!}p3ZYFPLYvA;N>WOCNl8jFjETV|RsAcyQJLvp)_VuxD}wX95@Mlz^0b(UgVYa} zrR!TdhKl~7v+P2pd;Jc`L=gO5C@Q?qXE!Amd#h4(PE7hM-N*!SCd5ZkAjq+bILOl; zlGZu%_n4JYBd%e1mxd~C9@lAp1hP_McGu>d9ZP8KCS;d}HV#Qyhs+Hbz{E)hVB&OZ z)VVl23KYBNrNsAIc?$Zu{Q*_>Q@rCm`(47u*|z1qh`ez-8ZfI943Ekcp+q(0X&pxn z`^-H-)wA7WA@$a9CpvPM+yPi6HH^JG>+ZDR(peD)MoT~tNPvHoLqUX{Kp7jxL89)k zgjAog-f^WX4)?XgVa|FnE4fw-E+SpssAw)5{4FayYxolL?;La?oTvj^K;)9Mz%aOh zYCZwf6S@AwGn3IHUsQ1Z@bQ|2pGuRA9@6s&;=>D~L2e3-?GX(15MpaK023)C?U$Lb zYuqhm1&;c#cp|aBM}4O)4fT1YlYU_briiZ%>D#>rah$L?;8sv6i9M4gD0V1qd;*@t zP)8F-b3Y(Q6Jk<#-cp_kX7fi|y2h0%Yy2xyT8*+|r!_N8GT_^VJ}a(z!32|kUy2>B z^A$CW0B0Hz_8Yv!7QD>mL@$V3Fs*`!S>3ptK`#E6RD$6ADurKD2v^c^kP@NjL`W&A z5EXxT5%lypVe@L|pdVTn(J7Gq5AV*|`HNfjrx{0(fm??#wek7p|4>{2E>AnCUiq?I zN!%u7iqNeG83-)N0Li5<>tXaiB1Y=e0=CFs02inBXRXbhAwxI@oe4g75bLf;J5yv* zP<5n9ZUBn4J4{cephgr^=*Kw%v}_?#y_1+GAKlnKymn~TB@9%}Qrs@jP*9+ywS7Xw z_K&|4ckx$>AXW_tacm#UDwM)X9t2<&mvK+T%~=4BC}us>yK@jkP5f?(ZVFfWx?8cKJF#A`8eDZ5vC3Ni5oD|BIhM+W9ywzyYskr5hsZ^)3g^1Ci# z0CE*3vYJS0HuO3@A=7|d9geU8DWb+vDJdgE+^6c7wKhjvT7bQ$KB}+i3Gv_9wyi%R z498_t+9vPKcVS?N!MK+fuTk5d1YR0I^td5cQA0DWKEA$0mtf(uNqIiHREekA**TY> z$n$f1j$Nj|Ym?#hveU`O#`IIJElv9ygD`8!1$LB`lRWBn92A8%&U3m>G{bZ=(-MlV zht;&nu%w(;oJlPsj@!#oKhf89_T}{%WWCQj9?I8T!{SGrsD3}ceF|gjE4r@Fl7d*| zD;&ch3E>zR94nx8z7H9)I@z1L>{#auMA9pv-YE72gbEKBL|h>2A4b~k;aWzqKbr&3 zkRTfUKjTpx%Yg)ci3&l9>>?i8>q%4CwlmN6sB6dz-fL*-C25oODIKlO=Ja1!6T1^@+&fp#BG;3c#YlFpH) zq`-o|+Wk_S;#bZjDf3g{>_ODXOv@kyizE=2)T3 z2U*>+R0|f0%VN|-sk@j|1c^?dm{bXE7r3Ah%fdH0CYi>cRVrNCQ2A59=jb5h7@mz3 z4e_{vNM{<3SYdK{3`Trs`>LG+cBo$>;bl#;jqM78lX5a-cp3!&C(QvOw*n?bA~SM4 zMMb$J3}^EGPkkmI3w2+%YOW(B<;0G05nO4NhxbJ#lO!!VXq+Pmd{Qgv<}1m?6^0BT zkh2x)Bet3SlSxSc;f1I$K?NqJK1*pj|17~)E}mzm=kIm-^Pd-sC!Xx&<+%+UE|q_u z1(+{H`t~I!&(*^z;>!#o`h`X_fvDiU!b~qftYLpy?&5BkLp4uvN5nbiWM_H*l?CBK z`8iXkq%l+fyz_|hO}fO$SOHNAx<0!ILS?W zn5Z#=smC;7Q~tIIKx+_KFInEpyoz~e1# z--E{WB!8}iu`u!bY3Bi}aCiUH#w)Tm6cnzfy`bGXpdi-C=G}yPM#-kKCHD+U6-WwJ zF1IL)?LcO65iMF5g&@v_F2}5aH3v7X)of{)N30UWFbYw4O_uMDH5bM>RX0XS4R^p7P5zJ3o+AF_#IIaPD>0 z$g)HgiS|%(M|ZHx%gep6o8v`M&Psle%=T<$#~j6O z5#SaSllRhVRK5NNz%WBWfZp?xXlmk?$*$x{$!LZGWt5*)G!MMzLxYq-iv8W_>sV-u zDOXENE63x~`vGSCXZ>HyY+kG%A|1h3$WNee!f(jA{lQ*=pN;J2wDm|hD-DtgKZ$Xg zwG*l{$!UH#LrqEjt22~HbIc?|DFWWwl=C!$VDbQ}J=|xd4O)h|&NjgjX53vigB{+NRuFqMrvRI(&1B`NU!etTb; zDyYg3y8CW2Q8|N(=Ykf?5C~4=PeDK~2BQZq#cw4OF@$XR(yv{GCA6Uj*g-X$l<*$o zq@ZH?_H2@2SK9^SLynW&$B>jo+W`oyfnKNJ!-m9$d?$ch3>5OQ!T=r|CkpBjbbK3n zk|HmiRfRn^%j@_G;x$yuwO(V1yoQKjJ|j!y!UzC^ja6nFxl5~NWQlZ0xV}Bw+c9P- zi%e-aEhyMm3wW2iK{((c(c1POkS`o{1@4f184Nz& zR%CW%1lopZQYv;q@OJ4$=HI=yi3r^>UBf}A=u0f_lxp3T6MCh{!R>6@54@0R42MiV z^pKr@=pj-aAK@1NWj*8ueC{85h*Qu~!rR!}+$07Sb4jEA%QR%k_^NcdfbkdD11uCl zHQC_{`K;;4F&c`|%9Jkt``B!n3JctWZ_3KX%-RqJQk=5?Du$pcy!Kvte^>^o}fqythF{m2-_c=%X zKXQu#f&AZdiz1D}4IazK+2_{6wNM^3masc1ZA(`5ruA&DA$B5BmnZ=#Z6CYz9=kkD z-oN{epA13mZ1PizJ}UHbFyaT{EY$=2 z?_JLRHt)vTYp7I7d9Za5b8JH(d(WYFnB4?`HNx1KC8glT=@Qgiz}7J}UKU4Nv-ycOAA7sWpz)x!5@pJ&jErT3m8b6=fR%UCh(Xwl6zj&;v zEED4B9qg=&757>4#|giG}O1I{@?S%~pj^OOy_sHJ+Bd$L8N#6bqM(?1~M_3@*l|8-LI>WWEnYY_Y!j8O#q^g7oXZW z16fJNAdDXqJS!*&ebali+jCFoT4l&_#w`4+?Qj^$E(C7y&?7&0i^Zm6jSs)quR(ft z0;$yZ5Os3IoUubr9WwYZogWF;7G2tOqS`}FYt;){uAjI#Iv>xbs&D{+LP!o0re_SO z*~J^QW+@ zV}JxRjNp-l19(|iGbK4w1;yS1YfkJ99UOK;7K%7F56!8=iYtCSckk4{&YaG0)@x%- zd>096rFq^d$Oi42FMN0Uo|Wl)4?sY=S1mZg(c{C>N*e^jj0tnKz1%~6uc{b%pXq7+ zyDYmCi|;L4-5grIjV(}yB#(yE!!%gT(Xp~6DeJ|{@Vj00BC#B7L z6=p^*o|EQ=k5aN>c9d9Jxk+cfco5A=+>LZv9}sJ?YE0+S7}Xun(~&ySj>rDX0oDBw%(&`?=Y0%^vTjYdZRbCt zPw0WsI&y$kl*&UO#&An~x9+C>T8JXNX z_1SOQfThJNY3je^63Ld8o_U9YWv?$GBk)V0c(Wr?63{$I=}F>nkj1*@N=whk6=`XB zZ;$2AhFYL$h>#!ni7X?DDGtXE<3|@~51-e>*iP6&#g#@jciyGPuW=W|qTR9d$XCLA zI&NWYo=3Mm5-j8FyNiSgFFkLW+1r}91<3i_`fv0QQTRnWr}9g#HqLpnvw6{k-4nwJ z{rw2BJc=i62Y%ykV>vCp3wKw<-^lNA_0Mm3U}AT;-?^6)-WOcG%lJ3}30Aj!Qlg2qtS)KNEES+UFH`RYO5Mv66$Al+T3rRR|G9*53HV0^nw)pr$C za}YTd(_MSsN0#JpgX?__+wOB%d=!E)=H6>`0_3>Aj`3=s zpYANj{(gvF68k@;QJ4xvD$v5<3E`p%(=?*SRAOvNp$ecq6;uf7Rwa`H1i9?$nz7pE4V;kBO7mG75a zKiliqv+#IE%Tyh=$vwWO(aIN#Tx9<3@J5FB!O1C|JBanfSJvfFT4(vWL-aaf<9`)M z`e2N)-}ybyLC_bSPh-#eFw;Ik07H@~!05b#CU6uf!(31QKfv_SXh`NFqBZG^F@t;QD73pv6x#$i2>d}q?gr-F`F05fzFOP|F| zZ<*$FoX+Aj&$UN3-mtEI1z?emcU;d1s$3Jo1?19+Z`X<0aX%0|4Sq#i zn@D)US0l*4?&<0^*G>bn{+gq!bfTU|N)uk<&PX6XUU8ln;(WU*uRSh*k80x(Ju%k^+qrv)* zBVXq1h6^zuB9qK@RL!i_|zqUG{{DqL{Bmi(KzJ;YDBcLEbNDlVfG@ij>a zoZ3MyDiI{yU1==IpxGcCO@qs-zpD#}1==r+3^vj;pKIMW=C2_hv7!*d9{ z3#@y9C|m1!%t>UN>>)Vnbm}|Y@}_w7zlr#3OqHMrr|Qmb%}#Ep_p0N(sIfYT>rqIg zs)qU@XB8PEX7>xL>#e1Akt>90wT2zweFgG{f6R#^6dmK#Fx_jU|Jl={;#2{aT?&dQ z=k8AtB1aV_OC>E?>H3T6&WLp?t*cf}Mbtz-voyvpx^sD=gU!_{{Kk*uP6irt_QW!$ zis$TrsB)Q_*VB4EZ;4?vVjHLG4x?m^*V8MbMV-1i;!t+B7VeVv&;s{_YdmyL_ynGw z?=!5KEyHi4FfgRRk*Y^T|L~EyjVSV5+z8$^a6L5nGAlELWTQR<>k5GdGfJMJGR@GA z3J1g2^x9;^;)@+Iw_W;nPnc!wJ_R%uiK95^Fa$*v7rAa*XP~L&-i%(Ve424F=Ym3D zd0He@VzEC<42kN0n4?tyEDp|76@$m?vDO7m#6R^8{C?4||7QmrK)%$S;jFpI+;#v4 z1~a5Tv5`S#VN+K-DOR0Dw zRZ&-6j9lW&iOwY)r+iupgH;2Uq?=b(EsmB8BQ@PwU6gK}oRv`FmUQOJS+z27V$G<2 zite|xZWKE#(pkk&J1@5KScuwPsXA{VH=Xv|v!Ejr|C9g&*UcWYkf8bo2^YHVGAd(O-WfVMYVM~&P9VIs^6IV^K zkZPsF=Ch28o7h!wYrI$^589?5PMV6V>=!wd=uh67fK;5D zo$}spS6iRr9BgWBKly~q*9xLsU4b)DU*DbcStZFUpkpl<96YzYf84O6XcY3Ce`8B$? zaCOzfHBKaS@zWBD;#7HrJ7YzedU=oq%yANz>O)T8{)&bTa~%3k$=9;P==X8VZ_o}esbdsWoB}TFQ`6wZN z0t2-Z3jt=35CbfUDJXD8e0~=0xt9x)UDQo3LoSaauiIPU;l$@*iIKyWrme{_xB(K< z^vzylmmtk{ZLS#^o%DBv3AG|w^Qa<<^~FtxMo*)&t5P+(&PQ}7Q99T2;q|JW4t1+^ zlM#*gJjZw;;|^X~#S#$YGXh{^VdAHtR&J}=GlN3~RKV&I0N(=xa}e+9m4PjM`q(7? zGvvT4t$QpF9n`_*G58WW&SUYG>6PY9JV$$ztPQpMRjJzX+v`g@82s@CX$PK0v23q zT~-o@RwzZw#@bho@fN7_UE+BIM@&}rTluS5+b@r~3AuUyggMr^?!v07ITRNawKEI+ zt07T?A|07cWX4QykILWST8vk~4W5L+5Ttcwl~*!d-A}C@+}W~+E?Q5JLBp0hTkF{4t7T85m2S?>YejE+kkwzdvq7Mrhb(0#@_+qgXRCA=zz zelobV+vvHwsF=cN3rH)=8mdUCxj*(r%H0d%Q%MEsG+a}jbp858O1_1m zX_berT$A9lizL6O(NW<)&&$m#Z#&weMV>(oyFZaYRGEt2%(}#neV~c1(h5vwwX4p~ z?m}}(KluX%8~^>=;IbeoI8AC!+_W-u7?YDyjelv%^P0X&gSF*28f%NLXgX3FT^nwEw zZZLfnNc@@>iUR)EXU6{-*WdGQ{niKK^Q|~wK?Df6G`<+$Nb7I5B@O(lj77Y96LoWJ z!hIIyrR3zT+GNb5i(E`H@Z?qN)@DJ6w?-pECNfM)nwHx7$?$^L>$vXHi zBt4BbH{UK1s5Xx@>rT=`=GM9SF_^0JHMH7g&7gO!Dn_e=HxCfyXDx=0_V-iik#rFq zmmYSS)p=s^y9pj50ryhCP0x;=xYye*(@0nWk@*#v3@8H4OakN2O|P{9QsZq+5EeT{ z9Qo?-5F=;t2xwI*;rTGHj8+!XOrL{aNd*BR zOaha>F1bb>-JvK=qBMwL?vuZgfXSylG z6If2n8;JXmC%3jtR7J)@SGMt5^;-#UdDT_#+`{o}7@h9cuV{`9>IuoIpt!_DWy%BM znXlMK=U4I$VZQLH##Y)+T?m{=|<}oL@}GQxixco2C~}kJWD;MuXVzIf>JJ8AgwLL_U-C6D2I+pi&79L{t?R7_usr1OdCGm9W20@f7mNZNLD_4|c(^~LVZ z;`BstN_Nk&vP(=^4z}#5dcRi*LNBhA6ru+Kz5UXU;dj(2(s=Vy)cIVgPOI^rX1)1> zs)SeFfv=@a?!w|8CoV2OPjUFQGjrqLLKjxL_m$=t#@&o`KuotOZG(M$?fO01kJ3X< zQSYO~;h7h`Ob^(YTUR^obTl5TJzRne8F{lcSlTSVTeYkL-n(^Sah;okHA=U=UE{Zd z_#gK(vd|IS+09S7kC^heuCVZ~CyaaMFM0S*4!7?ll_ZwF_>lE6@O;l;F8lnt?Y)vX zvsn5Y>l(fnTbZKf*jl`m+7`>=&wOsr1`X+HnZEOO59_X*Hm4xlECo+v&YZW}OdLze z)?4hyE}H7Ra{SA)obKQ9?}$pH5lb&gR0IZxT7x;dHb823dPV1s!n9;6q!v6%k;^GK z$w!V|Z6x#`6<4?H)OF8NlHn$O80RU?I0zwp^%NTdmpe;_8rlcq(5 z!rbpuj37Fwnc8C2g_yY}{ZoAXjTAOmIT>8|0PEO9-+Z!Oo zIXoOHjTRD$0MnxQBEgOPR#X%4mlvEu29rrB+9G#Rcc>mE?7dk=4i4x0UzBFQxjP>q zxiRO$v6S)LQf@YD1bV;-HvQVE%rY2DJbd+HaAi|GA1pS zCgCE}Yh80$M-M$7PS+@+$q&^O{%{t_x#}q zg`piZ7h`tB93A6_jl>xC&+DCbTRM3&mI7&WvoqxYJ|;PY2o9sZ?Ljb)yTwe|N>BMk zc=Vo24=nwYU=||Dx4ZncmnwXG=gc2C>)-km7URYAi$uZ>20B7w=iTQ%ZL@Y4>sxhU z&p>DmMQv6ybR3~N**)NFWN6K`IC?YbBC!_7lnwC+T1SWpmEXHz#UGSUfhUch)n%j{ zhZB2a>Ij^IZ6vfI5AkQ*71N7y)+Pkmb)T7b1xiv9XIPRJxX^Jc7fi05``mvjXj`zZ zwebr#1yxg8NQjuVvF^*_LLTCswhcEDi27|7lr7&}$iXc@NG!|{>d>w#Zi zqwC`6t!jC2xbBiMmQvCAABO0C7@3%uI5;qpc5_=@1?5r00k^(fc2EN$!7sBfuWIui zhFhPptSxBAzmlQ_p8Xp9!3VFcAOgc~i-a$lV^yt(;5*@aaqcOlw88f76ukqaM5ozd zcio*M>kQwdHF2UOxFh`NU(7=rEkwypi}@W@DW(bI3j643qu93t{W?REamdSSXA&e3 z4$F|o*CuNZ!>|DbgPerMykseEmf4qZxOURKz{Kc*hLPl(;tK!IuW_iIH!n{Zze#3! z?z@T!!8H^|N`oYt_O;Fx|Zlny|4PXT(^lH4BK zO9d_%bi{J9?2<2x2!C5%g6A7^H_no4;#1S4DUQK_JRC}@7(~_9J69p9^oK3B9*nz% zT-^)Oo~I;(y^#E|^zJUow0iX*H0}wpmX8b-4|whs6CPd`Po8nW%Q6x2_Fl~Io}(Bu zF6q)`p?a)MN)>(YG#!tn`KBlHY9IK@4?m>Fc6Ru4(~hn5Et&-!C>^R`*<=b$SMclE zkPZEw_6(Z<+MC@}`1#Vgw*2~QsF_c>vj_@_21?q=wBJZz)fh7KL^dQUfy7! z4(#Ct@~+fPo0fY$xE?&D6KDi?0;d>e7B32pgR4jR8SQDWMKftRxtJKU4KaJmFg zGFYMuO|f3ai%huNJ%!699Xm17AVxFr`yJJ~j387T557+>-mBenU&{32frK$qr;Hk8 zbpmi9YUDHs;EjdBh#W!W_9v*T&U*R>2^HMihrvA{Fv7PeAJ(}$!Wpd@--)QlY6wOd z%?vv+2IpWpR4^(d(VFi--cWy)o0=!bQ`IKT-5rt#shB>)r}zbmxCa|4sq7R%T=82z z-b=hT7ry%C6khjGYe`yKWtkV9+10ZSIEH&aD9$}ipF_|PXM658xb%Vhyx^ASJPpa4 zB(CV=yOPoCW@KUbJlgKcjE@Fe8_GO@%==AkJ7|D=JO?W~^jVv9JB{u8VFOE>bq73R zN$*)q-X4I3PB1O#<#Z2e+!6t*OL*fihl_MtiVshGj=Ep1AIaG+Y!r$cWJugRg2Rn` z>QUyN3}+x4{o@HQIjfWkxCl$XEhr>q+tO8!qwbT9StVi?Wb*Scnqgbj#&1cYk$?p) zrbFX4r6nXSc-$)w5e4xmroAJg4}`R~N_3S@jV3A{hsp1Q;Hr`~uV84DC?#RU z^_>oZZzM8S8}(zw<|mfYRB3`xZyOFh7>YzEmr8}*Ab$%drNqSQp{%W|p*@tgmb7-9 z2{X+rCS%0*!0Fg9$sh%^(Q@}sE0uJnubWTwA# z*yg*vpJQ-1ulo9|gxGLTIr}{r%Z4M2{_MFTmyZVehBVzzPUnN_TvVjp7LFb?ee!?Q zd!P0|V=v!3!_J z*cKKSyKe4EFCbv#82(iETlk-*b^O-1U?E>ZAKcm8m{&TsKttgxQw>J(&!t&i1b%%=vbokz3cNYq)H2S>YWx zwC^z_RndLB=~7oXJIPZ6DO=#{!%$NWpvK7C?_lBrs3(-&QFM#-tX$Cb4VY)tX%q?6 z8V(1)o2s+8u{7MHU7$hqs7427l{g(<4HM)U*f58!G$^^)4lti>JG&}}pBX+t%fe^h zH47t>{!>FdX)Wrb9q#(C?eNce87?CEF@CYIToHc?C(fZDBackDO@(wF9i9uLm8l%E z88uWIRHNeW9-*01MM;n}d7X~ow0>Q+@F@cf)f*e?eJybzVhnZ5x-JK^UX*_Eh_3Kz zo>f$<>_aw^KiVv&G7FIF_y?WTjRil|-QB3QLm{h^ZlCgODh!G$GrX}}S1J(MW^RLp zV}me0VEF2R{iAL14B6P&i2CK#bnyN1_`5mQ?-zYvg7L``Om| z9-+4KeYO3)^zJwRJ0P&n3wQt?ElOZ(+w8yU)R z70?+Eof5MCcRJ%~QGLLqih6PSeJaypGZy(ods zI!YelYNZ;X&0~T}sg@B#=O8DS-O7=IS3q-D9tB%ph(xZ3a1FbY5o=tRZ7Og7BZKd6hwsk zS4VM6j_88_{hELEEw%VvY*1N#sGKMuv{tT?1(6wW#&U?vYqLJIjMg6YxZ1LWRk_+Z zWQdw)YJLw92SO?|^N%KX$o{s@VVn|Cc6>pIY;~HxCgNq%dIzOHQCN;R(UAV%FTGh0 z7*viDbkj!ppnx-BOKqA8S`ldhnGy+AGopLU;SCMol3y=~4NJlnCO{>unsBm{MkG8I zr-TtnOngsDBmVm}))N5Q5Ax!|GntHVpF=!^h0dsxfA#r(=_iMRTM`Y405f0C;wT}p zL!2sy6EtutVEFjF(WL%VE3)*P;ymsb&@5{+oNc!EDXJ>48|^3Avfxc?nV+IFgls-NNKAtN3d{2neGd=?Bzyj zQqH`Uef`z{e6f15UTsA6^yj#?Tt4Vom!`rSANJQ-+Yi#q4AGC5sisUA?=jS_TCO^S z$ip>`PJ>w;!ne;+SjAB|T&x;8{k=DB{mg_tQs{s5_SRu>G~J&!5Zr_9>RCjN&a(IO@M4`xzph1 z>$@|YyqN(@PJN7vQVQ8FDj%FOtPnGfq!JCMg$s_J)#2@6v=pTh&P@V#3jtQSiQ{ZV zom2KQ!hafe;n5ej#r|-GCVA|QI_6%tM<3*8=6MU#>RH?s7*1bSnYn)ai3N4k87(@Q zGSu}K{rx$qk0xf!Qw4k_hHk;M_sw6jjxdp}#QxH!_w1{WT1$9l$@rdAOf;I6Lx<#t z6O54tdTtn)(_7y13zC8Pyk_#Jlw!pq=1c14uUcuiU*)Cj$j6ocU-;P#4$Jiw)V7n| zJg@E8T1zMMQmxvi79FSjQ$dJeaTi^6C`sCnPSkxU zY^4pVg4e@AynQtoiLOMpxZy156R4}IHN)ERS#EzJGON`pwt5gRznd-t_A`n$2~<*C z{CM)H%@rZl+tLG+mx(8a&weG&(yz^|N@)gw|Poo9uJT4^Zaa+k% zED%6AE~&p<;qMevve}3+m(Ifvj|j1%<_G;-eSiNlAbIky2l1sdT9eg%s=@up%b zUn(`!Q!HR}6sre^Uh6o(S}25cX)E{-P8z?KTQtJQM22nd@VAQ z8aev^$#L&)8c96fS7B{^q`sk>R7Szh|7>W|KnJUwz4u%uvd^36Ln%xVkL4n1KKj1V z3E`2^&RFdJ%Vf9T&KEWGJKsKfwIHlzQ^@u^2r5*i^8j#F3w`#jzwyaTJu*w3E`rzuo5L$5{|OIndI&fsb`7 z6_A|~-0>79$jSjatOW8jG3!jfy`k&rAAQTvby#{l$8aFgx&OfurC&S_5PjkIl$Shk zv-w`rqU7~7-uv6g`ZIRyZ|;jYI5`(n2T_grhG@$i9|FXXV62ShVZ&bBg(MzO*~MV}8|e zp6vbpQ@+lp8e-L&z4Xg3A7Da7E`G8FYKy)Uq_@@fA7|-RDr8cfnT-AR#a^oIv*b3{ z;reDuS$Vusy^8RJM3s9cQx&@lcNG8%3>p*pBFq7WEh9Dc{%Oy}N#dH!WASPO*PU_~ zQj4MlI4_?s$}%8^GR!HLTQB{HCb2^m@=|s48O!rSmo)Ao0^>)mKF{Z+YHGBJI0y}E zZ-y=Eob#G<3f&R>ngj6*fgsWlt>u9@_n`pKz1vu}{*= z5r1z>h$Jt=_9`GBk2+qoV6sSY=#EV&vb#6Hou~OVT0s|r%&5+o2)JguDU6(jh3vfk zz!7*A_*Z#%{o>H|B5JGMS*%VYJ0+rS`2qz1?M}%*ctrN;?muya*2>>-#4?G@X@*BMC@4<4-b^=ZyC=aV(cE75v((K zdS!`l1we5|1aL-BWPx>^#|rL}blkqph*dq6FBUABOKfc~F?;K^2-J}3n>sX+Q}#I> zf8D9sCstC!lHfC>pIb9(zbHY47iAt*SQCh+Tzd1aRBLdn6ztW$K5ywH^i-rE847mB zRb0t5vGnbnPYT$Q?H6FBE@MSbG%l;DhGy<`aw4Q;%GYU*s8v)_Cu=@-!25Xfn|=77hSr*&_c%4xca-k z3RmWdquA`c_;_&%B%)2z2Qe%56(?v7gJ08-wG~mHWYcxX)q4b!9S9Z@!-LSxd!V{G z_o?ZLJK3S3g1diO!h}WMS`?C-`Q-RsytY)4K-z>gAWC^b36AmFGKkO%J zKg49x@ZnJ4QI+Pgl0tpx0t{$k<#vA(3^>1r3V(@q5G6(5@mH=P&0pA`3(V+4czN=Y zo|QLLl%DIZQ_zj$k4~|jc*;g_xF-zjYr3s>@8%aC!>0nDTy1`o2peT%X)owonI~So+M|zeXQvjiV8ApAI-0LyFw9|embc5QX+DH%@O`}%7ND4YKBBGuacPS?SYH!cF zU?ij=)&=hf85@mi1UD#$aUU$|DAfAp#?Cg zS0@=0*RwlZ9v*nJ<>c0uS&wW$pFT3AXlH9`XoT$fLcfL-|V;I@i&ICYH^X{q#S)<&!Kq>0%T@0orP_X|yriG5V0mT#WlQaF4-Vvl

1lf&&11{hYEA45#QvQXM@21%0LAo5UDDiTlF>SeRI?i=cJ4IXFzQ%YGj1@&E~tZ zKz2)guo^S5=D6hQ9HgoHBuvjoKhf14FkLtMk(=~N>yGnj3cVg!ex~fGF?z<*#pqa$ z%H3YaRjrI+Gm%94@F-8?1Cq_F&%q?Ro zN5<~Y73$4LzTx4(tKgYSyP5+s-@U$_W8(T0IQj_@I>NMr^CATTIJ2 zJkVXO{ivYa=~ZL?q?i|n!fi_5C1KeVnQSZ>uDKYvs)rufpm!xl^fvZRY_jbtk8)Qf z7HM?$QzDgL!;9w|%6Vfvz&Zs<%BvGGConoLmfPl4PaiY->jg_3Jwz-(JX&3@fy1Ik zWm=?&-R_@Mh18GuyF)i-%%+dR5XHDAvjce1MWQ|fa&x@Lf^+W2U33)fZ;xRl1;m4Z z#H9rg1rslAx^1dDqY|#WLyI4X=@-RU zUsjCz``v7_O>3(0){Qp2e+6#1gMo57T!-0L)H!HPy#q(}?Eg+velB4uj>IB2kw=V} zNV8RZf02y(+CKmS3hI@@Gg>=EC_obEW9l$P22GMj(q#2+pRU!xYzXMX)(ETCl<58J z&Q(AtN@fendb!htnrcYKDHPrNUGxUASz9xmh{- zPW~IAQihgjR-AT1APHqIt|ikJ$JBMJ)Qc0t4jYijeZT8ThJwUl?Q(fu;2dD;GNE8} z5mz6Tt{}=WeSRCRn>;2m&~x_QK-?gw*1Hmlh{|RE`!&<3M7HaRyK$2XCjWd0AW46P z5T+DH+*88#gNp&vVU(>b*vlRC&QOb2S&?aN!_(~Plu0fc7EOx4T!KX5gt^6px#T=v zsVF0=@S|BCQXN+X-h)8$`R}m+#3A$frUy>5*iMnvbKl5>LU<3sJkD~@j4q@=6*jmK znrc@H6naHojx3=v!7<=-l9kG$t{GJEKDF4HhMnUWhrH*}dEDVYr$>L=H$G=(zlnvR z&-d=8gt6^~-jcqwXPozOT|jjIEw$O{ z*-#w8dxO@b`%TCPZ`{x_tEI`R_H`@SCmeHlCuU1wmi82yCf}tc;AF19KI4NA`%^a? zMVoHB4WDBUK}sIb2}3Sk$oT`N)Ls6BDTz?OuK-%3KpH6UBWg09yW@4)5O{XXZH5uN z2(@w)kU4|iI1MJ@WCKW~%3sQy0XhU&19yc$n9{0SP)J04h9&ebn1XWkCrtU``#Vg@ z5&TauCGzm^FomyX=0m%?DF+j9pX*g?viUdQTz$0R7=G$*`iZcHj!cf9dj|_B<>+`) z)r(scB^L`aJG_GDt3i|g14)tBjU0U`HzUg{vdf5R>K>SZA0xY27n3;TX(y-*cQ}a{ zTYzZ3KX=aUonr7JPMvzbZ(woVpV;a-8}i06RTQPyf_v3VkOXOknApuczQ~Xk3)d}u znJ2|qF;0Zik5o}5A`5TN=hnGi4PTd7V8f)^+ArPigNm)`Ru`s<*48$rmAI(!3`2|a zS;{n7{A>$T>iJU z-u%pVGxD@XANOziYq@b=>jg;qF@56$O6W7-LQy;K!ZUGjarSMfWVOU#zvN#e0NM|q zRQhE}@c=`FM2hFR`9{Hy&&^#ZUve4B$jV0eND=@EDLqzuT1RfE`2jv-kq9jm-Ew#m z#dC5}H!A$}yRMoy6@Je6^J)zyj{4tEn$mGPA?3>uMjrVqO;XKQ+dRc0alfzWu@R+4 z-r(xDVk$R_RMk$Qd;>4iHhfC2vtx(HZHG*f+UURosZp6CT6ayT;GSL6)cP0&qONR+>u*26%$$p0cO9-yNN5) zCIs9O{GcvGnIf)vajN!gOv*#slGT%85=s*C$?MXTX#wEY^))479DFYeDCKaf^k`7^L)+bnlPlJjR2ilFHt?Du?CZ{E+D#-JbJ4GaVBsMoX zj4(FzM)`p;aug|iBcI}-!rau6b@t~q4c09^FUv1k89fEh#O{-E_gqQ;L@y4~O?7r**e&v6 zyulmURCBH=c`B4KElj}?AWV02u73e9V#s%!1TS+dE6)cn4+%uO>EmqeD!X;DGTt6* z)1%&Z=M;h7FG`{Vopq^$y6(qpa|-D=4uyK)V6U%o#G>$>D}4LqF53ycGCMebPV+v3 zd#O7k21gZn-Cj+~**kXz8W|f4c|3bq>w|hQ9gj|Jw=>^8P~@zgk!DYdNeU!}-YxWr zik6KQ`umIbR(#2xx+pvx+yf7ZNT+Ed*@ek`5&fV_0TUH<8819UZ_?X~mrw;aBOzm^ ztQCHNq3QZ1Rq=-yLG-_i5k7KRvGaU;ckqTkfn`akR=Z%fQLnAhZB1|`yVzLg3p3w) zN8F#tC2Ic<>@zh{63`cj` z>)Im_Ft|7oSFWmNVBF)j>jUywXEy$C%mvb((i5(SH1Trl4cuBH(Csg!cnb6^j1n>y z53#dbq&mbF-f?ah;oz%|*M0aT!XZFEA*Ts1eUvUo)@h#tOUVw&MX)E5(hq{1U2nuC z7i7HU8!s{vg<9$pTNjh6`x6BG7=wuW^>I>@Y%RJB4JlhiB`7C;d4^B8!3aFBkV~u0ViVfFVr&R*mNqo6D&_p)Xx#M)$b>;f^C}2mAa~R#4uIcW8Se@ z)v+wKzC;iD7L<^_%qj>J?(;3W$8QHUIZbnPd_ioSh-nbvCV4kP2X36*Ce$-|D@v$_ zCUj+mLiJk@P+O^D?}n9sY%?cSG5J`3B7T;Zj~yBzG-}G79=sB}&07~^hk{dR^kHj9 zZB!;C+sM3#EE{@aWfnmM=1uNTFZ$WM7|5tfz!SfcqT-d?(?{D2=FVMs0NOexBTtz zugp_}9#oU%G8)^ekK;KK>B3_oC!cmQ_GOkgPLm(NmMB>D-rwi8c++sR*yWX#SiRGD z+cE!0Iz{)L!ccXvMxa=;@r?gb&%?Q7?scgP+@HMS#1cF|KVQ(>R+E&B48X8?FkNB` z!{cKyhlGxvKW#w-3ukzCH~r9*t>F4|UVI|-J6&NlM5O-jbmd*%8RTz^ydQ#;rCn$r zci&5SpUaJ`Abg-^2|OvM^E-3H3!~*`Uj&Ypkez6sr_OpI|DQ#GL&%SkKVQz?3xVLG z+wYDBydVa>dbS6Jg_>mYC(b4j_5maL!7iGiJJ@ZPs*2z>Us63s%k{R(16l`y- zCX_?+tyQF}^BQ*z~x7az~F5aeNpj7fztSuZkd zlIgK6@{Uh|MpQ@7i9y6_nT__Km{+Nyg10-ItTI&CJt7N z3Vb}LFk8_te>?t9e|ysxgu29S#spBS?sYH0`ELfrMw*X^(Bo%Ap+hNfm}Y7}mz_wVgwCP5gDxG|(kN=scU?AP+O-4i5vKd=uvl9t_? z*JQ{Bx|fnji(Svl(pc8k&~-2fNS*4Jf6WC78Ls)qtmyI$M)lAMPIfWno5F)@a%OWS z%g$})JTmdXM}x9W>>?ab`wj7{lLOdVZ;w#lDLaJ|0~c1Ufgqf;4Ih}nd-b>^Y-|{u zsTx@PC9s6va)txmHO%2evUjQs3|5xq-t6Nw^o6z%(o_GyBm+r4lJM&Ed_?I5Xh_C;xmvH} zs7APGX3GHt-YULL(%59ez(Vlz z`{2)naUAZkd&fhzZKN-{0QS*#S?PR;Cb><9Y+>w0O~RGteRNDrY{C~SiBd!q;73Kd zEorpknwWO%JcbI-pkrrI>mCdc+uX4;4k6NfC$Y<3aBEyJGQ<=D?-Q|) z^}2C%!x=@kZ&~D&X5D+&<=$vpnQ$xC*zr$Bp=DI+Xr}?y6-TXcy<205>@Z|un~4m$ zr@GaSw&N}LQ|R4e^KDdyLEt~FOnyMe`1p9LO&b>{C-e>?xtVRG#@(qt*lvnoVM&Sm z{;}Np*rh?3z)urFNz#!FSBaAbx^C6~Bfe+8IlO01{~D#IDpK+6qq1M3VJ(&cL_xB9 z_Z!GcDF{#5`o#svssmw1e%wM4CMAB^-GNod=3_=%4rNCdm{?4i=*9){FYNX($qiW= zSw^sfVIt7R7~l33EF?`=@$s45=rgFP$;(7MnCqwET_`cc#CM$*j)&cAcsmdZYc=7N zGr~!ABYhgPifQzaWhI$HxWqsC1%E&)kT3X$mY_Yjv&7Jcm5HuS@Jc;QH zMb^|f#3v~-;m&<<&}L_vq2eb_mhAq`G(!LkzeYY?S9k{?@UJ^eAI?_rq>s+(xPMq~ za;#{(Ke)T&RHsNBg5dE4Tehg{s(fW?Ks}|y+ z>#@wJuZ9U;GwXJk0TQ(1Mm3!x=e$45=P|QJ^cU1hDOK+W>Bv%1n^C^%c{IJH+L2M- zaHV=dWIdKIV&zVkjo0X_fcc0^PrtM{@A;YYjkVdw8#i*|xqCh-7eyueK}0DNieNI% zzy&xZ6?i!neV+DFXjQ}{Sgb(q2w0=4Osr(-*a6H)KzDNSR9f0F^S4`ju@^wgMr%49 zxu}ZF!OAF;td{p=q_V23p83J`53k+K7^-s%lqEhh(bm?MGVFbS&0^p^&tg;GK*M)+ zQ)g3C(^I4=%0E!XyZQf{D5HS3=s%*2M#^C?l)SZUR>4Y=yDI~Whg{@>Z^$5zKJHa5 z+&;1Ios!;*_CPKI-pWONx;04Z5ah7*Fga(a`v%!(Z}MNY7E#wP$XXL)Dw{MzLNNbXiXqMdlwx$<1xzPM=EnPtqW0;wx_3VQ&n$z}v+nX^mhtZCeOx+8 z@h&iiMXf7WiieU)Xb;fRqpgYaVlM+W7TsT?6uK`*2KZ#qGh?X{r&8T+Id@#kZnGyI zwLFU0oW5yldC4;I60NZaHEDq@n#^H3EBM}JP%~?lAgwZ^+$~?9r_tt83^;A(SC(E`cAY?a!XYMXe`tJRp zR%y8x#}D2wzg6<8J@u=9&7K<4_?-hGXw~y}d4?ZJmZUgSD^aPP%+FrC5z?#1I?-`@ z)6O8~?b2_83}>nyb6%J?K#)}SZtoi3F#|$5eSPBlcknqV5Y+W+fQWg()$DT^wbj6GkfH z(2X!N4yEPN8JSSeGsuy@yKT`{T=-^uufWE6@ol`=0rlw5Ny?uFbZGH<}Kq~)^# zG&}MjY*f7;N7QAIOC^hbB8>P)%)tYtLBtS@GFNSfShU2{jB00_mB9N@{acHwg^ME= zDsq+YN@dLwCt2PShLDTNsr5LL%=}nX+OO194N6ZKKWNF~zIJk$TYpF$d3<(KWNfhK z3a7_S=%n@9w#^fnm7LIg zH3(31qP)lV7PZzKd1tLw80O^!be&>&oChKYk}1vlwfHI^B#Y5s)q+(NH4S>_F1(Nbf8h#Kq5 zMt@Q8$FAWLv!r3>=36>P_|^g`=@*I|d(dN;L&HKt;s!ouw0MlfdDH?8jm7oZu#l?S z6V02)Yy=xC^|lY!T~&tdg=BAX7H7IPS7P=~Lj;k1NqY!tt8P|wgmZXMU9ttV* zOpD$!Ti>BO$n94un?78(`TqLZQ%+)VaVN7}0atL4%o4+TWwxehG)vV|`zik;3PCaB zLX4>~Ra~_k!?t=DQF~VTsrG%nJ5xn6n$qxB3jASRbN}uU3gD z0Nx(pm&Q8nGgB-YOV5YS7A%UP;crF=tcFOH_gUHYS@Vt%RY~s7XuN6_gBQ|f5o3C+ zCRrStEfILc-XHD7eiq4izsm2T=)(LR7>abB2C1!aI$3Ct?R`_{C)ZUAEy_}$L2#&B z%1!RNIf}l?F^E#*)N(iC$v71k^j9)r3iJK1WTGeNoEqt;nj^qj6^gK1a z{RLE~E5np5Rrw4>+myV3BKkw8ch2g%y5$r!_{6~Vj9Z_VKdyHsW?18i!+P#21=u_I z*dGg9`-qN7If18Pma&dVO!P2_QF>sq32X#fYI_$5rwbggIJynHJ$;Cq@y&72i#qTW|P2`YMU%_rX? zk$iAlMhhK%$Co$ch=9RKHumwu_)~H-e(Rd;`@n^v z$$HEZ9ix2Rj(I=2)`fR8u)3?J za8;=k(xWHUEhq;^ITBTkVeLN|Q8Hz~Tn16-eRFk>0GBH!q#`WcP~I!-jmyoQY0jih z=mO^i?yuHcNkmVt$Z&IVm+SZ*vgv`ZId`r?P)Z#|=F&hgBTi+N0^#tWIXQps>Jnqb zp9RB~f*U?9P9p>;@CIgSPvGF`{A`k7OLtp)Ep0NEXOQ8le}vnc%rBj_q|0S_9m2)l?gXsoae;_MSEO#sFg5 zdeCEu2ffRlz(UbWvn%DyC3a>RzYSf2QA9HDRzM3&CZY1;L$&@OuMP3C+~ zTk+6sA+h7no=g>>Mt39XNyJ&s{oU1oT?SlyV)xaU%i7GEb2YH}jun$_x*CTUL?$6+7W zx{L~v9TN4)W5dC&DeTvM>iz(f21gg0+^{m*X+vxGaj74>_EZO^)Dxlo=*gx$$EcZ3 z)kY6Wz8)*7tuMDKXXvF_$oQS}6T<0p#huj31v)y@N=k z=ylt8xwv3ZS%H2?eFv0+ZiUPK`!fgCE7!({HwKUODRDQaMg;H4*e1ct?pakvV&@diaLyXc-DdU9= zm;nr5E_ea*KQMyCFN{#RH2$xgM(JlC{x@p zC+d5vvHM*wLLttikVVGGik!Vwq)m}9r;TxN>SVYjls(cerVw7!>@9qe$WA4|*!R=c z^EFA61cQ#x6o}jFpIk(70%vLg4m=xH93hi21xB%IX$Cn6Nfhtkqrpai$(^(+V++6A z8RQ&Qhr?^8XDOUBWk-^FWBX#7X{U(062Qtg4Q>1E205UkL8m0eiL2G>T}_a@`7r_h z3hm&KL-83I1O)Y3LPSW}VK|k8fu0@_J+^}y1v)kl7>h)Ni_HEE-X3_`yt0eQNy`m% z^bT`_o%f{0WA;{+{f-r2C;v~>95-HZI}#JRbFRYAqcjpm0@o%kZW2P~R9sc9UjWL^ zkHd$!G5Z5M(~3H|`ZLa3bfCvA@HlL~aKg`eO5^F|+SvtV&793|6PdgpZZr%XQ|mdy zZ)%U$gtK5!kw2WOBoG`dsH}o4a&JkBZJ_LjqdDe+VH0OaH|;}&5^}!^LHn&k-mud| zT)tD|T4jx;W@+5!K@lwUc+3(J%UDUMBne*6bEIvM3zALyX2ZL7bP=j#RSSBVYl&{= z^uZ|YF(x-3d0S36x^%n=EDrIImh8kJpRu;B$gFcOPmKDfa~zb!hlLp|5Ed2a9R-OJ z6l7t(>$!VyecQn_;oaqNF?_xDJu06;6O#n5U{#e;>q|_Ver)Wp%^DL=7ws}#x+!`2 z@Jo;}%>;^xUtUT3w%KjZ(zaZgmUyxW6Ap?sLsawo-uil@nt>UbY7u&E1xm538|fEpn^F|6-)|&v-;u*ev7dyuz=hKiiC;4$79a1 zilu2`e|PHt^6lFuA}5_ppzzjh(W_yG{?a4=JLgabvY?pgn!3K^PygUA7yVyf8it@h zlWfwff&8BxqhGG5c?dSk<-eWg&V$$%XqyRToJU)j^7WrM=kF!K!Sm+`wSWfpaXpA2 za40u7m%;n{P@0nP(U726pwAo8zyJKt|JLXU38g#NjJ$VwRssDBb(Xus$=%B+6u?A6 z+J!_vW#2zd@?Rm%k7`Onlai9QoOQv4goLcM`|M*g;`za8xj7h7t_M2&wX>Za#bqzqC8304v zZFM>zv5?m2dm1IMkf7cGdDU_eMO^4_eCprjeSm>Jfq{X*riXuqQ&$EX4U3LOiV)3F zEm;@90BlMT&evJa?u?`cMZ#OL1Dg^EE`VxBpAZ0uDAb+rDq2gynoa<;mhW<-*Aw@y z=yraPgps|qT0a~vA)-($2*bacW{sT)gyG<91EI~^{TJaiANL6QH;LG@?v0ep~sZk^6(J9v0onuKvpLrB?Q9gp8cPM)OMh*DTH9c5WTmD3yulc3Dm0$87>ScQtHt%dY!b zm=7djU3viAv$PnEq+e+P|B{f+47$<_euv<&N3R9*%xM%N>4WFR)<9(?qpdXf$&k&uGNbS3{q3m0NOB2geiAzhizfsdJ# zRSOlh9J0dSGSp79gBSh{AjD#AU~Q|RV%1h`Oyw}Amc6cKcjA={-SJ+;9)x~k;lyZ!XqPEuOa`8mWJ zOflO%NGK?&(sfpcuH1iJ_GcfEmI&cBEli;KN{s=10YE>)xl}>vLRz_lGJHStR5%*i zJ34xtuJD4;rO$NT!D6z}BXe~OGV;FbCdhC_qNbzn4r-eyM>D6QVeqImyA<=I+;?MMq_#E8W6-M*ttH0+medg+yw)&A#( zJ{+5n4Tc3vv(`&V9BO4LsH`6i4@&9a8vy3QVtr-GkURezHmn0d?(hgex(9>-vgP=doZW`t#$G=(IGOYhd;=f`VMnwgNFuFttCXNf;s-(fa=wnDdgM~^&*t03(_ zUk!;i<&`Q@&&sM`U8lppdSVR&^o*wij;p1~-kxdg9WFf)lvbA%rwF+2D-pu0o13=p zJE;Z<=wa_XU|2Xw(dO9vq{h1gF?3wkf#V4T71A2n3%Y7d4v8>4XCKgJ>g7ZGRK1ew zd4fuXPWAK+V(a8W#z(=+n-=j8^gnP|&?6s6BQ&vuKYL*glU_rcDWXb>wtH$C%z}Y+ z;!NXMnyusLs#WX60$U>dOk$Yt==c4fh8PQMO=islYprVy>`-4%3kM0XRoMZqv$~Gn z1BgvXP*qg?cz3p5V>4%yF zN8~m8Fc4tcpESca@Weei+1dEjk%e#Z5#bPFCn17@EZz#&9dLn<5&=GmC%Ibr4=3jY z&YN-znhDmntb_NQrIceKo%&jtH zOo{N9tCTooX{1N`gO~WSm14XyG4iqD2wt*9PMqWuLp@S$wCUglriL-)Yor~-LPiEf z)Mj)-vL?o{`5A5V*KxPw3VCg%mMaRfhp3ASyCVj4(S!f-ta}L_?gTv%{<%WE3Je$Z zwEcy@-jT^q^c^q6*MdhkIyUcNG@9tU{H#L0TWFgCpU9kw9t7VTPmVI#M{UtEuDShV>~I^*kq%zb^JDG56o=^9ecZV-`A1crNkTIsTLauR9Z z6W_22y3vE?`m1aKnJNZ77dhkix7FShiIB`6VFqPf+Q!Q}S0kjKYYvTc|Yenf9_(|CRnq7hkf_T72!#H4X~W;_z0l$BdiRJ z)4SF~Scz?D$jy!G=@Cha1a(4Ce2WyT|8MoR2^?q@$*V|_ZLlK`&>CvJ7)~n}itqXT zT+EN+K@2gmw6Q%M0JtUh=azrlol6^|=KI>F~D2avvq?i#FaMO8YQ)xN5uOlvYPGQS8oY>3I zNQ8nuAfRC2&`5=7!LJw3sRbMpsYH+PYl=4!WA5VcGmmN9qgFV-sJZrVn&o0H>W*?Z z%@oe86K;+~dE?ZjvIC1gG63f1=b6MVCllr?=W10MPJQz%3C z(C)kRA{urFFNgMci}rn>YW~pL##c^AJloLLn!UZSbQ;QMMg02}{a$W~5Ny`Os3-39 z;aRImsLu!!92yrIz-eO@#gc%5fl;qU_s1!KZ}i@RMaUMgWK9gB{Ca zu=vY0{rIW_90D8*wMQl8w}$wmlsFAxzxZJh_Rr?|<5NOLApn4I^LAvmtE&tAbs_6t zzeddSfh!mOZh4zO2?r03rO-BQktYxw`nMCV`3|ju0MNi*3H2>4EqPh!+;6NuIm{;h z_3Ru~9sCRp)&^KM5rTBU)8(j+Wu=q!a$aBDR)-Uzm;CL3fw1!BBs*LYKh({8F5??f$Gsm_bXNTekPluKm@*?^_8s z2ged$)Z5Z_U0GJsV(cEpkwi_Hyz_5uBPJPIZ|B8qOmXIOD!zRCSLejSO~Em*cy2xV zMP<&wEX0QIj+paxz-pT@T%YXKsXA=NOqWhDd+ zynp;^%@%e_ideOIBwL_=Z|2a8Go;n>A3q_C7i=XZHZi(_!m3KKz&-d|J%3c9@5dp^ zpWPSZr_)oanraL}{#i9f{U5DS6_c4Mu*i{{UZHzzSu6gJdLbnRcL+v*v#!@HQ|-0O z@Hl7;En`RiYnyghgBdb3I2{(Ig8;mK-0>4TCEhj0f4f|9;x%6ym>gno>~z^6u)jVC zKl*1)5Za}`7jVahMf#7gqUjhI;Furr{#iLFK|;mwyu7>@kPp{eERT74C?LQ;iFdLh JT+hx@|JFd*G;(8Snnz{kd=&Bn;WsKq46$jHjT z(!^N4d&}9Q0#QslQ7W&W2V7b*jk`4F%j_l0dKayK&PwlSvJ0O4EbpS3$k$JAe`h{C z_OSiyONT%ealg)<6?<2td#;=?x#e}L#PhJciIE%D?U}Ph?cL1RwXROqBE@rFaQQ5i zmNkB&@aK1z!a2vx$KQ6Ju6Xf)<&RV}M}*N4$$N|9wU{SOJ&EF%)#FgQGuK($$?nTWJ!FJi#%{txGYU)?yCBE<~|7e^@D@x#ihu;p*$gjZFuo43GGkFR$l#w9J-^ zp>Vd)YfGEE;Rmx+Cg}cE>r)6lbr?Xu6T*EI7wQUa6z{w8Jxz;MzoYlUn<-9- zEk~l;9zT!XpmcY=az};8M}}B-jnwOW5_kS^#w_j&Nc;wlCe{c&Qv*u|24;1GCT3-W zCZ-2WEI#KiiyCmVv1_$?oU>qIW@RuaHxvLy9CIiOn=pH5UUpu7c^*uJ14D!zLxc-M zgc~6uZ6FCUnORuK8JuDioKuTRGSf1X6H8JJw_C*ONBIp?t9JAd-LS^H^J>eHt$^+4$Ij$YztJ& zbd7aT3j7^u+Z*v~|Ix3z48EP79h!5!(Yis_B>z)d!uIT$0ZW(6pZ|iTT0rTL|L&F7 z|7|}e{lhx^`LPwtMEO~MH-Ec)RPHd7X^3!?*hHgmrJvVnCtlZj|4E5ia_&VB#^X$) z4AT<=`bCww&up5svuV-m7VZxbGG9SX`~Gl2>$Tao_fGFw9b>j5d}rm9^}Byry=wXy z?!#@%-jp5~8Q!<+N~Hg@LUw`1hAp0Ag~~5{G{l6rpXW5{IkD`ajaXH8NKLQCgT?FQ zv^Os+otU?{p8J=gY0}5vNmYz0)@PP{+_xfdLg$L+`wyd@&S#s{ofg+n9jcnb=wtow zyr&j(V+#XQV}qE#=dv>_tVS0SM zb^Yo%sXLrK}p1`GzeOd@Pw zodt4u{6e(vA2`HO+B92w!wh#|NKWxz^=|T`qCJkbBGpxg?9xA-vrW8om3MhUa?$C( Q>4ou<|EC`eTz_&S0BPL&!vFvP diff --git a/providers/netty4/src/test/resources/gzip.txt.gz b/providers/netty4/src/test/resources/gzip.txt.gz deleted file mode 100644 index 80aeb98d2b03a00c13d7c2725d3e281f47a57d81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47 zcmb2|=HR#?o9WBIoL-e#pjT2+!r;CBl#a)_^V*(gwKoQx@Hl(&Bs)Xnt@zJE3=9ka Do?Q|u diff --git a/providers/netty4/src/test/resources/logback-test.xml b/providers/netty4/src/test/resources/logback-test.xml deleted file mode 100644 index 4acf278710..0000000000 --- a/providers/netty4/src/test/resources/logback-test.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - %d [%thread] %level %logger - %m%n - - - - - - - - - \ No newline at end of file diff --git a/providers/netty4/src/test/resources/realm.properties b/providers/netty4/src/test/resources/realm.properties deleted file mode 100644 index bc9faad66a..0000000000 --- a/providers/netty4/src/test/resources/realm.properties +++ /dev/null @@ -1 +0,0 @@ -user=admin, admin \ No newline at end of file diff --git a/providers/netty4/src/test/resources/ssltest-cacerts.jks b/providers/netty4/src/test/resources/ssltest-cacerts.jks deleted file mode 100644 index 9c1ffbe49a7b27a250fc7d00fedd45405744fc38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29888 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 A2><{9 diff --git a/providers/netty4/src/test/resources/ssltest-keystore.jks b/providers/netty4/src/test/resources/ssltest-keystore.jks deleted file mode 100644 index a95b7c5f4fbe9f9cba881475b3854861cb182263..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1445 zcmezO_TO6u1_mY|W&~rFV#CB@AV=73#>MqOiCqRwOxq0j*toRW7+Dy#m;@OaSs7TG zm==Ahum1KTDl}5e`kqee9lk>GwJCjfCp7D~ov*(x`E$+b6=xjpnr%q9^|4>@`!@E+ zi}uIu{+!!*!-C=Vp@lxxA&;l{Nk*7CEQ|PTX1Z`q#lHsu!v5(6#vw9KEZxO~bf#Zb zE?Il~dGkX>A=cv6>qSIP+-5VqZyGyUOVDFdgS=Z}_JfL#JMP&==)6<6Sh2b9`t5ry zzPG<5v@n$N`kc{=4oZz#{&v|`N2`6xPN7puSbB3j=P~RMOPb*@xlF*s=gqB!uVbHl z2<4ScJw8W+`&G{xr?Pq@ONfRqihy3>ye<~BGoZ+h#{_(N>D|Fs2ur2%ps7pF>I zDyq!Qu>EItI4*4OBcD3qb*tuHm9|&fwWayCaOz5XseaVX$*ge1Rv~TixJ>9JFm50^r<=XRnFV)>E5XyX* z`20}6VuG$_v3GNlivo}JLPd+C-52%4{>}87eQM$FyR%#4dd~lTX}Zdhn78D zytSvNb1s=yot+rkvqPAJ?zzMvo@KDD8@ayqLK67Bw^~d=)8$1RIHVRV%M2zL;>ym3BqC9{cp0q5C^#N!jg;Oqrb#c%Au7>88ae?(Y>*m{+_%clwSNmXJJ#2fue( zuJwJi@zm*L*}2hczl+}&I(v&Z{!e{~)NHj6dxQ**W%OyC*S>kOC&pB3PjK_JM&HO^ zRTfe!{SI<_w7t5|Kk+(W=VGl)mKCeLEB4tu{LIA6$iRr~JYXVV2D)pqdW7|<3-SD= zw~jv1mb8iy&d&Q#`!qf`dG>O9pQr~tnf__VzSRmDJ@rmb-s#wH?mS~d;Zu_jv8;Q9 z_!+M@r%rmY%EEvlj5C;{;O@<$*r}E~J@OlQHe8oaPUyCHC1O(4B!1-dme<8*cPnpb uEt|hX&w*2cNrXr4-_Nv(?*ugF{&{FJ?S$IdRLMmwGq0yv95LW9JOTidn?aWV diff --git a/providers/netty4/src/test/resources/textfile.txt b/providers/netty4/src/test/resources/textfile.txt deleted file mode 100644 index 87daee60a9..0000000000 --- a/providers/netty4/src/test/resources/textfile.txt +++ /dev/null @@ -1 +0,0 @@ -filecontent: hello \ No newline at end of file diff --git a/providers/netty4/src/test/resources/textfile2.txt b/providers/netty4/src/test/resources/textfile2.txt deleted file mode 100644 index 6a91fe609c..0000000000 --- a/providers/netty4/src/test/resources/textfile2.txt +++ /dev/null @@ -1 +0,0 @@ -filecontent: hello2 \ No newline at end of file diff --git a/providers/pom.xml b/providers/pom.xml index 9a7178b576..72af625a7f 100644 --- a/providers/pom.xml +++ b/providers/pom.xml @@ -46,7 +46,6 @@ grizzly netty - netty4 From 290dd6643dfe9a996fcb9a0933d1cd31b99b68dd Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Oct 2013 13:31:49 +0200 Subject: [PATCH 0004/2254] Remove HttpContent and clean up Status, Headers and BodyParts --- .../asynchttpclient/AsyncHttpProvider.java | 17 +-- .../java/org/asynchttpclient/HttpContent.java | 49 -------- .../asynchttpclient/HttpResponseBodyPart.java | 7 +- .../asynchttpclient/HttpResponseHeaders.java | 9 +- .../asynchttpclient/HttpResponseStatus.java | 54 ++++++--- .../java/org/asynchttpclient/Response.java | 109 +++++++++--------- .../providers/ResponseBase.java | 2 +- .../providers/jdk/JDKAsyncHttpProvider.java | 76 ++++++------ .../providers/jdk/ResponseBodyPart.java | 1 - .../providers/jdk/ResponseHeaders.java | 1 - .../providers/jdk/ResponseStatus.java | 15 ++- .../resumable/ResumableAsyncHandler.java | 2 +- .../webdav/WebDavCompletionHandlerBase.java | 28 +++-- .../asynchttpclient/async/PostWithQSTest.java | 8 +- .../providers/grizzly/EventHandler.java | 16 ++- .../grizzly/GrizzlyAsyncHttpProvider.java | 20 +--- .../grizzly/GrizzlyResponseBodyPart.java | 4 +- .../grizzly/GrizzlyResponseHeaders.java | 8 +- .../grizzly/GrizzlyResponseStatus.java | 24 ++-- .../netty/NettyAsyncHttpProvider.java | 10 -- .../providers/netty/handler/HttpProtocol.java | 2 +- .../netty/handler/WebSocketProtocol.java | 4 +- .../netty/response/ResponseBodyPart.java | 2 - .../netty/response/ResponseHeaders.java | 3 +- .../netty/response/ResponseStatus.java | 43 +++---- .../netty/NettyAsyncResponseTest.java | 6 +- 26 files changed, 214 insertions(+), 306 deletions(-) delete mode 100644 api/src/main/java/org/asynchttpclient/HttpContent.java diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpProvider.java b/api/src/main/java/org/asynchttpclient/AsyncHttpProvider.java index da96a0497e..c8f910b8fb 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpProvider.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpProvider.java @@ -17,7 +17,6 @@ import java.io.Closeable; import java.io.IOException; -import java.util.List; /** * Interface to be used when implementing custom asynchronous I/O HTTP client. @@ -33,22 +32,10 @@ public interface AsyncHttpProvider extends Closeable { * @return a {@link ListenableFuture} of Type T. * @throws IOException */ - public ListenableFuture execute(Request request, AsyncHandler handler) throws IOException; + ListenableFuture execute(Request request, AsyncHandler handler) throws IOException; /** * Close the current underlying TCP/HTTP connection. */ - public void close(); - - /** - * Prepare a {@link Response} - * - * @param status {@link HttpResponseStatus} - * @param headers {@link HttpResponseHeaders} - * @param bodyParts list of {@link HttpResponseBodyPart} - * @return a {@link Response} - */ - public Response prepareResponse(HttpResponseStatus status, - HttpResponseHeaders headers, - List bodyParts); + void close(); } diff --git a/api/src/main/java/org/asynchttpclient/HttpContent.java b/api/src/main/java/org/asynchttpclient/HttpContent.java deleted file mode 100644 index 219e2411cc..0000000000 --- a/api/src/main/java/org/asynchttpclient/HttpContent.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2010 Ning, Inc. - * - * Ning licenses this file to you under the Apache License, version 2.0 - * (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ -package org.asynchttpclient; - -import java.net.URI; - -/** - * Base class for callback class used by {@link AsyncHandler} - */ -public class HttpContent { - protected final AsyncHttpProvider provider; - protected final URI uri; - - protected HttpContent(URI url, AsyncHttpProvider provider) { - this.provider = provider; - this.uri = url; - } - - /** - * Return the current {@link AsyncHttpProvider} - * - * @return the current {@link AsyncHttpProvider} - */ - public final AsyncHttpProvider provider() { - return provider; - } - - /** - * Return the request {@link URI} - * - * @return the request {@link URI} - */ - public final URI getUrl() { - return uri; - } -} diff --git a/api/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java b/api/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java index e7f350e439..42bb8c8158 100644 --- a/api/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java +++ b/api/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java @@ -18,17 +18,12 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.URI; import java.nio.ByteBuffer; /** * A callback class used when an HTTP response body is received. */ -public abstract class HttpResponseBodyPart extends HttpContent { - - public HttpResponseBodyPart(URI uri, AsyncHttpProvider provider) { - super(uri, provider); - } +public abstract class HttpResponseBodyPart { /** * Return length of this part in bytes. diff --git a/api/src/main/java/org/asynchttpclient/HttpResponseHeaders.java b/api/src/main/java/org/asynchttpclient/HttpResponseHeaders.java index 54c894f712..5ecc6898c3 100644 --- a/api/src/main/java/org/asynchttpclient/HttpResponseHeaders.java +++ b/api/src/main/java/org/asynchttpclient/HttpResponseHeaders.java @@ -15,22 +15,19 @@ */ package org.asynchttpclient; -import java.net.URI; /** * A class that represent the HTTP headers. */ -public abstract class HttpResponseHeaders extends HttpContent { +public abstract class HttpResponseHeaders { private final boolean traillingHeaders; - public HttpResponseHeaders(URI uri, AsyncHttpProvider provider) { - super(uri, provider); + public HttpResponseHeaders() { this.traillingHeaders = false; } - public HttpResponseHeaders(URI uri, AsyncHttpProvider provider, boolean traillingHeaders) { - super(uri, provider); + public HttpResponseHeaders(boolean traillingHeaders) { this.traillingHeaders = traillingHeaders; } diff --git a/api/src/main/java/org/asynchttpclient/HttpResponseStatus.java b/api/src/main/java/org/asynchttpclient/HttpResponseStatus.java index 3581e46582..b2e5b0d7ab 100644 --- a/api/src/main/java/org/asynchttpclient/HttpResponseStatus.java +++ b/api/src/main/java/org/asynchttpclient/HttpResponseStatus.java @@ -17,55 +17,79 @@ package org.asynchttpclient; import java.net.URI; +import java.util.List; /** * A class that represent the HTTP response' status line (code + text) */ -public abstract class HttpResponseStatus extends HttpContent { +public abstract class HttpResponseStatus { - public HttpResponseStatus(URI uri, AsyncHttpProvider provider) { - super(uri, provider); + private final URI uri; + protected final AsyncHttpClientConfig config; + + public HttpResponseStatus(URI uri, AsyncHttpClientConfig config) { + this.uri = uri; + this.config = config; } /** - * Return the response status code + * Return the request {@link URI} + * + * @return the request {@link URI} + */ + public final URI getUri() { + return uri; + } + + /** + * Prepare a {@link Response} * + * @param headers {@link HttpResponseHeaders} + * @param bodyParts list of {@link HttpResponseBodyPart} + * @param config the client config + * @return a {@link Response} + */ + public abstract Response prepareResponse(HttpResponseHeaders headers, List bodyParts); + + /** + * Return the response status code + * * @return the response status code */ - abstract public int getStatusCode(); + public abstract int getStatusCode(); /** * Return the response status text - * + * * @return the response status text */ - abstract public String getStatusText(); + public abstract String getStatusText(); /** * Protocol name from status line. - * + * * @return Protocol name. */ - abstract public String getProtocolName(); + public abstract String getProtocolName(); /** * Protocol major version. - * + * * @return Major version. */ - abstract public int getProtocolMajorVersion(); + public abstract int getProtocolMajorVersion(); /** * Protocol minor version. - * + * * @return Minor version. */ - abstract public int getProtocolMinorVersion(); + public abstract int getProtocolMinorVersion(); /** * Full protocol name + version - * + * * @return protocol name + version */ - abstract public String getProtocolText(); + public abstract String getProtocolText(); } diff --git a/api/src/main/java/org/asynchttpclient/Response.java b/api/src/main/java/org/asynchttpclient/Response.java index 3db376606d..e8598013f4 100644 --- a/api/src/main/java/org/asynchttpclient/Response.java +++ b/api/src/main/java/org/asynchttpclient/Response.java @@ -31,50 +31,50 @@ public interface Response { /** * Returns the status code for the request. - * + * * @return The status code */ int getStatusCode(); /** * Returns the status text for the request. - * + * * @return The status text */ String getStatusText(); /** * Return the entire response body as a byte[]. - * + * * @return the entire response body as a byte[]. * @throws IOException */ byte[] getResponseBodyAsBytes() throws IOException; - + /** * Return the entire response body as a ByteBuffer. - * + * * @return the entire response body as a ByteBuffer. * @throws IOException */ ByteBuffer getResponseBodyAsByteBuffer() throws IOException; /** - * Returns an input stream for the response body. Note that you should not try to get this more than once, - * and that you should not close the stream. - * + * Returns an input stream for the response body. Note that you should not try to get this more than once, and that you should not close the stream. + * * @return The input stream * @throws java.io.IOException */ InputStream getResponseBodyAsStream() throws IOException; /** - * Returns the first maxLength bytes of the response body as a string. Note that this does not check - * whether the content type is actually a textual one, but it will use the charset if present in the content - * type header. - * - * @param maxLength The maximum number of bytes to read - * @param charset the charset to use when decoding the stream + * Returns the first maxLength bytes of the response body as a string. Note that this does not check whether the content type is actually a textual one, but it will use the + * charset if present in the content type header. + * + * @param maxLength + * The maximum number of bytes to read + * @param charset + * the charset to use when decoding the stream * @return The response body * @throws java.io.IOException */ @@ -82,19 +82,20 @@ public interface Response { /** * Return the entire response body as a String. - * - * @param charset the charset to use when decoding the stream + * + * @param charset + * the charset to use when decoding the stream * @return the entire response body as a String. * @throws IOException */ String getResponseBody(String charset) throws IOException; /** - * Returns the first maxLength bytes of the response body as a string. Note that this does not check - * whether the content type is actually a textual one, but it will use the charset if present in the content - * type header. - * - * @param maxLength The maximum number of bytes to read + * Returns the first maxLength bytes of the response body as a string. Note that this does not check whether the content type is actually a textual one, but it will use the + * charset if present in the content type header. + * + * @param maxLength + * The maximum number of bytes to read * @return The response body * @throws java.io.IOException */ @@ -102,16 +103,15 @@ public interface Response { /** * Return the entire response body as a String. - * + * * @return the entire response body as a String. * @throws IOException */ String getResponseBody() throws IOException; /** - * Return the request {@link URI}. Note that if the request got redirected, the value of the {@link URI} will be - * the last valid redirect url. - * + * Return the request {@link URI}. Note that if the request got redirected, the value of the {@link URI} will be the last valid redirect url. + * * @return the request {@link URI}. * @throws MalformedURLException */ @@ -119,21 +119,21 @@ public interface Response { /** * Return the content-type header value. - * + * * @return the content-type header value. */ String getContentType(); /** * Return the response header - * + * * @return the response header */ String getHeader(String name); /** * Return a {@link List} of the response header value. - * + * * @return the response header */ List getHeaders(String name); @@ -142,14 +142,14 @@ public interface Response { /** * Return true if the response redirects to another object. - * + * * @return True if the response redirects to another object. */ boolean isRedirected(); /** * Subclasses SHOULD implement toString() in a way that identifies the request for logging. - * + * * @return The textual representation */ String toString(); @@ -161,66 +161,61 @@ public interface Response { /** * Return true if the response's status has been computed by an {@link AsyncHandler} - * + * * @return true if the response's status has been computed by an {@link AsyncHandler} */ boolean hasResponseStatus(); /** - * Return true if the response's headers has been computed by an {@link AsyncHandler} It will return false if the - * either {@link AsyncHandler#onStatusReceived(HttpResponseStatus)} - * or {@link AsyncHandler#onHeadersReceived(HttpResponseHeaders)} returned {@link AsyncHandler.STATE#ABORT} - * + * Return true if the response's headers has been computed by an {@link AsyncHandler} It will return false if the either + * {@link AsyncHandler#onStatusReceived(HttpResponseStatus)} or {@link AsyncHandler#onHeadersReceived(HttpResponseHeaders)} returned {@link AsyncHandler.STATE#ABORT} + * * @return true if the response's headers has been computed by an {@link AsyncHandler} */ boolean hasResponseHeaders(); /** - * Return true if the response's body has been computed by an {@link AsyncHandler}. It will return false if the - * either {@link AsyncHandler#onStatusReceived(HttpResponseStatus)} + * Return true if the response's body has been computed by an {@link AsyncHandler}. It will return false if the either {@link AsyncHandler#onStatusReceived(HttpResponseStatus)} * or {@link AsyncHandler#onHeadersReceived(HttpResponseHeaders)} returned {@link AsyncHandler.STATE#ABORT} - * + * * @return true if the response's body has been computed by an {@link AsyncHandler} */ boolean hasResponseBody(); public static class ResponseBuilder { - private final List bodies = - Collections.synchronizedList(new ArrayList()); + private final List bodyParts = Collections.synchronizedList(new ArrayList()); private HttpResponseStatus status; private HttpResponseHeaders headers; - /** - * Accumulate {@link HttpContent} in order to build a {@link Response} - * - * @param httpContent {@link HttpContent} - * @return this - */ - public ResponseBuilder accumulate(HttpContent httpContent) { - if (httpContent instanceof HttpResponseStatus) { - status = (HttpResponseStatus) httpContent; - } else if (httpContent instanceof HttpResponseHeaders) { - headers = (HttpResponseHeaders) httpContent; - } else if (httpContent instanceof HttpResponseBodyPart) { - bodies.add((HttpResponseBodyPart) httpContent); - } + public ResponseBuilder accumulate(HttpResponseStatus status) { + this.status = status; + return this; + } + + public ResponseBuilder accumulate(HttpResponseHeaders headers) { + this.headers = headers; + return this; + } + + public ResponseBuilder accumulate(HttpResponseBodyPart bodyPart) { + bodyParts.add(bodyPart); return this; } /** * Build a {@link Response} instance - * + * * @return a {@link Response} instance */ public Response build() { - return status == null ? null : status.provider().prepareResponse(status, headers, bodies); + return status == null ? null : status.prepareResponse(headers, bodyParts); } /** * Reset the internal state of this builder. */ public void reset() { - bodies.clear(); + bodyParts.clear(); status = null; headers = null; } diff --git a/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java b/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java index 9718c3302c..b7f4dfd410 100644 --- a/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java +++ b/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java @@ -47,7 +47,7 @@ public final String getStatusText() { /* @Override */ public final URI getUri() { - return status.getUrl(); + return status.getUri(); } /* @Override */ diff --git a/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProvider.java b/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProvider.java index 9268f67cc9..061dcd191e 100644 --- a/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProvider.java +++ b/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProvider.java @@ -12,43 +12,9 @@ */ package org.asynchttpclient.providers.jdk; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.AsyncHttpProviderUtils.*; +import static org.asynchttpclient.util.MiscUtil.*; -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.asynchttpclient.Body; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.MaxRedirectException; -import org.asynchttpclient.ProgressAsyncHandler; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; -import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterException; -import org.asynchttpclient.filter.IOExceptionFilter; -import org.asynchttpclient.filter.ResponseFilter; -import org.asynchttpclient.listener.TransferCompletionHandler; -import org.asynchttpclient.multipart.MultipartRequestEntity; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.asynchttpclient.util.AuthenticatorUtils; -import org.asynchttpclient.util.ProxyUtils; -import org.asynchttpclient.util.SslUtils; -import org.asynchttpclient.util.UTF8UrlEncoder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.naming.AuthenticationException; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLHandshakeException; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; @@ -78,7 +44,37 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.zip.GZIPInputStream; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.DEFAULT_CHARSET; +import javax.naming.AuthenticationException; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; + +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.AsyncHttpProvider; +import org.asynchttpclient.AsyncHttpProviderConfig; +import org.asynchttpclient.Body; +import org.asynchttpclient.FluentCaseInsensitiveStringsMap; +import org.asynchttpclient.ListenableFuture; +import org.asynchttpclient.MaxRedirectException; +import org.asynchttpclient.ProgressAsyncHandler; +import org.asynchttpclient.ProxyServer; +import org.asynchttpclient.Realm; +import org.asynchttpclient.Request; +import org.asynchttpclient.RequestBuilder; +import org.asynchttpclient.filter.FilterContext; +import org.asynchttpclient.filter.FilterException; +import org.asynchttpclient.filter.IOExceptionFilter; +import org.asynchttpclient.filter.ResponseFilter; +import org.asynchttpclient.listener.TransferCompletionHandler; +import org.asynchttpclient.multipart.MultipartRequestEntity; +import org.asynchttpclient.util.AsyncHttpProviderUtils; +import org.asynchttpclient.util.AuthenticatorUtils; +import org.asynchttpclient.util.ProxyUtils; +import org.asynchttpclient.util.SslUtils; +import org.asynchttpclient.util.UTF8UrlEncoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class JDKAsyncHttpProvider implements AsyncHttpProvider { private final static Logger logger = LoggerFactory.getLogger(JDKAsyncHttpProvider.class); @@ -208,10 +204,6 @@ public void close() { } } - public Response prepareResponse(HttpResponseStatus status, HttpResponseHeaders headers, List bodyParts) { - return new JDKResponse(status, headers, bodyParts); - } - private final class AsyncHttpUrlConnection implements Callable { private HttpURLConnection urlConnection; @@ -254,7 +246,7 @@ public T call() throws Exception { logger.debug("\n\nRequest {}\n\nResponse {}\n", request, statusCode); - ResponseStatus status = new ResponseStatus(uri, urlConnection, JDKAsyncHttpProvider.this); + ResponseStatus status = new ResponseStatus(uri, urlConnection, config); FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(asyncHandler).request(request).responseStatus(status).build(); for (ResponseFilter asyncFilter : config.getResponseFilters()) { fc = asyncFilter.filter(fc); diff --git a/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseBodyPart.java b/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseBodyPart.java index 3081baee6f..b80ced8345 100644 --- a/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseBodyPart.java +++ b/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseBodyPart.java @@ -32,7 +32,6 @@ public class ResponseBodyPart extends HttpResponseBodyPart { private boolean closeConnection; public ResponseBodyPart(URI uri, byte[] chunk, AsyncHttpProvider provider, boolean last) { - super(uri, provider); this.chunk = chunk; isLast = last; } diff --git a/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseHeaders.java b/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseHeaders.java index 1a07b53c0e..ed71a96aa7 100644 --- a/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseHeaders.java +++ b/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseHeaders.java @@ -30,7 +30,6 @@ public class ResponseHeaders extends HttpResponseHeaders { private final FluentCaseInsensitiveStringsMap headers; public ResponseHeaders(URI uri, HttpURLConnection urlConnection, AsyncHttpProvider provider) { - super(uri, provider, false); this.urlConnection = urlConnection; headers = computerHeaders(); } diff --git a/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseStatus.java b/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseStatus.java index d88d211683..b5cbc7094a 100644 --- a/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseStatus.java +++ b/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseStatus.java @@ -12,12 +12,16 @@ */ package org.asynchttpclient.providers.jdk; -import org.asynchttpclient.AsyncHttpProvider; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.HttpResponseHeaders; import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.Response; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; +import java.util.List; /** * A class that represent the HTTP response' status line (code + text) @@ -26,10 +30,15 @@ public class ResponseStatus extends HttpResponseStatus { private final HttpURLConnection urlConnection; - public ResponseStatus(URI uri, HttpURLConnection urlConnection, AsyncHttpProvider provider) { - super(uri, provider); + public ResponseStatus(URI uri, HttpURLConnection urlConnection, AsyncHttpClientConfig config) { + super(uri, config); this.urlConnection = urlConnection; } + + @Override + public Response prepareResponse(HttpResponseHeaders headers, List bodyParts) { + return new JDKResponse(this, headers, bodyParts); + } /** * Return the response status code diff --git a/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java b/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java index 6991d05383..297ed0a0f4 100644 --- a/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java +++ b/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java @@ -106,7 +106,7 @@ public ResumableAsyncHandler(ResumableProcessor resumableProcessor, boolean accu public AsyncHandler.STATE onStatusReceived(final HttpResponseStatus status) throws Exception { responseBuilder.accumulate(status); if (status.getStatusCode() == 200 || status.getStatusCode() == 206) { - url = status.getUrl().toURL().toString(); + url = status.getUri().toURL().toString(); } else { return AsyncHandler.STATE.ABORT; } diff --git a/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java b/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java index 74ac5ab684..6781d561cd 100644 --- a/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java +++ b/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java @@ -29,6 +29,7 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; + import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -81,12 +82,12 @@ public final STATE onHeadersReceived(final HttpResponseHeaders headers) throws E /* @Override */ public final T onCompleted() throws Exception { if (status != null) { - Response response = status.provider().prepareResponse(status, headers, bodies); + Response response = status.prepareResponse(headers, bodies); Document document = null; if (status.getStatusCode() == 207) { document = readXMLResponse(response.getResponseBodyAsStream()); } - return onCompleted(new WebDavResponse(status.provider().prepareResponse(status, headers, bodies), document)); + return onCompleted(new WebDavResponse(status.prepareResponse(headers, bodies), document)); } else { throw new IllegalStateException("Status is null"); } @@ -111,47 +112,52 @@ public void onThrowable(Throwable t) { private class HttpStatusWrapper extends HttpResponseStatus { - private final HttpResponseStatus wrapper; + private final HttpResponseStatus wrapped; private final String statusText; private final int statusCode; public HttpStatusWrapper(HttpResponseStatus wrapper, String statusText, int statusCode) { - super(wrapper.getUrl(), wrapper.provider()); - this.wrapper = wrapper; + super(wrapper.getUri(), null); + this.wrapped = wrapper; this.statusText = statusText; this.statusCode = statusCode; } + + @Override + public Response prepareResponse(HttpResponseHeaders headers, List bodyParts) { + return wrapped.prepareResponse(headers, bodyParts); + } @Override public int getStatusCode() { - return (statusText == null ? wrapper.getStatusCode() : statusCode); + return (statusText == null ? wrapped.getStatusCode() : statusCode); } @Override public String getStatusText() { - return (statusText == null ? wrapper.getStatusText() : statusText); + return (statusText == null ? wrapped.getStatusText() : statusText); } @Override public String getProtocolName() { - return wrapper.getProtocolName(); + return wrapped.getProtocolName(); } @Override public int getProtocolMajorVersion() { - return wrapper.getProtocolMajorVersion(); + return wrapped.getProtocolMajorVersion(); } @Override public int getProtocolMinorVersion() { - return wrapper.getProtocolMinorVersion(); + return wrapped.getProtocolMinorVersion(); } @Override public String getProtocolText() { - return wrapper.getStatusText(); + return wrapped.getStatusText(); } } diff --git a/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java b/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java index 442f389082..26d85587ac 100644 --- a/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java +++ b/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java @@ -91,8 +91,8 @@ public void postWithNulParamQS() throws IOException, ExecutionException, Timeout /* @Override */ public STATE onStatusReceived(final HttpResponseStatus status) throws Exception { - if (!status.getUrl().toURL().toString().equals("http://127.0.0.1/" + port1 + "/?a=")) { - throw new IOException(status.getUrl().toURL().toString()); + if (!status.getUri().toURL().toString().equals("http://127.0.0.1/" + port1 + "/?a=")) { + throw new IOException(status.getUri().toURL().toString()); } return super.onStatusReceived(status); } @@ -114,7 +114,7 @@ public void postWithNulParamsQS() throws IOException, ExecutionException, Timeou /* @Override */ public STATE onStatusReceived(final HttpResponseStatus status) throws Exception { - if (!status.getUrl().toURL().toString().equals("http://127.0.0.1/" + port1 + "/?a=b&c&d=e")) { + if (!status.getUri().toURL().toString().equals("http://127.0.0.1/" + port1 + "/?a=b&c&d=e")) { throw new IOException("failed to parse the query properly"); } return super.onStatusReceived(status); @@ -137,7 +137,7 @@ public void postWithEmptyParamsQS() throws IOException, ExecutionException, Time /* @Override */ public STATE onStatusReceived(final HttpResponseStatus status) throws Exception { - if (!status.getUrl().toURL().toString().equals("http://127.0.0.1/" + port1 + "/?a=b&c=&d=e")) { + if (!status.getUri().toURL().toString().equals("http://127.0.0.1/" + port1 + "/?a=b&c=&d=e")) { throw new IOException("failed to parse the query properly"); } return super.onStatusReceived(status); diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java index c0e28d05c1..732697e3b5 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java @@ -14,6 +14,7 @@ package org.asynchttpclient.providers.grizzly; import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.AsyncHttpProviderConfig; import org.asynchttpclient.Cookie; import org.asynchttpclient.MaxRedirectException; @@ -74,15 +75,15 @@ public final class EventHandler { } - private final GrizzlyAsyncHttpProvider provider; + private final AsyncHttpClientConfig config; GrizzlyAsyncHttpProvider.Cleanup cleanup; // -------------------------------------------------------- Constructors - EventHandler(final GrizzlyAsyncHttpProvider provider) { - this.provider = provider; + EventHandler(final AsyncHttpClientConfig config) { + this.config = config; } @@ -107,8 +108,7 @@ public void onHttpContentParsed(HttpContent content, context.setCurrentState(handler.onBodyPartReceived( new GrizzlyResponseBodyPart(content, context.getRequest().getURI(), - ctx.getConnection(), - provider))); + ctx.getConnection()))); } catch (Exception e) { handler.onThrowable(e); } @@ -188,7 +188,7 @@ public void onInitialLineParsed(HttpHeader httpHeader, final GrizzlyResponseStatus responseStatus = new GrizzlyResponseStatus((HttpResponsePacket) httpHeader, context.getRequest().getURI(), - provider); + config); context.setResponseStatus(responseStatus); if (context.getStatusHandler() != null) { return; @@ -238,9 +238,7 @@ public void onHttpHeadersParsed(HttpHeader httpHeader, final AsyncHandler handler = context.getHandler(); final GrizzlyResponseHeaders responseHeaders = - new GrizzlyResponseHeaders((HttpResponsePacket) httpHeader, - context.getRequest().getURI(), - provider); + new GrizzlyResponseHeaders((HttpResponsePacket) httpHeader); if (context.getProvider().getClientConfig().hasResponseFilters()) { final List filters = context.getProvider() .getClientConfig().getResponseFilters(); diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java index a375d815de..9ef48ad96b 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java @@ -189,22 +189,6 @@ public void close() { } - - /** - * {@inheritDoc} - */ - public Response prepareResponse(HttpResponseStatus status, - HttpResponseHeaders headers, - List bodyParts) { - - return new GrizzlyResponse(status, - headers, - bodyParts, - clientConfig.isRfc6265CookieEncoding()); - - } - - // ---------------------------------------------------------- Public Methods @@ -321,7 +305,7 @@ public void onTimeout(Connection connection) { } final AsyncHttpClientEventFilter eventFilter; - final EventHandler handler = new EventHandler(this); + final EventHandler handler = new EventHandler(clientConfig); if (providerConfig != null) { eventFilter = new AsyncHttpClientEventFilter(handler, @@ -434,7 +418,7 @@ private FilterChainBuilder createSpdyFilterChain(final FilterChainBuilder fcb, spdyFcb.set(idx, new SpdyFramingFilter()); final SpdyMode spdyMode = ((npnEnabled) ? SpdyMode.NPN : SpdyMode.PLAIN); AsyncSpdyClientEventFilter spdyFilter = - new AsyncSpdyClientEventFilter(new EventHandler(this), + new AsyncSpdyClientEventFilter(new EventHandler(clientConfig), spdyMode, clientConfig.executorService()); spdyFilter.setInitialWindowSize(clientConfig.getSpdyInitialWindowSize()); diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseBodyPart.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseBodyPart.java index e0a2931ac9..4f2236e321 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseBodyPart.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseBodyPart.java @@ -48,9 +48,7 @@ class GrizzlyResponseBodyPart extends HttpResponseBodyPart { public GrizzlyResponseBodyPart(final HttpContent content, final URI uri, - final Connection connection, - final AsyncHttpProvider provider) { - super(uri, provider); + final Connection connection) { this.content = content; this.connection = connection; diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseHeaders.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseHeaders.java index 588c33ca50..babd1af641 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseHeaders.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseHeaders.java @@ -13,15 +13,12 @@ package org.asynchttpclient.providers.grizzly; -import org.asynchttpclient.AsyncHttpProvider; import org.asynchttpclient.FluentCaseInsensitiveStringsMap; import org.asynchttpclient.HttpResponseHeaders; import org.glassfish.grizzly.http.HttpResponsePacket; import org.glassfish.grizzly.http.util.MimeHeaders; -import java.net.URI; - /** * {@link HttpResponseHeaders} implementation using the Grizzly 2.0 HTTP client @@ -39,11 +36,8 @@ class GrizzlyResponseHeaders extends HttpResponseHeaders { // ------------------------------------------------------------ Constructors - public GrizzlyResponseHeaders(final HttpResponsePacket response, - final URI uri, - final AsyncHttpProvider provider) { + public GrizzlyResponseHeaders(final HttpResponsePacket response) { - super(uri, provider); grizzlyHeaders = new MimeHeaders(); grizzlyHeaders.copyFrom(response.getHeaders()); diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseStatus.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseStatus.java index e4e74e3c91..9bafb37be4 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseStatus.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseStatus.java @@ -13,13 +13,16 @@ package org.asynchttpclient.providers.grizzly; -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.HttpResponseStatus; +import java.net.URI; +import java.util.List; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.HttpResponseHeaders; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.Response; import org.glassfish.grizzly.http.HttpResponsePacket; -import java.net.URI; - /** * {@link HttpResponseStatus} implementation using the Grizzly 2.0 HTTP client * codec. @@ -43,9 +46,9 @@ public class GrizzlyResponseStatus extends HttpResponseStatus { public GrizzlyResponseStatus(final HttpResponsePacket response, final URI uri, - final AsyncHttpProvider provider) { + AsyncHttpClientConfig config) { - super(uri, provider); + super(uri, config); statusCode = response.getStatus(); statusText = response.getReasonPhrase(); majorVersion = response.getProtocol().getMajorVersion(); @@ -57,7 +60,14 @@ public GrizzlyResponseStatus(final HttpResponsePacket response, // ----------------------------------------- Methods from HttpResponseStatus - + @Override + public Response prepareResponse(HttpResponseHeaders headers, List bodyParts) { + return new GrizzlyResponse(this, + headers, + bodyParts, + config.isRfc6265CookieEncoding()); + }; + /** * {@inheritDoc} */ diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java index 60656ffad2..f8348abde6 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java @@ -16,18 +16,13 @@ package org.asynchttpclient.providers.netty; import java.io.IOException; -import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; import org.asynchttpclient.ListenableFuture; import org.asynchttpclient.Request; -import org.asynchttpclient.Response; import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.handler.NettyChannelHandler; import org.asynchttpclient.providers.netty.request.NettyRequestSender; @@ -75,11 +70,6 @@ public void close() { } } - @Override - public Response prepareResponse(final HttpResponseStatus status, final HttpResponseHeaders headers, final List bodyParts) { - throw new UnsupportedOperationException("Mocked, should be refactored"); - } - @Override public ListenableFuture execute(Request request, final AsyncHandler asyncHandler) throws IOException { return requestSender.sendRequest(request, asyncHandler, null, asyncHttpProviderConfig.isAsyncConnect(), false); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java index a5d689ac91..0b871daf61 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java @@ -242,7 +242,7 @@ private boolean handleResponseAndExit(final ChannelHandlerContext ctx, final Net ProxyServer proxyServer, HttpResponse response) throws Exception { Request request = future.getRequest(); int statusCode = response.getStatus().code(); - HttpResponseStatus status = new ResponseStatus(future.getURI(), response); + HttpResponseStatus status = new ResponseStatus(future.getURI(), response, config); HttpResponseHeaders responseHeaders = new ResponseHeaders(future.getURI(), response.headers()); final FluentCaseInsensitiveStringsMap headers = request.getHeaders(); final RequestBuilder builder = new RequestBuilder(future.getRequest()); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java index eee8b91515..d29c3a85b0 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java @@ -78,7 +78,7 @@ public void handle(ChannelHandlerContext ctx, NettyResponseFuture future, Object if (e instanceof HttpResponse) { HttpResponse response = (HttpResponse) e; - HttpResponseStatus s = new ResponseStatus(future.getURI(), response); + HttpResponseStatus s = new ResponseStatus(future.getURI(), response, config); HttpResponseHeaders responseHeaders = new ResponseHeaders(future.getURI(), response.headers()); // FIXME there's a method for that IIRC @@ -116,7 +116,7 @@ public void handle(ChannelHandlerContext ctx, NettyResponseFuture future, Object boolean validConnection = c == null ? false : c.equalsIgnoreCase(HttpHeaders.Values.UPGRADE); - s = new ResponseStatus(future.getURI(), response); + s = new ResponseStatus(future.getURI(), response, config); final boolean statusReceived = h.onStatusReceived(s) == STATE.UPGRADE; final boolean headerOK = h.onHeadersReceived(responseHeaders) == STATE.CONTINUE; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseBodyPart.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseBodyPart.java index 86289d146d..e50d36faa8 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseBodyPart.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseBodyPart.java @@ -36,9 +36,7 @@ public class ResponseBodyPart extends HttpResponseBodyPart { private final boolean last; private boolean closeConnection = false; - // FIXME unused AsyncHttpProvider provider public ResponseBodyPart(URI uri, ByteBuf buf, boolean last) { - super(uri, null); bytes = ByteBufUtil.byteBuf2bytes(buf); this.last = last; } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseHeaders.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseHeaders.java index a024e8a624..c48aad3394 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseHeaders.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseHeaders.java @@ -37,9 +37,8 @@ public ResponseHeaders(URI uri, HttpHeaders responseHeaders) { this(uri, responseHeaders, null); } - // FIXME unused AsyncHttpProvider provider public ResponseHeaders(URI uri,HttpHeaders responseHeaders, HttpHeaders traillingHeaders) { - super(uri, null, traillingHeaders != null); + super(traillingHeaders != null); this.responseHeaders = responseHeaders; this.trailingHeaders = traillingHeaders; headers = computerHeaders(); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseStatus.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseStatus.java index 45c7274245..79acf80565 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseStatus.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseStatus.java @@ -16,49 +16,33 @@ */ package org.asynchttpclient.providers.netty.response; -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.Request; -import org.asynchttpclient.Response; - import io.netty.handler.codec.http.HttpResponse; -import java.io.IOException; import java.net.URI; import java.util.List; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.HttpResponseHeaders; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.Response; + /** * A class that represent the HTTP response' status line (code + text) */ public class ResponseStatus extends HttpResponseStatus { - private static final AsyncHttpProvider fakeProvider = new AsyncHttpProvider() { - public ListenableFuture execute(Request request, AsyncHandler handler) throws IOException { - throw new UnsupportedOperationException("Mocked, should be refactored"); - } - - public void close() { - throw new UnsupportedOperationException("Mocked, should be refactored"); - } - - public Response prepareResponse(HttpResponseStatus status, - HttpResponseHeaders headers, - List bodyParts) { - return new NettyResponse(status, headers, bodyParts); - } - }; - private final HttpResponse response; - // FIXME ResponseStatus should have an abstract prepareResponse(headers, bodyParts) method instead of being passed the provider! - public ResponseStatus(URI uri, HttpResponse response) { - super(uri, fakeProvider); + public ResponseStatus(URI uri, HttpResponse response, AsyncHttpClientConfig config) { + super(uri, config); this.response = response; } + + @Override + public Response prepareResponse(HttpResponseHeaders headers, List bodyParts) { + return new NettyResponse(this, headers, bodyParts); + } /** * Return the response status code @@ -97,5 +81,4 @@ public int getProtocolMinorVersion() { public String getProtocolText() { return response.getProtocolVersion().text(); } - } diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncResponseTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncResponseTest.java index e679303540..e20854bba7 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncResponseTest.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncResponseTest.java @@ -43,7 +43,7 @@ public void testCookieParseExpires() { final String cookieDef = String.format("efmembercheck=true; expires=%s; path=/; domain=.eclipse.org", sdf.format(date)); NettyResponse - response = new NettyResponse(new ResponseStatus(null, null), new HttpResponseHeaders(null, null, false) { + response = new NettyResponse(new ResponseStatus(null, null, null), new HttpResponseHeaders() { @Override public FluentCaseInsensitiveStringsMap getHeaders() { return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); @@ -60,7 +60,7 @@ public FluentCaseInsensitiveStringsMap getHeaders() { @Test(groups = "standalone") public void testCookieParseMaxAge() { final String cookieDef = "efmembercheck=true; max-age=60; path=/; domain=.eclipse.org"; - NettyResponse response = new NettyResponse(new ResponseStatus(null, null), new HttpResponseHeaders(null, null, false) { + NettyResponse response = new NettyResponse(new ResponseStatus(null, null, null), new HttpResponseHeaders() { @Override public FluentCaseInsensitiveStringsMap getHeaders() { return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); @@ -76,7 +76,7 @@ public FluentCaseInsensitiveStringsMap getHeaders() { @Test(groups = "standalone") public void testCookieParseWeirdExpiresValue() { final String cookieDef = "efmembercheck=true; expires=60; path=/; domain=.eclipse.org"; - NettyResponse response = new NettyResponse(new ResponseStatus(null, null), new HttpResponseHeaders(null, null, false) { + NettyResponse response = new NettyResponse(new ResponseStatus(null, null, null), new HttpResponseHeaders() { @Override public FluentCaseInsensitiveStringsMap getHeaders() { return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); From 23a101121459dbdcf641f9a05ec6a8c64cde128b Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Oct 2013 13:47:23 +0200 Subject: [PATCH 0005/2254] Minor clean up --- .../BodyDeferringAsyncHandler.java | 2 -- .../providers/jdk/JDKAsyncHttpProvider.java | 32 +++++++++++++++++-- .../PropertiesBasedResumableProcessor.java | 8 +++-- .../resumable/ResumableIOExceptionFilter.java | 8 ++--- .../util/AsyncHttpProviderUtils.java | 30 ----------------- .../asynchttpclient/async/RemoteSiteTest.java | 10 ++---- .../netty/handler/WebSocketProtocol.java | 2 +- 7 files changed, 43 insertions(+), 49 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/BodyDeferringAsyncHandler.java b/api/src/main/java/org/asynchttpclient/BodyDeferringAsyncHandler.java index 1bf49bbb03..3114dcf2e1 100644 --- a/api/src/main/java/org/asynchttpclient/BodyDeferringAsyncHandler.java +++ b/api/src/main/java/org/asynchttpclient/BodyDeferringAsyncHandler.java @@ -12,8 +12,6 @@ */ package org.asynchttpclient; -import org.asynchttpclient.Response.ResponseBuilder; - import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; diff --git a/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProvider.java b/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProvider.java index 061dcd191e..7dfefa7af6 100644 --- a/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProvider.java +++ b/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProvider.java @@ -322,7 +322,7 @@ public T call() throws Exception { InputStream stream = is; if (bufferResponseInMemory || byteToRead <= 0) { int[] lengthWrapper = new int[1]; - byte[] bytes = AsyncHttpProviderUtils.readFully(is, lengthWrapper); + byte[] bytes = readFully(is, lengthWrapper); stream = new ByteArrayInputStream(bytes, 0, lengthWrapper[0]); byteToRead = lengthWrapper[0]; } @@ -410,6 +410,34 @@ public T call() throws Exception { } return null; } + + // FIXME Streams should be streamed, not turned into byte arrays + private final byte[] readFully(InputStream in, int[] lengthWrapper) throws IOException { + // just in case available() returns bogus (or -1), allocate non-trivial chunk + byte[] b = new byte[Math.max(512, in.available())]; + int offset = 0; + while (true) { + int left = b.length - offset; + int count = in.read(b, offset, left); + if (count < 0) { // EOF + break; + } + offset += count; + if (count == left) { // full buffer, need to expand + b = doubleUp(b); + } + } + // wish Java had Tuple return type... + lengthWrapper[0] = offset; + return b; + } + + private final byte[] doubleUp(byte[] b) { + int len = b.length; + byte[] b2 = new byte[len + len]; + System.arraycopy(b, 0, b2, 0, len); + return b2; + } private FilterContext handleIoException(FilterContext fc) throws FilterException { for (IOExceptionFilter asyncFilter : config.getIOExceptionFilters()) { @@ -571,7 +599,7 @@ private void configure(URI uri, HttpURLConnection urlConnection, Request request urlConnection.getOutputStream().write(b); } else if (request.getStreamData() != null) { int[] lengthWrapper = new int[1]; - cachedBytes = AsyncHttpProviderUtils.readFully(request.getStreamData(), lengthWrapper); + cachedBytes = readFully(request.getStreamData(), lengthWrapper); cachedBytesLenght = lengthWrapper[0]; urlConnection.setRequestProperty("Content-Length", String.valueOf(cachedBytesLenght)); urlConnection.setFixedLengthStreamingMode(cachedBytesLenght); diff --git a/api/src/main/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcessor.java b/api/src/main/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcessor.java index 85ebb2bde6..980869a5ad 100644 --- a/api/src/main/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcessor.java +++ b/api/src/main/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcessor.java @@ -96,10 +96,11 @@ private static String append(Map.Entry e) { /** * {@inheritDoc} */ - /* @Override */ + @Override public Map load() { + Scanner scan = null; try { - Scanner scan = new Scanner(new File(TMP, storeName), "UTF-8"); + scan = new Scanner(new File(TMP, storeName), "UTF-8"); scan.useDelimiter("[=\n]"); String key; @@ -115,6 +116,9 @@ public Map load() { } catch (Throwable ex) { // Survive any exceptions log.warn(ex.getMessage(), ex); + } finally { + if (scan != null) + scan.close(); } return properties; } diff --git a/api/src/main/java/org/asynchttpclient/resumable/ResumableIOExceptionFilter.java b/api/src/main/java/org/asynchttpclient/resumable/ResumableIOExceptionFilter.java index 456360ae15..87868e35de 100644 --- a/api/src/main/java/org/asynchttpclient/resumable/ResumableIOExceptionFilter.java +++ b/api/src/main/java/org/asynchttpclient/resumable/ResumableIOExceptionFilter.java @@ -18,18 +18,16 @@ import org.asynchttpclient.filter.IOExceptionFilter; /** - * Simple {@link org.asynchttpclient.filter.IOExceptionFilter} that replay the current {@link org.asynchttpclient.Request} using - * a {@link ResumableAsyncHandler} + * Simple {@link org.asynchttpclient.filter.IOExceptionFilter} that replay the current {@link org.asynchttpclient.Request} using a {@link ResumableAsyncHandler} */ public class ResumableIOExceptionFilter implements IOExceptionFilter { - public FilterContext filter(FilterContext ctx) throws FilterException { + public FilterContext filter(FilterContext ctx) throws FilterException { if (ctx.getIOException() != null && ctx.getAsyncHandler() instanceof ResumableAsyncHandler) { Request request = ResumableAsyncHandler.class.cast(ctx.getAsyncHandler()).adjustRequestRange(ctx.getRequest()); - return new FilterContext.FilterContextBuilder(ctx).request(request).replayRequest(true).build(); + return new FilterContext.FilterContextBuilder(ctx).request(request).replayRequest(true).build(); } return ctx; } } - \ No newline at end of file diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java index a98cd8a33b..500ed4a290 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java @@ -14,7 +14,6 @@ import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; -import java.io.IOException; import java.io.InputStream; import java.io.SequenceInputStream; import java.io.UnsupportedEncodingException; @@ -379,35 +378,6 @@ public final static MultipartRequestEntity createMultipartRequestEntity(List cookies) { StringBuilder sb = new StringBuilder(); diff --git a/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java b/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java index a5025ffb17..e5b7c85d43 100644 --- a/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java +++ b/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java @@ -23,6 +23,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.apache.commons.io.IOUtils; import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; @@ -33,7 +34,6 @@ import org.asynchttpclient.Request; import org.asynchttpclient.RequestBuilder; import org.asynchttpclient.Response; -import org.asynchttpclient.util.AsyncHttpProviderUtils; import org.testng.annotations.Test; /** @@ -173,13 +173,9 @@ public void asyncFullBodyProperlyRead() throws Exception { Response r = client.prepareGet("http://www.cyberpresse.ca/").execute().get(); InputStream stream = r.getResponseBodyAsStream(); - // FIXME available is an ESTIMATE!!! - int available = stream.available(); - int[] lengthWrapper = new int[1]; - AsyncHttpProviderUtils.readFully(stream, lengthWrapper); - int byteToRead = lengthWrapper[0]; + int contentLength = Integer.valueOf(r.getHeader("Content-Length")); - assertEquals(available, byteToRead); + assertEquals(contentLength, IOUtils.toByteArray(stream).length); } finally { client.close(); } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java index d29c3a85b0..49bdadc313 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java @@ -184,7 +184,7 @@ public void onError(ChannelHandlerContext ctx, Throwable e) { return; } - NettyResponseFuture nettyResponse = (NettyResponseFuture) attribute; + NettyResponseFuture nettyResponse = (NettyResponseFuture) attribute; WebSocketUpgradeHandler h = WebSocketUpgradeHandler.class.cast(nettyResponse.getAsyncHandler()); NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted()); From 86605899a1a59a0d19b64feebe6bfdcdca72776e Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Oct 2013 14:26:23 +0200 Subject: [PATCH 0006/2254] Fix webdav test --- .../webdav/WebDavCompletionHandlerBase.java | 105 +++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java b/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java index 6781d561cd..1386adb160 100644 --- a/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java +++ b/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java @@ -15,6 +15,8 @@ import org.asynchttpclient.AsyncCompletionHandlerBase; import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.Cookie; +import org.asynchttpclient.FluentCaseInsensitiveStringsMap; import org.asynchttpclient.HttpResponseBodyPart; import org.asynchttpclient.HttpResponseHeaders; import org.asynchttpclient.HttpResponseStatus; @@ -32,6 +34,9 @@ import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URI; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -127,7 +132,105 @@ public HttpStatusWrapper(HttpResponseStatus wrapper, String statusText, int stat @Override public Response prepareResponse(HttpResponseHeaders headers, List bodyParts) { - return wrapped.prepareResponse(headers, bodyParts); + final Response wrappedResponse = wrapped.prepareResponse(headers, bodyParts); + + return new Response() { + + @Override + public int getStatusCode() { + return statusCode; + } + + @Override + public String getStatusText() { + return statusText; + } + + @Override + public byte[] getResponseBodyAsBytes() throws IOException { + return wrappedResponse.getResponseBodyAsBytes(); + } + + @Override + public ByteBuffer getResponseBodyAsByteBuffer() throws IOException { + return wrappedResponse.getResponseBodyAsByteBuffer(); + } + + @Override + public InputStream getResponseBodyAsStream() throws IOException { + return wrappedResponse.getResponseBodyAsStream(); + } + + @Override + public String getResponseBodyExcerpt(int maxLength, String charset) throws IOException { + return wrappedResponse.getResponseBodyExcerpt(maxLength, charset); + } + + @Override + public String getResponseBody(String charset) throws IOException { + return wrappedResponse.getResponseBody(charset); + } + + @Override + public String getResponseBodyExcerpt(int maxLength) throws IOException { + return wrappedResponse.getResponseBodyExcerpt(maxLength); + } + + @Override + public String getResponseBody() throws IOException { + return wrappedResponse.getResponseBody(); + } + + @Override + public URI getUri() throws MalformedURLException { + return wrappedResponse.getUri(); + } + + @Override + public String getContentType() { + return wrappedResponse.getContentType(); + } + + @Override + public String getHeader(String name) { + return wrappedResponse.getHeader(name); + } + + @Override + public List getHeaders(String name) { + return wrappedResponse.getHeaders(name); + } + + @Override + public FluentCaseInsensitiveStringsMap getHeaders() { + return wrappedResponse.getHeaders(); + } + + @Override + public boolean isRedirected() { + return wrappedResponse.isRedirected(); + } + + @Override + public List getCookies() { + return wrappedResponse.getCookies(); + } + + @Override + public boolean hasResponseStatus() { + return wrappedResponse.hasResponseStatus(); + } + + @Override + public boolean hasResponseHeaders() { + return wrappedResponse.hasResponseHeaders(); + } + + @Override + public boolean hasResponseBody() { + return wrappedResponse.hasResponseBody(); + } + }; } @Override From 41460f4f6f501b918c7f23ab57c3f30456582cb8 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Oct 2013 14:46:29 +0200 Subject: [PATCH 0007/2254] Removed deprecated APIs --- .../AsyncHttpClientConfig.java | 22 ------- .../java/org/asynchttpclient/ProxyServer.java | 12 ---- .../main/java/org/asynchttpclient/Realm.java | 22 ------- .../java/org/asynchttpclient/Request.java | 8 --- .../asynchttpclient/RequestBuilderBase.java | 61 +++++++++---------- .../SimpleAsyncHttpClient.java | 4 +- .../org/asynchttpclient/multipart/Part.java | 11 ---- .../providers/jdk/JDKAsyncHttpProvider.java | 2 +- 8 files changed, 33 insertions(+), 109 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java index 104137ea0b..3ce08af1ad 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -296,16 +296,6 @@ public boolean getAllowPoolingConnection() { return allowPoolingConnection; } - /** - * Is the {@link ConnectionsPool} support enabled. - * - * @return true if keep-alive is enabled - * @deprecated - Use {@link AsyncHttpClientConfig#getAllowPoolingConnection()} - */ - public boolean getKeepAlive() { - return allowPoolingConnection; - } - /** * Return the USER_AGENT header value * @@ -777,18 +767,6 @@ public Builder setAllowPoolingConnection(boolean allowPoolingConnection) { return this; } - /** - * Set true if connection can be pooled by a {@link ConnectionsPool}. Default is true. - * - * @param allowPoolingConnection true if connection can be pooled by a {@link ConnectionsPool} - * @return a {@link Builder} - * @deprecated - Use {@link AsyncHttpClientConfig.Builder#setAllowPoolingConnection(boolean)} - */ - public Builder setKeepAlive(boolean allowPoolingConnection) { - this.allowPoolingConnection = allowPoolingConnection; - return this; - } - /** * Set the{@link ScheduledExecutorService} used to expire idle connections. * diff --git a/api/src/main/java/org/asynchttpclient/ProxyServer.java b/api/src/main/java/org/asynchttpclient/ProxyServer.java index d7d841c3fc..9cd3ecc70a 100644 --- a/api/src/main/java/org/asynchttpclient/ProxyServer.java +++ b/api/src/main/java/org/asynchttpclient/ProxyServer.java @@ -57,18 +57,6 @@ public String toString() { private String encoding = "UTF-8"; private String ntlmDomain = System.getProperty("http.auth.ntlm.domain", ""); - private boolean isBasic = true; - - @Deprecated - public boolean isBasic() { - return isBasic; - } - - @Deprecated - public void setBasic(boolean isBasic) { - this.isBasic = isBasic; - } - public ProxyServer(final Protocol protocol, final String host, final int port, String principal, String password) { this.protocol = protocol; this.host = host; diff --git a/api/src/main/java/org/asynchttpclient/Realm.java b/api/src/main/java/org/asynchttpclient/Realm.java index de213f74c0..9c09faaedb 100644 --- a/api/src/main/java/org/asynchttpclient/Realm.java +++ b/api/src/main/java/org/asynchttpclient/Realm.java @@ -164,16 +164,6 @@ public boolean getUsePreemptiveAuth() { return usePreemptiveAuth; } - /** - * Return the NTLM domain to use. This value should map the JDK - * - * @return the NTLM domain - * @deprecated - use getNtlmDomain() - */ - public String getDomain() { - return domain; - } - /** * Return the NTLM domain to use. This value should map the JDK * @@ -280,17 +270,6 @@ public static class RealmBuilder { private String host = "localhost"; private boolean messageType2Received = false; - @Deprecated - public String getDomain() { - return domain; - } - - @Deprecated - public RealmBuilder setDomain(String domain) { - this.domain = domain; - return this; - } - public String getNtlmDomain() { return domain; } @@ -630,5 +609,4 @@ public Realm build() { opaque); } } - } diff --git a/api/src/main/java/org/asynchttpclient/Request.java b/api/src/main/java/org/asynchttpclient/Request.java index 2c68f6df5b..48cabc6a76 100644 --- a/api/src/main/java/org/asynchttpclient/Request.java +++ b/api/src/main/java/org/asynchttpclient/Request.java @@ -120,14 +120,6 @@ public interface Request { */ public BodyGenerator getBodyGenerator(); - /** - * Return the current size of the content-lenght header based on the body's size. - * - * @return the current size of the content-lenght header based on the body's size. - * @deprecated - */ - public long getLength(); - /** * Return the current size of the content-lenght header based on the body's size. * diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java index c1bbc4b289..0a0001f8bc 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -98,7 +98,7 @@ public RequestImpl(Request prototype) { this.proxyServer = prototype.getProxyServer(); this.realm = prototype.getRealm(); this.file = prototype.getFile(); - this.followRedirects = prototype.isRedirectOverrideSet()? prototype.isRedirectEnabled() : null; + this.followRedirects = prototype.isRedirectOverrideSet() ? prototype.isRedirectEnabled() : null; this.requestTimeoutInMs = prototype.getRequestTimeoutInMs(); this.rangeOffset = prototype.getRangeOffset(); this.charset = prototype.getBodyEncoding(); @@ -107,16 +107,17 @@ public RequestImpl(Request prototype) { } } - /* @Override */ - + @Override public String getMethod() { return method; } + @Override public InetAddress getInetAddress() { return address; } + @Override public InetAddress getLocalAddress() { return localAddress; } @@ -130,12 +131,12 @@ private String removeTrailingSlash(URI uri) { } } - /* @Override */ + @Override public String getUrl() { return removeTrailingSlash(getURI()); } - /* @Override */ + @Override public String getRawUrl() { return removeTrailingSlash(getRawURI()); } @@ -208,7 +209,7 @@ private URI toURI(boolean encode) { return URI.create(builder.toString()); } - /* @Override */ + @Override public FluentCaseInsensitiveStringsMap getHeaders() { if (headers == null) { headers = new FluentCaseInsensitiveStringsMap(); @@ -221,87 +222,85 @@ public boolean hasHeaders() { return headers != null && !headers.isEmpty(); } - /* @Override */ + @Override public Collection getCookies() { if (cookies == null) { - cookies = Collections.unmodifiableCollection(Collections.emptyList()); + cookies = Collections.unmodifiableCollection(Collections. emptyList()); } return cookies; } - /* @Override */ + @Override public byte[] getByteData() { return byteData; } - /* @Override */ + @Override public String getStringData() { return stringData; } - /* @Override */ + @Override public InputStream getStreamData() { return streamData; } - /* @Override */ + @Override public BodyGenerator getBodyGenerator() { return bodyGenerator; } - /* @Override */ - - /** - * @return - * @deprecated - */ - public long getLength() { - return length; - } - + @Override public long getContentLength() { return length; } - /* @Override */ + @Override public FluentStringsMap getParams() { return params; } - /* @Override */ + @Override public List getParts() { return parts; } - /* @Override */ + @Override public String getVirtualHost() { return virtualHost; } + @Override public FluentStringsMap getQueryParams() { return queryParams; } + @Override public ProxyServer getProxyServer() { return proxyServer; } + @Override public Realm getRealm() { return realm; } + @Override public File getFile() { return file; } + @Override public boolean isRedirectEnabled() { - return (followRedirects != null && followRedirects); + return followRedirects != null && followRedirects; } - public boolean isRedirectOverrideSet(){ + @Override + public boolean isRedirectOverrideSet() { return followRedirects != null; } + @Override public int getRequestTimeoutInMs() { return requestTimeoutInMs; } @@ -377,10 +376,10 @@ public T setUrl(String url) { } public T setInetAddress(InetAddress address) { - request.address = address; - return derived.cast(this); + request.address = address; + return derived.cast(this); } - + public T setLocalInetAddress(InetAddress address) { request.localAddress = address; return derived.cast(this); @@ -407,7 +406,7 @@ private URI buildURI(String url) { url = s + url.substring(uri.getScheme().length() + 1); return buildURI(url); } else { - throw new IllegalArgumentException("Invalid url " + uri.toString()); + throw new IllegalArgumentException("Invalid url " + uri.toString()); } } diff --git a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java index e4cd81b893..8f38f7ef44 100644 --- a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java +++ b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java @@ -557,8 +557,8 @@ public Builder setRequestCompressionLevel(int requestCompressionLevel) { return this; } - public Builder setRealmDomain(String domain) { - realm().setDomain(domain); + public Builder setRealmNtlmDomain(String domain) { + realm().setNtlmDomain(domain); return this; } diff --git a/api/src/main/java/org/asynchttpclient/multipart/Part.java b/api/src/main/java/org/asynchttpclient/multipart/Part.java index 3d71bf5240..40d8b90382 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/Part.java +++ b/api/src/main/java/org/asynchttpclient/multipart/Part.java @@ -116,16 +116,6 @@ public abstract class Part implements org.asynchttpclient.Part { */ static final byte[] CONTENT_ID_BYTES = MultipartEncodingUtil.getAsciiBytes(CONTENT_ID); - /** - * Return the boundary string. - * - * @return the boundary string - * @deprecated uses a constant string. Rather use {@link #getPartBoundary} - */ - public static String getBoundary() { - return BOUNDARY; - } - /** * The ASCII bytes to use as the multipart boundary. */ @@ -165,7 +155,6 @@ public static String getBoundary() { * Gets the part boundary to be used. * * @return the part boundary as an array of bytes. - * @since 3.0 */ protected byte[] getPartBoundary() { if (boundaryBytes == null) { diff --git a/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProvider.java b/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProvider.java index 7dfefa7af6..d6cec0a5b5 100644 --- a/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProvider.java +++ b/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProvider.java @@ -544,7 +544,7 @@ private void configure(URI uri, HttpURLConnection urlConnection, Request request break; case NTLM: jdkNtlmDomain = System.getProperty(NTLM_DOMAIN); - System.setProperty(NTLM_DOMAIN, realm.getDomain()); + System.setProperty(NTLM_DOMAIN, realm.getNtlmDomain()); break; case NONE: break; From 20392262064c5ec1ac89b039d4ef8ed6d3ab709a Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Oct 2013 14:50:06 +0200 Subject: [PATCH 0008/2254] Port 169051cb72ff8487696cd7e19a20e3654f50fc49 on master --- .../org/asynchttpclient/multipart/MultipartBody.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java b/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java index 9f5ec1d756..70d15e21e2 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java +++ b/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java @@ -579,18 +579,20 @@ private long writeToTarget(WritableByteChannel target, ByteArrayOutputStream byt final SocketChannel channel = (SocketChannel) target; channel.register(selector, SelectionKey.OP_WRITE); - while (written < byteWriter.size() && selector.select() != 0) { + while (written < byteWriter.size()) { + selector.select(1000); + maxSpin++; final Set selectedKeys = selector.selectedKeys(); for (SelectionKey key : selectedKeys) { if (key.isWritable()) { written += target.write(message); + maxSpin = 0; } } - } - - if (written < byteWriter.size()) { - throw new IOException("Unable to write on channel " + target); + if (maxSpin >= 10) { + throw new IOException("Unable to write on channel " + target); + } } } finally { selector.close(); From 0db5154bb4e010bf4c9b6cc6d69323860e18b066 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Oct 2013 14:50:23 +0200 Subject: [PATCH 0009/2254] Port fb2daaca6441d9846db25ffccc194f9921b5a3c8 on master --- api/src/main/java/org/asynchttpclient/multipart/FilePart.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/asynchttpclient/multipart/FilePart.java b/api/src/main/java/org/asynchttpclient/multipart/FilePart.java index 6cbab86a56..1d06806710 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/FilePart.java +++ b/api/src/main/java/org/asynchttpclient/multipart/FilePart.java @@ -148,9 +148,9 @@ public FilePart(String name, String fileName, File file, String contentType, Str * @throws java.io.IOException If an IO problem occurs */ protected void sendDispositionHeader(OutputStream out) throws IOException { + super.sendDispositionHeader(out); String filename = this.source.getFileName(); if (filename != null) { - super.sendDispositionHeader(out); out.write(FILE_NAME_BYTES); out.write(QUOTE_BYTES); out.write(MultipartEncodingUtil.getAsciiBytes(filename)); From 91fc8269c3e17320a7c8015186b697a754458048 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Oct 2013 15:19:34 +0200 Subject: [PATCH 0010/2254] Drop JDK provider, close #392 --- .../org/asynchttpclient/AsyncHttpClient.java | 11 +- .../asynchttpclient/AsyncHttpProvider.java | 2 - .../providers/jdk/JDKAsyncHttpProvider.java | 759 ------------------ .../jdk/JDKAsyncHttpProviderConfig.java | 44 - .../providers/jdk/JDKDelegateFuture.java | 84 -- .../providers/jdk/JDKFuture.java | 182 ----- .../providers/jdk/JDKResponse.java | 63 -- .../providers/jdk/ResponseBodyPart.java | 92 --- .../providers/jdk/ResponseHeaders.java | 59 -- .../providers/jdk/ResponseStatus.java | 89 -- 10 files changed, 3 insertions(+), 1382 deletions(-) delete mode 100644 api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProvider.java delete mode 100644 api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProviderConfig.java delete mode 100644 api/src/main/java/org/asynchttpclient/providers/jdk/JDKDelegateFuture.java delete mode 100644 api/src/main/java/org/asynchttpclient/providers/jdk/JDKFuture.java delete mode 100644 api/src/main/java/org/asynchttpclient/providers/jdk/JDKResponse.java delete mode 100644 api/src/main/java/org/asynchttpclient/providers/jdk/ResponseBodyPart.java delete mode 100644 api/src/main/java/org/asynchttpclient/providers/jdk/ResponseHeaders.java delete mode 100644 api/src/main/java/org/asynchttpclient/providers/jdk/ResponseStatus.java diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClient.java index 7097322940..18ddaa77fe 100755 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClient.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClient.java @@ -147,8 +147,7 @@ public class AsyncHttpClient implements Closeable { */ private static final String[] DEFAULT_PROVIDERS = { "org.asynchttpclient.providers.netty.NettyAsyncHttpProvider", - "org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider", - "org.asynchttpclient.providers.jdk.JDKAsyncHttpProvider" + "org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider" }; private final AsyncHttpProvider httpProvider; @@ -174,8 +173,7 @@ public class AsyncHttpClient implements Closeable { *

  • JDK
  • * * - * If none of those providers are found, then the runtime will default to - * the {@link org.asynchttpclient.providers.jdk.JDKAsyncHttpProvider}. + * If none of those providers are found, then the engine will throw an IllegalStateException. */ public AsyncHttpClient() { this(new AsyncHttpClientConfig.Builder().build()); @@ -200,11 +198,9 @@ public AsyncHttpClient(AsyncHttpProvider provider) { *
      *
    • netty
    • *
    • grizzly
    • - *
    • JDK
    • *
    * - * If none of those providers are found, then the runtime will default to - * the {@link org.asynchttpclient.providers.jdk.JDKAsyncHttpProvider}. + * If none of those providers are found, then the engine will throw an IllegalStateException. * * @param config a {@link AsyncHttpClientConfig} */ @@ -642,7 +638,6 @@ private static AsyncHttpProvider loadProvider(final String className, return null; } - @SuppressWarnings("unchecked") private static AsyncHttpProvider loadDefaultProvider(String[] providerClassNames, AsyncHttpClientConfig config) { AsyncHttpProvider provider; diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpProvider.java b/api/src/main/java/org/asynchttpclient/AsyncHttpProvider.java index c8f910b8fb..848249a6d1 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpProvider.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpProvider.java @@ -20,8 +20,6 @@ /** * Interface to be used when implementing custom asynchronous I/O HTTP client. - * By default, the {@link org.asynchttpclient.providers.jdk.JDKAsyncHttpProvider} is used if - * none of the other provider modules are found on the classpath. */ public interface AsyncHttpProvider extends Closeable { diff --git a/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProvider.java b/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProvider.java deleted file mode 100644 index d6cec0a5b5..0000000000 --- a/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProvider.java +++ /dev/null @@ -1,759 +0,0 @@ -/* - * Copyright (c) 2010-2013 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.jdk; - -import static org.asynchttpclient.util.AsyncHttpProviderUtils.*; -import static org.asynchttpclient.util.MiscUtil.*; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Field; -import java.net.Authenticator; -import java.net.ConnectException; -import java.net.HttpURLConnection; -import java.net.InetSocketAddress; -import java.net.PasswordAuthentication; -import java.net.Proxy; -import java.net.SocketAddress; -import java.net.SocketTimeoutException; -import java.net.URI; -import java.net.UnknownHostException; -import java.nio.ByteBuffer; -import java.security.GeneralSecurityException; -import java.security.NoSuchAlgorithmException; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.zip.GZIPInputStream; - -import javax.naming.AuthenticationException; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLHandshakeException; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.asynchttpclient.Body; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.ListenableFuture; -import org.asynchttpclient.MaxRedirectException; -import org.asynchttpclient.ProgressAsyncHandler; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.Realm; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.filter.FilterContext; -import org.asynchttpclient.filter.FilterException; -import org.asynchttpclient.filter.IOExceptionFilter; -import org.asynchttpclient.filter.ResponseFilter; -import org.asynchttpclient.listener.TransferCompletionHandler; -import org.asynchttpclient.multipart.MultipartRequestEntity; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.asynchttpclient.util.AuthenticatorUtils; -import org.asynchttpclient.util.ProxyUtils; -import org.asynchttpclient.util.SslUtils; -import org.asynchttpclient.util.UTF8UrlEncoder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class JDKAsyncHttpProvider implements AsyncHttpProvider { - private final static Logger logger = LoggerFactory.getLogger(JDKAsyncHttpProvider.class); - - private final static String NTLM_DOMAIN = "http.auth.ntlm.domain"; - - private final AsyncHttpClientConfig config; - - private final AtomicBoolean isClose = new AtomicBoolean(false); - - private final static int MAX_BUFFERED_BYTES = 8192; - - private final AtomicInteger maxConnections = new AtomicInteger(); - - private String jdkNtlmDomain; - - private Authenticator jdkAuthenticator; - - private boolean bufferResponseInMemory = false; - - private ExecutorService service; - - private boolean managedExecutorService; - - public JDKAsyncHttpProvider(AsyncHttpClientConfig config) { - - this.config = config; - service = config.executorService(); - managedExecutorService = (service == null); - if (service == null) { - service = AsyncHttpProviderUtils.createDefaultExecutorService(); - } - AsyncHttpProviderConfig providerConfig = config.getAsyncHttpProviderConfig(); - if (providerConfig instanceof JDKAsyncHttpProviderConfig) { - configure(JDKAsyncHttpProviderConfig.class.cast(providerConfig)); - } - } - - private void configure(JDKAsyncHttpProviderConfig config) { - for (Map.Entry e : config.propertiesSet()) { - System.setProperty(e.getKey(), e.getValue()); - } - - if (config.getProperty(JDKAsyncHttpProviderConfig.FORCE_RESPONSE_BUFFERING) != null) { - bufferResponseInMemory = true; - } - } - - public ListenableFuture execute(Request request, AsyncHandler handler) throws IOException { - return execute(request, handler, null); - } - - public ListenableFuture execute(Request request, AsyncHandler handler, ListenableFuture future) throws IOException { - if (isClose.get()) { - throw new IOException("Closed"); - } - - if (config.getMaxTotalConnections() > -1 && (maxConnections.get() + 1) > config.getMaxTotalConnections()) { - throw new IOException(String.format("Too many connections %s", config.getMaxTotalConnections())); - } - - ProxyServer proxyServer = ProxyUtils.getProxyServer(config, request); - Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - if (proxyServer != null || realm != null) { - try { - configureProxyAndAuth(proxyServer, realm); - } catch (AuthenticationException e) { - throw new IOException(e.getMessage()); - } - } - - HttpURLConnection urlConnection = createUrlConnection(request); - - int requestTimeout = AsyncHttpProviderUtils.requestTimeout(config, request); - - JDKDelegateFuture delegate = null; - if (future != null) { - delegate = new JDKDelegateFuture(handler, requestTimeout, future, urlConnection); - } - - JDKFuture f = (delegate == null) ? new JDKFuture(handler, requestTimeout, urlConnection) : delegate; - f.touch(); - - f.setInnerFuture(service.submit(new AsyncHttpUrlConnection(urlConnection, request, handler, f))); - maxConnections.incrementAndGet(); - - return f; - } - - private HttpURLConnection createUrlConnection(Request request) throws IOException { - ProxyServer proxyServer = ProxyUtils.getProxyServer(config, request); - Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - Proxy proxy = null; - if (proxyServer != null || realm != null) { - try { - proxy = configureProxyAndAuth(proxyServer, realm); - } catch (AuthenticationException e) { - throw new IOException(e.getMessage()); - } - } - - HttpURLConnection urlConnection = (HttpURLConnection) - request.getURI().toURL().openConnection(proxy == null ? Proxy.NO_PROXY : proxy); - - if (request.getUrl().startsWith("https")) { - HttpsURLConnection secure = (HttpsURLConnection) urlConnection; - SSLContext sslContext = config.getSSLContext(); - if (sslContext == null) { - try { - sslContext = SslUtils.getSSLContext(); - } catch (NoSuchAlgorithmException e) { - throw new IOException(e.getMessage()); - } catch (GeneralSecurityException e) { - throw new IOException(e.getMessage()); - } - } - secure.setSSLSocketFactory(sslContext.getSocketFactory()); - secure.setHostnameVerifier(config.getHostnameVerifier()); - } - return urlConnection; - } - - public void close() { - isClose.set(true); - if (managedExecutorService) { - service.shutdownNow(); - } - } - - private final class AsyncHttpUrlConnection implements Callable { - - private HttpURLConnection urlConnection; - private Request request; - private final AsyncHandler asyncHandler; - private final ListenableFuture future; - private int currentRedirectCount; - private AtomicBoolean isAuth = new AtomicBoolean(false); - private byte[] cachedBytes; - private int cachedBytesLenght; - private boolean terminate = true; - - public AsyncHttpUrlConnection(HttpURLConnection urlConnection, Request request, AsyncHandler asyncHandler, ListenableFuture future) { - this.urlConnection = urlConnection; - this.request = request; - this.asyncHandler = asyncHandler; - this.future = future; - this.request = request; - } - - public T call() throws Exception { - AsyncHandler.STATE state = AsyncHandler.STATE.ABORT; - try { - URI uri = null; - // Encoding with URLConnection is a bit bogus so we need to try both way before setting it - try { - uri = AsyncHttpProviderUtils.createUri(request.getRawUrl()); - } catch (IllegalArgumentException u) { - uri = AsyncHttpProviderUtils.createUri(request.getUrl()); - } - - configure(uri, urlConnection, request); - urlConnection.connect(); - - if (asyncHandler instanceof TransferCompletionHandler) { - throw new IllegalStateException(TransferCompletionHandler.class.getName() + "not supported by this provider"); - } - - int statusCode = urlConnection.getResponseCode(); - - logger.debug("\n\nRequest {}\n\nResponse {}\n", request, statusCode); - - ResponseStatus status = new ResponseStatus(uri, urlConnection, config); - FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(asyncHandler).request(request).responseStatus(status).build(); - for (ResponseFilter asyncFilter : config.getResponseFilters()) { - fc = asyncFilter.filter(fc); - if (fc == null) { - throw new NullPointerException("FilterContext is null"); - } - } - - // The request has changed - if (fc.replayRequest()) { - request = fc.getRequest(); - urlConnection = createUrlConnection(request); - terminate = false; - return call(); - } - - boolean redirectEnabled = (request.isRedirectEnabled() || config.isRedirectEnabled()); - if (redirectEnabled && (statusCode == 302 || statusCode == 301)) { - - if (currentRedirectCount++ < config.getMaxRedirects()) { - String location = urlConnection.getHeaderField("Location"); - URI redirUri = AsyncHttpProviderUtils.getRedirectUri(uri, location); - String newUrl = redirUri.toString(); - - if (!newUrl.equals(uri.toString())) { - RequestBuilder builder = new RequestBuilder(request); - - logger.debug("Redirecting to {}", newUrl); - - request = builder.setUrl(newUrl).build(); - urlConnection = createUrlConnection(request); - terminate = false; - return call(); - } - } else { - throw new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects()); - } - } - - Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - if (statusCode == 401 && !isAuth.getAndSet(true) && realm != null) { - String wwwAuth = urlConnection.getHeaderField("WWW-Authenticate"); - - logger.debug("Sending authentication to {}", request.getUrl()); - - Realm nr = new Realm.RealmBuilder().clone(realm) - .parseWWWAuthenticateHeader(wwwAuth) - .setUri(URI.create(request.getUrl()).getPath()) - .setMethodName(request.getMethod()) - .setUsePreemptiveAuth(true) - .build(); - RequestBuilder builder = new RequestBuilder(request); - request = builder.setRealm(nr).build(); - urlConnection = createUrlConnection(request); - terminate = false; - return call(); - } - - state = asyncHandler.onStatusReceived(status); - if (state == AsyncHandler.STATE.CONTINUE) { - state = asyncHandler.onHeadersReceived(new ResponseHeaders(uri, urlConnection, JDKAsyncHttpProvider.this)); - } - - if (state == AsyncHandler.STATE.CONTINUE) { - InputStream is = getInputStream(urlConnection); - String contentEncoding = urlConnection.getHeaderField("Content-Encoding"); - boolean isGZipped = contentEncoding == null ? false : "gzip".equalsIgnoreCase(contentEncoding); - if (isGZipped) { - is = new GZIPInputStream(is); - } - - int byteToRead = urlConnection.getContentLength(); - InputStream stream = is; - if (bufferResponseInMemory || byteToRead <= 0) { - int[] lengthWrapper = new int[1]; - byte[] bytes = readFully(is, lengthWrapper); - stream = new ByteArrayInputStream(bytes, 0, lengthWrapper[0]); - byteToRead = lengthWrapper[0]; - } - - if (byteToRead > 0) { - int minBytes = Math.min(8192, byteToRead); - byte[] bytes = new byte[minBytes]; - int leftBytes = minBytes < 8192 ? minBytes : byteToRead; - int read = 0; - while (leftBytes > -1) { - - read = stream.read(bytes); - if (read == -1) { - break; - } - - future.touch(); - - byte[] b = new byte[read]; - System.arraycopy(bytes, 0, b, 0, read); - leftBytes -= read; - asyncHandler.onBodyPartReceived(new ResponseBodyPart(uri, b, JDKAsyncHttpProvider.this, leftBytes > -1)); - } - } - - if (request.getMethod().equalsIgnoreCase("HEAD")) { - asyncHandler.onBodyPartReceived(new ResponseBodyPart(uri, "".getBytes(), JDKAsyncHttpProvider.this, true)); - } - } - - if (asyncHandler instanceof ProgressAsyncHandler) { - ProgressAsyncHandler progressAsyncHandler = ProgressAsyncHandler.class.cast(asyncHandler); - progressAsyncHandler.onHeaderWriteCompleted(); - progressAsyncHandler.onContentWriteCompleted(); - } - try { - T t = asyncHandler.onCompleted(); - future.content(t); - future.done(); - return t; - } catch (Throwable t) { - RuntimeException ex = new RuntimeException(); - ex.initCause(t); - throw ex; - } - } catch (Throwable t) { - logger.debug(t.getMessage(), t); - - if (t instanceof IOException && !config.getIOExceptionFilters().isEmpty()) { - FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(asyncHandler) - .request(request).ioException(IOException.class.cast(t)).build(); - - try { - fc = handleIoException(fc); - } catch (FilterException e) { - if (config.getMaxTotalConnections() != -1) { - maxConnections.decrementAndGet(); - } - future.done(); - } - - if (fc.replayRequest()) { - request = fc.getRequest(); - urlConnection = createUrlConnection(request); - return call(); - } - } - - try { - future.abort(filterException(t)); - } catch (Throwable t2) { - logger.error(t2.getMessage(), t2); - } - } finally { - if (terminate) { - if (config.getMaxTotalConnections() != -1) { - maxConnections.decrementAndGet(); - } - urlConnection.disconnect(); - if (jdkNtlmDomain != null) { - System.setProperty(NTLM_DOMAIN, jdkNtlmDomain); - } - Authenticator.setDefault(jdkAuthenticator); - } - } - return null; - } - - // FIXME Streams should be streamed, not turned into byte arrays - private final byte[] readFully(InputStream in, int[] lengthWrapper) throws IOException { - // just in case available() returns bogus (or -1), allocate non-trivial chunk - byte[] b = new byte[Math.max(512, in.available())]; - int offset = 0; - while (true) { - int left = b.length - offset; - int count = in.read(b, offset, left); - if (count < 0) { // EOF - break; - } - offset += count; - if (count == left) { // full buffer, need to expand - b = doubleUp(b); - } - } - // wish Java had Tuple return type... - lengthWrapper[0] = offset; - return b; - } - - private final byte[] doubleUp(byte[] b) { - int len = b.length; - byte[] b2 = new byte[len + len]; - System.arraycopy(b, 0, b2, 0, len); - return b2; - } - - private FilterContext handleIoException(FilterContext fc) throws FilterException { - for (IOExceptionFilter asyncFilter : config.getIOExceptionFilters()) { - fc = asyncFilter.filter(fc); - if (fc == null) { - throw new NullPointerException("FilterContext is null"); - } - } - return fc; - } - - private Throwable filterException(Throwable t) { - if (t instanceof UnknownHostException) { - t = new ConnectException(t.getMessage()); - - } else if (t instanceof SocketTimeoutException) { - int requestTimeout = AsyncHttpProviderUtils.requestTimeout(config, request); - t = new TimeoutException("No response received after " + requestTimeout); - - } else if (t instanceof SSLHandshakeException) { - Throwable t2 = new ConnectException(); - t2.initCause(t); - t = t2; - } - - return t; - } - - private void configure(URI uri, HttpURLConnection urlConnection, Request request) throws IOException, AuthenticationException { - - int requestTimeout = AsyncHttpProviderUtils.requestTimeout(config, request); - - if (requestTimeout != 0) { - urlConnection.setConnectTimeout(requestTimeout); - urlConnection.setReadTimeout(requestTimeout); - } - - urlConnection.setInstanceFollowRedirects(false); - String host = uri.getHost(); - String method = request.getMethod(); - - if (request.getVirtualHost() != null) { - host = request.getVirtualHost(); - } - - if (uri.getPort() == -1 || request.getVirtualHost() != null) { - urlConnection.setRequestProperty("Host", host); - } else { - urlConnection.setRequestProperty("Host", host + ":" + uri.getPort()); - } - - - if (config.isCompressionEnabled()) { - urlConnection.setRequestProperty("Accept-Encoding", "gzip"); - } - - if (!method.equalsIgnoreCase("CONNECT")) { - FluentCaseInsensitiveStringsMap h = request.getHeaders(); - if (h != null) { - for (String name : h.keySet()) { - if (!"host".equalsIgnoreCase(name)) { - for (String value : h.get(name)) { - urlConnection.setRequestProperty(name, value); - if (name.equalsIgnoreCase("Expect")) { - throw new IllegalStateException("Expect: 100-Continue not supported"); - } - } - } - } - } - } - - String ka = AsyncHttpProviderUtils.keepAliveHeaderValue(config); - urlConnection.setRequestProperty("Connection", ka); - ProxyServer proxyServer = ProxyUtils.getProxyServer(config, request); - if (proxyServer != null) { - urlConnection.setRequestProperty("Proxy-Connection", ka); - if (proxyServer.getPrincipal() != null) { - urlConnection.setRequestProperty("Proxy-Authorization", AuthenticatorUtils.computeBasicAuthentication(proxyServer)); - } - - if (proxyServer.getProtocol().equals(ProxyServer.Protocol.NTLM)) { - jdkNtlmDomain = System.getProperty(NTLM_DOMAIN); - System.setProperty(NTLM_DOMAIN, proxyServer.getNtlmDomain()); - } - } - - Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - if (realm != null && realm.getUsePreemptiveAuth()) { - switch (realm.getAuthScheme()) { - case BASIC: - urlConnection.setRequestProperty("Authorization", - AuthenticatorUtils.computeBasicAuthentication(realm)); - break; - case DIGEST: - if (isNonEmpty(realm.getNonce())) { - try { - urlConnection.setRequestProperty("Authorization", - AuthenticatorUtils.computeDigestAuthentication(realm)); - } catch (NoSuchAlgorithmException e) { - throw new SecurityException(e); - } - } - break; - case NTLM: - jdkNtlmDomain = System.getProperty(NTLM_DOMAIN); - System.setProperty(NTLM_DOMAIN, realm.getNtlmDomain()); - break; - case NONE: - break; - default: - throw new IllegalStateException(String.format("Invalid Authentication %s", realm.toString())); - } - - } - - // Add default accept headers. - if (request.getHeaders().getFirstValue("Accept") == null) { - urlConnection.setRequestProperty("Accept", "*/*"); - } - - if (request.getHeaders().getFirstValue("User-Agent") != null) { - urlConnection.setRequestProperty("User-Agent", request.getHeaders().getFirstValue("User-Agent")); - } else if (config.getUserAgent() != null) { - urlConnection.setRequestProperty("User-Agent", config.getUserAgent()); - } else { - urlConnection.setRequestProperty("User-Agent", - AsyncHttpProviderUtils.constructUserAgent(JDKAsyncHttpProvider.class, - config)); - } - - if (isNonEmpty(request.getCookies())) { - urlConnection.setRequestProperty("Cookie", AsyncHttpProviderUtils.encodeCookies(request.getCookies())); - } - - String reqType = request.getMethod(); - urlConnection.setRequestMethod(reqType); - - if ("POST".equals(reqType) || "PUT".equals(reqType)) { - urlConnection.setRequestProperty("Content-Length", "0"); - urlConnection.setDoOutput(true); - String bodyCharset = request.getBodyEncoding() == null ? DEFAULT_CHARSET : request.getBodyEncoding(); - - if (cachedBytes != null) { - urlConnection.setRequestProperty("Content-Length", String.valueOf(cachedBytesLenght)); - urlConnection.setFixedLengthStreamingMode(cachedBytesLenght); - urlConnection.getOutputStream().write(cachedBytes, 0, cachedBytesLenght); - } else if (request.getByteData() != null) { - urlConnection.setRequestProperty("Content-Length", String.valueOf(request.getByteData().length)); - urlConnection.setFixedLengthStreamingMode(request.getByteData().length); - - urlConnection.getOutputStream().write(request.getByteData()); - } else if (request.getStringData() != null) { - if (!request.getHeaders().containsKey("Content-Type")) { - urlConnection.setRequestProperty("Content-Type", "text/html;" + bodyCharset); - } - byte[] b = request.getStringData().getBytes(bodyCharset); - urlConnection.setRequestProperty("Content-Length", String.valueOf(b.length)); - urlConnection.getOutputStream().write(b); - } else if (request.getStreamData() != null) { - int[] lengthWrapper = new int[1]; - cachedBytes = readFully(request.getStreamData(), lengthWrapper); - cachedBytesLenght = lengthWrapper[0]; - urlConnection.setRequestProperty("Content-Length", String.valueOf(cachedBytesLenght)); - urlConnection.setFixedLengthStreamingMode(cachedBytesLenght); - - urlConnection.getOutputStream().write(cachedBytes, 0, cachedBytesLenght); - } else if (request.getParams() != null) { - StringBuilder sb = new StringBuilder(); - for (final Map.Entry> paramEntry : request.getParams()) { - final String key = paramEntry.getKey(); - for (final String value : paramEntry.getValue()) { - if (sb.length() > 0) { - sb.append("&"); - } - UTF8UrlEncoder.appendEncoded(sb, key); - sb.append("="); - UTF8UrlEncoder.appendEncoded(sb, value); - } - } - urlConnection.setRequestProperty("Content-Length", String.valueOf(sb.length())); - urlConnection.setFixedLengthStreamingMode(sb.length()); - - if (!request.getHeaders().containsKey("Content-Type")) { - urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - } - urlConnection.getOutputStream().write(sb.toString().getBytes(bodyCharset)); - } else if (request.getParts() != null) { - int lenght = (int) request.getContentLength(); - if (lenght != -1) { - urlConnection.setRequestProperty("Content-Length", String.valueOf(lenght)); - urlConnection.setFixedLengthStreamingMode(lenght); - } - - if (lenght == -1) { - lenght = MAX_BUFFERED_BYTES; - } - - MultipartRequestEntity mre = AsyncHttpProviderUtils.createMultipartRequestEntity(request.getParts(), request.getHeaders()); - - urlConnection.setRequestProperty("Content-Type", mre.getContentType()); - urlConnection.setRequestProperty("Content-Length", String.valueOf(mre.getContentLength())); - - mre.writeRequest(urlConnection.getOutputStream()); - } else if (request.getFile() != null) { - File file = request.getFile(); - if (!file.isFile()) { - throw new IOException(String.format(Thread.currentThread() - + "File %s is not a file or doesn't exist", file.getAbsolutePath())); - } - urlConnection.setRequestProperty("Content-Length", String.valueOf(file.length())); - urlConnection.setFixedLengthStreamingMode((int) file.length()); - - FileInputStream fis = new FileInputStream(file); - try { - OutputStream os = urlConnection.getOutputStream(); - for (final byte[] buffer = new byte[1024 * 16]; ; ) { - int read = fis.read(buffer); - if (read < 0) { - break; - } - os.write(buffer, 0, read); - } - } finally { - fis.close(); - } - } else if (request.getBodyGenerator() != null) { - Body body = request.getBodyGenerator().createBody(); - try { - int length = (int) body.getContentLength(); - if (length < 0) { - length = (int) request.getContentLength(); - } - if (length >= 0) { - urlConnection.setRequestProperty("Content-Length", String.valueOf(length)); - urlConnection.setFixedLengthStreamingMode(length); - } - OutputStream os = urlConnection.getOutputStream(); - for (ByteBuffer buffer = ByteBuffer.allocate(1024 * 8); ; ) { - buffer.clear(); - if (body.read(buffer) < 0) { - break; - } - os.write(buffer.array(), buffer.arrayOffset(), buffer.position()); - } - } finally { - try { - body.close(); - } catch (IOException e) { - logger.warn("Failed to close request body: {}", e.getMessage(), e); - } - } - } - } - } - } - - private Proxy configureProxyAndAuth(final ProxyServer proxyServer, final Realm realm) throws AuthenticationException { - - Proxy proxy = null; - if (proxyServer != null) { - - String proxyHost = proxyServer.getHost().startsWith("http://") - ? proxyServer.getHost().substring("http://".length()) : proxyServer.getHost(); - - SocketAddress addr = new InetSocketAddress(proxyHost, proxyServer.getPort()); - proxy = new Proxy(Proxy.Type.HTTP, addr); - } - - final boolean hasProxy = (proxyServer != null && proxyServer.getPrincipal() != null); - final boolean hasAuthentication = (realm != null && realm.getPrincipal() != null); - if (hasProxy || hasAuthentication) { - - Field f = null; - try { - f = Authenticator.class.getDeclaredField("theAuthenticator"); - - f.setAccessible(true); - jdkAuthenticator = (Authenticator) f.get(Authenticator.class); - } catch (NoSuchFieldException e) { - } catch (IllegalAccessException e) { - } - - - Authenticator.setDefault(new Authenticator() { - protected PasswordAuthentication getPasswordAuthentication() { - if (hasProxy && getRequestingHost().equals(proxyServer.getHost()) - && getRequestingPort() == proxyServer.getPort()) { - String password = ""; - if (proxyServer.getPassword() != null) { - password = proxyServer.getPassword(); - } - return new PasswordAuthentication(proxyServer.getPrincipal(), password.toCharArray()); - } - - if (hasAuthentication) { - return new PasswordAuthentication(realm.getPrincipal(), realm.getPassword().toCharArray()); - } - - return super.getPasswordAuthentication(); - } - }); - } else { - Authenticator.setDefault(null); - } - return proxy; - } - - private InputStream getInputStream(HttpURLConnection urlConnection) throws IOException { - if (urlConnection.getResponseCode() < 400) { - return urlConnection.getInputStream(); - } else { - InputStream ein = urlConnection.getErrorStream(); - return (ein != null) - ? ein : new ByteArrayInputStream(new byte[0]); - } - } - -} diff --git a/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProviderConfig.java b/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProviderConfig.java deleted file mode 100644 index 8d743a21a5..0000000000 --- a/api/src/main/java/org/asynchttpclient/providers/jdk/JDKAsyncHttpProviderConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.jdk; - -import org.asynchttpclient.AsyncHttpProviderConfig; - -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -public class JDKAsyncHttpProviderConfig implements AsyncHttpProviderConfig { - - public static final String FORCE_RESPONSE_BUFFERING = "bufferResponseInMemory"; - - private final ConcurrentHashMap properties = new ConcurrentHashMap(); - - @Override - public JDKAsyncHttpProviderConfig addProperty(String name, String value) { - properties.put(name, value); - return this; - } - - public String getProperty(String name) { - return properties.get(name); - } - - public String removeProperty(String name) { - return properties.remove(name); - } - - public Set> propertiesSet() { - return properties.entrySet(); - } -} diff --git a/api/src/main/java/org/asynchttpclient/providers/jdk/JDKDelegateFuture.java b/api/src/main/java/org/asynchttpclient/providers/jdk/JDKDelegateFuture.java deleted file mode 100644 index 1b4dd34ea0..0000000000 --- a/api/src/main/java/org/asynchttpclient/providers/jdk/JDKDelegateFuture.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.jdk; - -import static org.asynchttpclient.util.DateUtil.millisTime; -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.ListenableFuture; - -import java.net.HttpURLConnection; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -public class JDKDelegateFuture extends JDKFuture { - - private final ListenableFuture delegateFuture; - - public JDKDelegateFuture(AsyncHandler asyncHandler, int responseTimeoutInMs, - ListenableFuture delegateFuture, HttpURLConnection urlConnection) { - super(asyncHandler, responseTimeoutInMs, urlConnection); - this.delegateFuture = delegateFuture; - } - - public void done() { - delegateFuture.done(); - runListeners(); - } - - public void abort(Throwable t) { - if (innerFuture != null) { - innerFuture.cancel(true); - } - delegateFuture.abort(t); - } - - public boolean cancel(boolean mayInterruptIfRunning) { - delegateFuture.cancel(mayInterruptIfRunning); - if (innerFuture != null) { - return innerFuture.cancel(mayInterruptIfRunning); - } else { - return false; - } - } - - public boolean isCancelled() { - if (innerFuture != null) { - return innerFuture.isCancelled(); - } else { - return false; - } - } - - public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - V content = null; - try { - if (innerFuture != null) { - content = innerFuture.get(timeout, unit); - } - } catch (Throwable t) { - if (!contentProcessed.get() && timeout != -1 && ((millisTime() - touch.get()) <= responseTimeoutInMs)) { - return get(timeout, unit); - } - timedOut.set(true); - delegateFuture.abort(t); - } - - if (exception.get() != null) { - delegateFuture.abort(new ExecutionException(exception.get())); - } - delegateFuture.content(content); - delegateFuture.done(); - return content; - } -} diff --git a/api/src/main/java/org/asynchttpclient/providers/jdk/JDKFuture.java b/api/src/main/java/org/asynchttpclient/providers/jdk/JDKFuture.java deleted file mode 100644 index 893312cfcb..0000000000 --- a/api/src/main/java/org/asynchttpclient/providers/jdk/JDKFuture.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.jdk; - -import static org.asynchttpclient.util.DateUtil.millisTime; -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.listenable.AbstractListenableFuture; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.HttpURLConnection; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; - - -public class JDKFuture extends AbstractListenableFuture { - - private final static Logger logger = LoggerFactory.getLogger(JDKFuture.class); - - protected Future innerFuture; - protected final AsyncHandler asyncHandler; - protected final int responseTimeoutInMs; - protected final AtomicBoolean cancelled = new AtomicBoolean(false); - protected final AtomicBoolean timedOut = new AtomicBoolean(false); - protected final AtomicBoolean isDone = new AtomicBoolean(false); - protected final AtomicReference exception = new AtomicReference(); - protected final AtomicLong touch = new AtomicLong(millisTime()); - protected final AtomicBoolean contentProcessed = new AtomicBoolean(false); - protected final HttpURLConnection urlConnection; - private boolean writeHeaders; - private boolean writeBody; - - public JDKFuture(AsyncHandler asyncHandler, int responseTimeoutInMs, HttpURLConnection urlConnection) { - this.asyncHandler = asyncHandler; - this.responseTimeoutInMs = responseTimeoutInMs; - this.urlConnection = urlConnection; - writeHeaders = true; - writeBody = true; - } - - protected void setInnerFuture(Future innerFuture) { - this.innerFuture = innerFuture; - } - - public void done() { - isDone.set(true); - runListeners(); - } - - public void abort(Throwable t) { - exception.set(t); - if (innerFuture != null) { - innerFuture.cancel(true); - } - if (!timedOut.get() && !cancelled.get()) { - try { - asyncHandler.onThrowable(t); - } catch (Throwable te) { - logger.debug("asyncHandler.onThrowable", te); - } - } - runListeners(); - } - - public void content(V v) { - } - - public boolean cancel(boolean mayInterruptIfRunning) { - if (!cancelled.get() && innerFuture != null) { - urlConnection.disconnect(); - try { - asyncHandler.onThrowable(new CancellationException()); - } catch (Throwable te) { - logger.debug("asyncHandler.onThrowable", te); - } - cancelled.set(true); - runListeners(); - return innerFuture.cancel(mayInterruptIfRunning); - } else { - runListeners(); - return false; - } - } - - public boolean isCancelled() { - if (innerFuture != null) { - return innerFuture.isCancelled(); - } else { - return false; - } - } - - public boolean isDone() { - contentProcessed.set(true); - return innerFuture.isDone(); - } - - public V get() throws InterruptedException, ExecutionException { - try { - return get(responseTimeoutInMs, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - throw new ExecutionException(e); - } - } - - public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - V content = null; - try { - if (innerFuture != null) { - content = innerFuture.get(timeout, unit); - } - } catch (TimeoutException t) { - if (!contentProcessed.get() && timeout != -1 && ((millisTime() - touch.get()) <= responseTimeoutInMs)) { - return get(timeout, unit); - } - - if (exception.get() == null) { - timedOut.set(true); - throw new ExecutionException(new TimeoutException(String.format("No response received after %s", responseTimeoutInMs))); - } - } catch (CancellationException ce) { - } - - if (exception.get() != null) { - throw new ExecutionException(exception.get()); - } - return content; - } - - /** - * Is the Future still valid - * - * @return true if response has expired and should be terminated. - */ - public boolean hasExpired() { - return responseTimeoutInMs != -1 && ((millisTime() - touch.get()) > responseTimeoutInMs); - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public void touch() { - touch.set(millisTime()); - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public boolean getAndSetWriteHeaders(boolean writeHeaders) { - boolean b = this.writeHeaders; - this.writeHeaders = writeHeaders; - return b; - } - - /** - * {@inheritDoc} - */ - /* @Override */ - public boolean getAndSetWriteBody(boolean writeBody) { - boolean b = this.writeBody; - this.writeBody = writeBody; - return b; - } -} diff --git a/api/src/main/java/org/asynchttpclient/providers/jdk/JDKResponse.java b/api/src/main/java/org/asynchttpclient/providers/jdk/JDKResponse.java deleted file mode 100644 index c3b536e971..0000000000 --- a/api/src/main/java/org/asynchttpclient/providers/jdk/JDKResponse.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.jdk; - -import org.asynchttpclient.org.jboss.netty.handler.codec.http.CookieDecoder; -import org.asynchttpclient.Cookie; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.providers.ResponseBase; -import org.asynchttpclient.util.AsyncHttpProviderUtils; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -public class JDKResponse extends ResponseBase { - - public JDKResponse(HttpResponseStatus status, - HttpResponseHeaders headers, - List bodyParts) { - super(status, headers, bodyParts); - } - - /* @Override */ - - public String getResponseBodyExcerpt(int maxLength) throws IOException { - return getResponseBodyExcerpt(maxLength, DEFAULT_CHARSET); - } - - public String getResponseBodyExcerpt(int maxLength, String charset) throws IOException { - // should be fine; except that it may split multi-byte chars (last char may become '?') - byte[] b = AsyncHttpProviderUtils.contentToBytes(bodyParts, maxLength); - return new String(b, charset); - } - - /* @Override */ - public List buildCookies() { - List cookies = new ArrayList(); - for (Map.Entry> header : headers.getHeaders().entrySet()) { - if (header.getKey().equalsIgnoreCase("Set-Cookie")) { - // TODO: ask for parsed header - List v = header.getValue(); - for (String value : v) { - cookies.addAll(CookieDecoder.decode(value)); - } - } - } - return Collections.unmodifiableList(cookies); - } -} diff --git a/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseBodyPart.java b/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseBodyPart.java deleted file mode 100644 index b80ced8345..0000000000 --- a/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseBodyPart.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.jdk; - -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.HttpResponseBodyPart; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URI; -import java.nio.ByteBuffer; - -/** - * A callback class used when an HTTP response body is received. - */ -public class ResponseBodyPart extends HttpResponseBodyPart { - - private final byte[] chunk; - private final boolean isLast; - private boolean closeConnection; - - public ResponseBodyPart(URI uri, byte[] chunk, AsyncHttpProvider provider, boolean last) { - this.chunk = chunk; - isLast = last; - } - - /** - * Return the response body's part bytes received. - * - * @return the response body's part bytes received. - */ - public byte[] getBodyPartBytes() { - return chunk; - } - - @Override - public InputStream readBodyPartBytes() { - return new ByteArrayInputStream(chunk); - } - - @Override - public int length() { - return chunk.length; - } - - @Override - public int writeTo(OutputStream outputStream) throws IOException { - outputStream.write(chunk); - return chunk.length; - } - - @Override - public ByteBuffer getBodyByteBuffer() { - return ByteBuffer.wrap(chunk); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isLast() { - return isLast; - } - - /** - * {@inheritDoc} - */ - @Override - public void markUnderlyingConnectionAsClosed() { - closeConnection = true; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean closeUnderlyingConnection() { - return closeConnection; - } -} \ No newline at end of file diff --git a/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseHeaders.java b/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseHeaders.java deleted file mode 100644 index ed71a96aa7..0000000000 --- a/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseHeaders.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.jdk; - -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseHeaders; - -import java.net.HttpURLConnection; -import java.net.URI; -import java.util.List; -import java.util.Map; - -/** - * A class that represent the HTTP headers. - */ -public class ResponseHeaders extends HttpResponseHeaders { - - private final HttpURLConnection urlConnection; - private final FluentCaseInsensitiveStringsMap headers; - - public ResponseHeaders(URI uri, HttpURLConnection urlConnection, AsyncHttpProvider provider) { - this.urlConnection = urlConnection; - headers = computerHeaders(); - } - - private FluentCaseInsensitiveStringsMap computerHeaders() { - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - - Map> uh = urlConnection.getHeaderFields(); - - for (Map.Entry> e : uh.entrySet()) { - if (e.getKey() != null) { - h.add(e.getKey(), e.getValue()); - } - } - return h; - } - - /** - * Return the HTTP header - * - * @return an {@link org.asynchttpclient.FluentCaseInsensitiveStringsMap} - */ - @Override - public FluentCaseInsensitiveStringsMap getHeaders() { - return headers; - } -} \ No newline at end of file diff --git a/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseStatus.java b/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseStatus.java deleted file mode 100644 index b5cbc7094a..0000000000 --- a/api/src/main/java/org/asynchttpclient/providers/jdk/ResponseStatus.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. - * - * This program is licensed to you under the Apache License Version 2.0, - * and you may not use this file except in compliance with the Apache License Version 2.0. - * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the Apache License Version 2.0 is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. - */ -package org.asynchttpclient.providers.jdk; - -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.Response; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URI; -import java.util.List; - -/** - * A class that represent the HTTP response' status line (code + text) - */ -public class ResponseStatus extends HttpResponseStatus { - - private final HttpURLConnection urlConnection; - - public ResponseStatus(URI uri, HttpURLConnection urlConnection, AsyncHttpClientConfig config) { - super(uri, config); - this.urlConnection = urlConnection; - } - - @Override - public Response prepareResponse(HttpResponseHeaders headers, List bodyParts) { - return new JDKResponse(this, headers, bodyParts); - } - - /** - * Return the response status code - * - * @return the response status code - */ - public int getStatusCode() { - try { - return urlConnection.getResponseCode(); - } catch (IOException e) { - return 500; - } - } - - /** - * Return the response status text - * - * @return the response status text - */ - public String getStatusText() { - try { - return urlConnection.getResponseMessage(); - } catch (IOException e) { - return "Internal Error"; - } - } - - @Override - public String getProtocolName() { - return "http"; - } - - @Override - public int getProtocolMajorVersion() { - return 1; - } - - @Override - public int getProtocolMinorVersion() { - return 1; //TODO - } - - @Override - public String getProtocolText() { - return ""; //TODO - } - -} \ No newline at end of file From 5a636c71913c901fc3952da427062e420b669893 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Oct 2013 15:29:12 +0200 Subject: [PATCH 0011/2254] Remove useless URI parameter --- .../asynchttpclient/providers/netty/handler/HttpProtocol.java | 2 +- .../providers/netty/handler/WebSocketProtocol.java | 2 +- .../providers/netty/response/ResponseBodyPart.java | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java index 0b871daf61..e768c2f576 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java @@ -417,7 +417,7 @@ public void handle(final ChannelHandlerContext ctx, final NettyResponseFuture fu if (!interrupt && chunk.content().readableBytes() > 0) { // FIXME why - interrupt = updateBodyAndInterrupt(future, handler, new ResponseBodyPart(future.getURI(), chunk.content(), last)); + interrupt = updateBodyAndInterrupt(future, handler, new ResponseBodyPart(chunk.content(), last)); } if (interrupt || last) { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java index 49bdadc313..103a677abd 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java @@ -155,7 +155,7 @@ public void handle(ChannelHandlerContext ctx, NettyResponseFuture future, Object } if (frame.content() != null && frame.content().readableBytes() > 0) { - ResponseBodyPart rp = new ResponseBodyPart(future.getURI(), frame.content(), frame.isFinalFragment()); + ResponseBodyPart rp = new ResponseBodyPart(frame.content(), frame.isFinalFragment()); h.onBodyPartReceived(rp); if (pendingOpcode == OPCODE_BINARY) { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseBodyPart.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseBodyPart.java index e50d36faa8..5248c64d7d 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseBodyPart.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseBodyPart.java @@ -21,7 +21,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.URI; import java.nio.ByteBuffer; import org.asynchttpclient.HttpResponseBodyPart; @@ -36,7 +35,7 @@ public class ResponseBodyPart extends HttpResponseBodyPart { private final boolean last; private boolean closeConnection = false; - public ResponseBodyPart(URI uri, ByteBuf buf, boolean last) { + public ResponseBodyPart(ByteBuf buf, boolean last) { bytes = ByteBufUtil.byteBuf2bytes(buf); this.last = last; } From f2e8218e96a0f7e29b57ab79d49b7848a407c12f Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Oct 2013 15:57:57 +0200 Subject: [PATCH 0012/2254] Minor clean up --- .../netty/future/NettyResponseFuture.java | 62 +++++++++++-------- .../providers/netty/handler/HttpProtocol.java | 15 ++--- .../netty/request/NettyRequestSender.java | 4 +- 3 files changed, 44 insertions(+), 37 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java index ff5790ca3a..9e15cc652f 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java @@ -57,39 +57,43 @@ public enum STATE { NEW, POOLED, RECONNECTED, CLOSED, } + private final int requestTimeoutInMs; + private final AsyncHttpClientConfig config; + private final long start = millisTime(); + private final ConnectionPoolKeyStrategy connectionPoolKeyStrategy; + private final ProxyServer proxyServer; + private final int maxRetry; private final CountDownLatch latch = new CountDownLatch(1); + + // state mutated from outside the event loop private final AtomicBoolean isDone = new AtomicBoolean(false); private final AtomicBoolean isCancelled = new AtomicBoolean(false); - private AsyncHandler asyncHandler; - private final int requestTimeoutInMs; - private final AsyncHttpClientConfig config; - private Request request; - private HttpRequest nettyRequest; - private final AtomicReference content = new AtomicReference(); - private URI uri; - private boolean keepAlive = true; - private HttpResponse httpResponse; - private final AtomicReference exEx = new AtomicReference(); private final AtomicInteger redirectCount = new AtomicInteger(); - private volatile FutureReaper reaperFuture; private final AtomicBoolean inAuth = new AtomicBoolean(false); private final AtomicBoolean statusReceived = new AtomicBoolean(false); private final AtomicLong touch = new AtomicLong(millisTime()); - private final long start = millisTime(); private final AtomicReference state = new AtomicReference(STATE.NEW); private final AtomicBoolean contentProcessed = new AtomicBoolean(false); - private Channel channel; - private boolean reuseChannel = false; private final AtomicInteger currentRetry = new AtomicInteger(0); - private final int maxRetry; + private final AtomicBoolean throwableCalled = new AtomicBoolean(false); + private final AtomicReference content = new AtomicReference(); + private final AtomicReference exEx = new AtomicReference(); + private volatile FutureReaper reaperFuture; + + // state mutated only inside the event loop + private Channel channel; + private URI uri; + private boolean keepAlive = true; + private Request request; + private HttpRequest nettyRequest; + private HttpResponse httpResponse; + private AsyncHandler asyncHandler; + private HttpResponse pendingResponse; + private boolean streamWasAlreadyConsumed; + private boolean reuseChannel; private boolean writeHeaders; private boolean writeBody; - private final AtomicBoolean throwableCalled = new AtomicBoolean(false); - private boolean allowConnect = false; - private final ConnectionPoolKeyStrategy connectionPoolKeyStrategy; - private final ProxyServer proxyServer; - private final AtomicReference pendingResponse = new AtomicReference(); - private final AtomicBoolean streamWasAlreadyConsumed = new AtomicBoolean(false); + private boolean allowConnect; public NettyResponseFuture(URI uri,// Request request,// @@ -247,7 +251,7 @@ public V get(long l, TimeUnit tu) throws InterruptedException, TimeoutException, return getContent(); } - public V getContent() throws ExecutionException { + private V getContent() throws ExecutionException { ExecutionException e = exEx.getAndSet(null); if (e != null) { throw e; @@ -383,12 +387,20 @@ public boolean getAndSetStatusReceived(boolean sr) { return statusReceived.getAndSet(sr); } - public AtomicReference getPendingResponse() { + public HttpResponse getPendingResponse() { return pendingResponse; } - public boolean getAndSetStreamWasAlreadyConsumed() { - return streamWasAlreadyConsumed.getAndSet(true); + public void setPendingResponse(HttpResponse pendingResponse) { + this.pendingResponse = pendingResponse; + } + + public boolean isStreamWasAlreadyConsumed() { + return streamWasAlreadyConsumed; + } + + public void setStreamWasAlreadyConsumed(boolean streamWasAlreadyConsumed) { + this.streamWasAlreadyConsumed = streamWasAlreadyConsumed; } @Override diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java index e768c2f576..6aa436f40a 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java @@ -31,7 +31,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; -import java.util.concurrent.atomic.AtomicReference; import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpClientConfig; @@ -227,8 +226,7 @@ private boolean applyResponseFiltersAndReplayRequest(ChannelHandlerContext ctx, } // The handler may have been wrapped. - handler = fc.getAsyncHandler(); - future.setAsyncHandler(handler); + future.setAsyncHandler(fc.getAsyncHandler()); // The request has changed if (fc.replayRequest()) { @@ -379,20 +377,19 @@ public void handle(final ChannelHandlerContext ctx, final NettyResponseFuture fu HttpRequest nettyRequest = future.getNettyRequest(); AsyncHandler handler = future.getAsyncHandler(); - Request request = future.getRequest(); ProxyServer proxyServer = future.getProxyServer(); try { if (e instanceof HttpResponse) { HttpResponse response = (HttpResponse) e; NettyChannelHandler.LOGGER.debug("\n\nRequest {}\n\nResponse {}\n", nettyRequest, response); - future.getPendingResponse().set(response); + future.setPendingResponse(response); return; } if (e instanceof HttpContent) { - AtomicReference responseRef = future.getPendingResponse(); - HttpResponse response = responseRef.getAndSet(null); + HttpResponse response = future.getPendingResponse(); + future.setPendingResponse(null); if (handler != null) { if (response != null && handleResponseAndExit(ctx, future, handler, nettyRequest, proxyServer, response)) { return; @@ -403,10 +400,6 @@ public void handle(final ChannelHandlerContext ctx, final NettyResponseFuture fu boolean interrupt = false; boolean last = chunk instanceof LastHttpContent; - // FIXME - // Netty 3 provider is broken: in case of trailing headers, - // onHeadersReceived should be called before - // updateBodyAndInterrupt if (last) { LastHttpContent lastChunk = (LastHttpContent) chunk; HttpHeaders trailingHeaders = lastChunk.trailingHeaders(); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java index 8dd47cbae2..5c049d70ba 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java @@ -358,13 +358,15 @@ public void operationComplete(ChannelProgressiveFuture cf) { private boolean sendStreamAndExit(Channel channel, final InputStream is, NettyResponseFuture future) throws IOException { - if (future.getAndSetStreamWasAlreadyConsumed()) { + if (future.isStreamWasAlreadyConsumed()) { if (is.markSupported()) is.reset(); else { LOGGER.warn("Stream has already been consumed and cannot be reset"); return true; } + } else { + future.setStreamWasAlreadyConsumed(true); } channel.write(new ChunkedStream(is), channel.newProgressivePromise()).addListener(new ProgressListener(config, false, future.getAsyncHandler(), future) { From deb72bdfb9e463adf9da991ce7490dbe4f9dcb81 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Oct 2013 16:05:33 +0200 Subject: [PATCH 0013/2254] Minor clean up --- .../providers/netty/future/NettyResponseFuture.java | 1 + .../providers/netty/handler/HttpProtocol.java | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java index 9e15cc652f..73e3027bc0 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java @@ -66,6 +66,7 @@ public enum STATE { private final CountDownLatch latch = new CountDownLatch(1); // state mutated from outside the event loop + // TODO check if they are indeed mutated outside the event loop private final AtomicBoolean isDone = new AtomicBoolean(false); private final AtomicBoolean isCancelled = new AtomicBoolean(false); private final AtomicInteger redirectCount = new AtomicInteger(); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java index 6aa436f40a..a16e2d061a 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java @@ -170,7 +170,7 @@ private List getAuthorizationToken(Iterable> list, return l; } - private void finishUpdate(final NettyResponseFuture future, final ChannelHandlerContext ctx, boolean lastValidChunk) throws IOException { + private void finishUpdate(final NettyResponseFuture future, ChannelHandlerContext ctx, boolean lastValidChunk) throws IOException { if (lastValidChunk && future.isKeepAlive()) { channels.drainChannel(ctx, future); } else { @@ -183,7 +183,7 @@ private void finishUpdate(final NettyResponseFuture future, final ChannelHand markAsDone(future, ctx); } - private final boolean updateBodyAndInterrupt(final NettyResponseFuture future, AsyncHandler handler, HttpResponseBodyPart c) throws Exception { + private final boolean updateBodyAndInterrupt(NettyResponseFuture future, AsyncHandler handler, HttpResponseBodyPart c) throws Exception { boolean state = handler.onBodyPartReceived(c) != STATE.CONTINUE; if (c.closeUnderlyingConnection()) { future.setKeepAlive(false); @@ -191,7 +191,7 @@ private final boolean updateBodyAndInterrupt(final NettyResponseFuture future return state; } - private void markAsDone(final NettyResponseFuture future, final ChannelHandlerContext ctx) throws MalformedURLException { + private void markAsDone(NettyResponseFuture future, final ChannelHandlerContext ctx) throws MalformedURLException { // We need to make sure everything is OK before adding the // connection back to the pool. try { @@ -409,7 +409,6 @@ public void handle(final ChannelHandlerContext ctx, final NettyResponseFuture fu } if (!interrupt && chunk.content().readableBytes() > 0) { - // FIXME why interrupt = updateBodyAndInterrupt(future, handler, new ResponseBodyPart(chunk.content(), last)); } From 1cc86aea143a53dbbaafcd3f351fe933fad0ea60 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Oct 2013 16:39:38 +0200 Subject: [PATCH 0014/2254] Remove ListenableFuture.content, close #393 --- .../main/java/org/asynchttpclient/ListenableFuture.java | 7 ------- .../providers/grizzly/GrizzlyResponseFuture.java | 8 -------- .../providers/netty/future/NettyResponseFuture.java | 4 ---- 3 files changed, 19 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/ListenableFuture.java b/api/src/main/java/org/asynchttpclient/ListenableFuture.java index b7593b0e93..04a636229e 100755 --- a/api/src/main/java/org/asynchttpclient/ListenableFuture.java +++ b/api/src/main/java/org/asynchttpclient/ListenableFuture.java @@ -54,13 +54,6 @@ public interface ListenableFuture extends Future { */ void abort(Throwable t); - /** - * Set the content that will be returned by this instance - * - * @param v the content that will be returned by this instance - */ - void content(V v); - /** * Touch the current instance to prevent external service to times out. */ diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseFuture.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseFuture.java index b53c55fe61..51fcd5c663 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseFuture.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseFuture.java @@ -92,14 +92,6 @@ public void abort(Throwable t) { } - - public void content(V v) { - - delegate.result(v); - - } - - public void touch() { provider.touchConnection(connection, request); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java index 73e3027bc0..2a7868a9ff 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java @@ -323,10 +323,6 @@ public final void abort(final Throwable t) { runListeners(); } - public void content(V v) { - content.set(v); - } - public final Request getRequest() { return request; } From e67638f5cbd081952f1ebdd59d37823631f60c59 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Oct 2013 17:47:49 +0200 Subject: [PATCH 0015/2254] Extract HttpResponseBodyPart creation into a factory so one can chose to bypass auto bytes extraction --- .../asynchttpclient/HttpResponseBodyPart.java | 4 +- .../async/AsyncStreamHandlerTest.java | 2 +- .../grizzly/GrizzlyResponseBodyPart.java | 4 +- .../netty/NettyAsyncHttpProvider.java | 10 +-- .../netty/NettyAsyncHttpProviderConfig.java | 40 +++++++++- .../providers/netty/handler/HttpProtocol.java | 24 +++--- .../netty/handler/NettyChannelHandler.java | 7 +- .../providers/netty/handler/Protocol.java | 5 +- .../netty/handler/WebSocketProtocol.java | 17 +++-- .../response/DefaultResponseBodyPart.java | 66 +++++++++++++++++ .../netty/response/LazyResponseBodyPart.java | 73 +++++++++++++++++++ .../netty/response/ResponseBodyPart.java | 52 ++----------- .../providers/netty/util/ByteBufUtil.java | 43 +++++++++-- 13 files changed, 259 insertions(+), 88 deletions(-) create mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/DefaultResponseBodyPart.java create mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/LazyResponseBodyPart.java diff --git a/api/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java b/api/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java index 42bb8c8158..173f4d384a 100644 --- a/api/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java +++ b/api/src/main/java/org/asynchttpclient/HttpResponseBodyPart.java @@ -75,13 +75,13 @@ public abstract class HttpResponseBodyPart { * underlying TCP connection will be closed as soon as the processing of the response is completed. That * means the underlying connection will never get pooled. */ - public abstract void markUnderlyingConnectionAsClosed(); + public abstract void markUnderlyingConnectionAsToBeClosed(); /** * Return true of the underlying connection will be closed once the response has been fully processed. * * @return true of the underlying connection will be closed once the response has been fully processed. */ - public abstract boolean closeUnderlyingConnection(); + public abstract boolean isUnderlyingConnectionToBeClosed(); } diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java index 1d31d21ac7..7147487156 100644 --- a/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java @@ -546,7 +546,7 @@ public STATE onBodyPartReceived(HttpResponseBodyPart content) throws Exception { builder.accumulate(content); if (content.isLast()) { - content.markUnderlyingConnectionAsClosed(); + content.markUnderlyingConnectionAsToBeClosed(); } return STATE.CONTINUE; } diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseBodyPart.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseBodyPart.java index 4f2236e321..b010cd3c3d 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseBodyPart.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseBodyPart.java @@ -122,7 +122,7 @@ public boolean isLast() { * {@inheritDoc} */ @Override - public void markUnderlyingConnectionAsClosed() { + public void markUnderlyingConnectionAsToBeClosed() { ConnectionManager.markConnectionAsDoNotCache(connection); } @@ -130,7 +130,7 @@ public void markUnderlyingConnectionAsClosed() { * {@inheritDoc} */ @Override - public boolean closeUnderlyingConnection() { + public boolean isUnderlyingConnectionToBeClosed() { return !ConnectionManager.isConnectionCacheable(connection); } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java index f8348abde6..d67821c497 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java @@ -34,7 +34,7 @@ public class NettyAsyncHttpProvider implements AsyncHttpProvider { private static final Logger LOGGER = LoggerFactory.getLogger(NettyAsyncHttpProvider.class); private final AsyncHttpClientConfig config; - private final NettyAsyncHttpProviderConfig asyncHttpProviderConfig; + private final NettyAsyncHttpProviderConfig nettyConfig; private final AtomicBoolean closed = new AtomicBoolean(false); private final Channels channels; private final NettyRequestSender requestSender; @@ -43,13 +43,13 @@ public class NettyAsyncHttpProvider implements AsyncHttpProvider { public NettyAsyncHttpProvider(AsyncHttpClientConfig config) { this.config = config; - asyncHttpProviderConfig = config.getAsyncHttpProviderConfig() instanceof NettyAsyncHttpProviderConfig ? // + nettyConfig = config.getAsyncHttpProviderConfig() instanceof NettyAsyncHttpProviderConfig ? // NettyAsyncHttpProviderConfig.class.cast(config.getAsyncHttpProviderConfig()) : new NettyAsyncHttpProviderConfig(); - channels = new Channels(config, asyncHttpProviderConfig); + channels = new Channels(config, nettyConfig); requestSender = new NettyRequestSender(closed, config, channels); - channelHandler = new NettyChannelHandler(config, requestSender, channels, closed); + channelHandler = new NettyChannelHandler(config, nettyConfig, requestSender, channels, closed); channels.configure(channelHandler); } @@ -72,6 +72,6 @@ public void close() { @Override public ListenableFuture execute(Request request, final AsyncHandler asyncHandler) throws IOException { - return requestSender.sendRequest(request, asyncHandler, null, asyncHttpProviderConfig.isAsyncConnect(), false); + return requestSender.sendRequest(request, asyncHandler, null, nettyConfig.isAsyncConnect(), false); } } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java index 210f1dc8a9..da6af380e2 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java @@ -16,6 +16,7 @@ */ package org.asynchttpclient.providers.netty; +import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; @@ -25,6 +26,10 @@ import java.util.Set; import org.asynchttpclient.AsyncHttpProviderConfig; +import org.asynchttpclient.providers.netty.response.DefaultResponseBodyPart; +import org.asynchttpclient.providers.netty.response.LazyResponseBodyPart; +import org.asynchttpclient.providers.netty.response.ResponseBodyPart; +import org.asynchttpclient.providers.netty.util.ByteBufUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,7 +49,7 @@ public class NettyAsyncHttpProviderConfig implements AsyncHttpProviderConfig properties = new HashMap(); + private ResponseBodyPartFactory bodyPartFactory = new DefaultResponseBodyPartFactory(); + public NettyAsyncHttpProviderConfig() { properties.put(REUSE_ADDRESS, Boolean.FALSE); } @@ -219,9 +226,38 @@ public AdditionalChannelInitializer getWssAdditionalChannelInitializer() { public void setWssAdditionalChannelInitializer(AdditionalChannelInitializer wssAdditionalChannelInitializer) { this.wssAdditionalChannelInitializer = wssAdditionalChannelInitializer; } - + + public ResponseBodyPartFactory getBodyPartFactory() { + return bodyPartFactory; + } + + public void setBodyPartFactory(ResponseBodyPartFactory bodyPartFactory) { + this.bodyPartFactory = bodyPartFactory; + } + public static interface AdditionalChannelInitializer { void initChannel(Channel ch) throws Exception; } + + public static interface ResponseBodyPartFactory { + + ResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last); + } + + public static class DefaultResponseBodyPartFactory implements ResponseBodyPartFactory { + + @Override + public ResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { + return new DefaultResponseBodyPart(ByteBufUtil.byteBuf2Bytes(buf), last); + } + } + + public static class LazyResponseBodyPartFactory implements ResponseBodyPartFactory { + + @Override + public ResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { + return new LazyResponseBodyPart(buf, last); + } + } } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java index a16e2d061a..a595e98cbb 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java @@ -16,7 +16,8 @@ package org.asynchttpclient.providers.netty.handler; import static io.netty.handler.codec.http.HttpResponseStatus.*; -import static org.asynchttpclient.providers.netty.util.HttpUtil.isNTLM; +import static org.asynchttpclient.providers.netty.util.HttpUtil.*; +import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpHeaders; @@ -33,6 +34,7 @@ import java.util.Map.Entry; import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.AsyncHandler.STATE; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.FluentCaseInsensitiveStringsMap; import org.asynchttpclient.HttpResponseBodyPart; @@ -42,26 +44,25 @@ import org.asynchttpclient.Realm; import org.asynchttpclient.Request; import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.AsyncHandler.STATE; import org.asynchttpclient.filter.FilterContext; import org.asynchttpclient.filter.FilterException; import org.asynchttpclient.filter.ResponseFilter; import org.asynchttpclient.ntlm.NTLMEngine; import org.asynchttpclient.ntlm.NTLMEngineException; -import org.asynchttpclient.spnego.SpnegoEngine; import org.asynchttpclient.providers.netty.Callback; +import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.request.NettyRequestSender; -import org.asynchttpclient.providers.netty.response.ResponseBodyPart; import org.asynchttpclient.providers.netty.response.ResponseHeaders; import org.asynchttpclient.providers.netty.response.ResponseStatus; +import org.asynchttpclient.spnego.SpnegoEngine; import org.asynchttpclient.util.AsyncHttpProviderUtils; final class HttpProtocol extends Protocol { - public HttpProtocol(Channels channels, AsyncHttpClientConfig config, NettyRequestSender requestSender) { - super(channels, config, requestSender); + public HttpProtocol(Channels channels, AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, NettyRequestSender requestSender) { + super(channels, config, nettyConfig, requestSender); } private Realm kerberosChallenge(List proxyAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, @@ -183,9 +184,9 @@ private void finishUpdate(final NettyResponseFuture future, ChannelHandlerCon markAsDone(future, ctx); } - private final boolean updateBodyAndInterrupt(NettyResponseFuture future, AsyncHandler handler, HttpResponseBodyPart c) throws Exception { - boolean state = handler.onBodyPartReceived(c) != STATE.CONTINUE; - if (c.closeUnderlyingConnection()) { + private final boolean updateBodyAndInterrupt(NettyResponseFuture future, AsyncHandler handler, HttpResponseBodyPart bodyPart) throws Exception { + boolean state = handler.onBodyPartReceived(bodyPart) != STATE.CONTINUE; + if (bodyPart.isUnderlyingConnectionToBeClosed()) { future.setKeepAlive(false); } return state; @@ -408,8 +409,9 @@ public void handle(final ChannelHandlerContext ctx, final NettyResponseFuture fu } } - if (!interrupt && chunk.content().readableBytes() > 0) { - interrupt = updateBodyAndInterrupt(future, handler, new ResponseBodyPart(chunk.content(), last)); + ByteBuf buf = chunk.content(); + if (!interrupt && buf.readableBytes() > 0) { + interrupt = updateBodyAndInterrupt(future, handler, nettyConfig.getBodyPartFactory().newResponseBodyPart(buf, last)); } if (interrupt || last) { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/NettyChannelHandler.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/NettyChannelHandler.java index fb47346455..03f68ae246 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/NettyChannelHandler.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/NettyChannelHandler.java @@ -30,6 +30,7 @@ import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.providers.netty.Callback; import org.asynchttpclient.providers.netty.DiscardEvent; +import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.future.NettyResponseFutures; @@ -49,13 +50,13 @@ public class NettyChannelHandler extends ChannelInboundHandlerAdapter { private final Protocol httpProtocol; private final Protocol webSocketProtocol; - public NettyChannelHandler(AsyncHttpClientConfig config, NettyRequestSender requestSender, Channels channels, AtomicBoolean isClose) { + public NettyChannelHandler(AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, NettyRequestSender requestSender, Channels channels, AtomicBoolean isClose) { this.config = config; this.requestSender = requestSender; this.channels = channels; this.closed = isClose; - httpProtocol = new HttpProtocol(channels, config, requestSender); - webSocketProtocol = new WebSocketProtocol(channels, config, requestSender); + httpProtocol = new HttpProtocol(channels, config, nettyConfig, requestSender); + webSocketProtocol = new WebSocketProtocol(channels, config, nettyConfig, requestSender); } @Override diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Protocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Protocol.java index dcd456f41d..500a2bc729 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Protocol.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Protocol.java @@ -28,6 +28,7 @@ import org.asynchttpclient.RequestBuilder; import org.asynchttpclient.org.jboss.netty.handler.codec.http.CookieDecoder; import org.asynchttpclient.providers.netty.Callback; +import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.request.NettyRequestSender; @@ -42,10 +43,12 @@ public abstract class Protocol { protected final Channels channels; protected final AsyncHttpClientConfig config; protected final NettyRequestSender requestSender; + protected final NettyAsyncHttpProviderConfig nettyConfig; - public Protocol(Channels channels, AsyncHttpClientConfig config, NettyRequestSender requestSender) { + public Protocol(Channels channels, AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, NettyRequestSender requestSender) { this.channels = channels; this.config = config; + this.nettyConfig = nettyConfig; this.requestSender = requestSender; } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java index 103a677abd..07c59b9e64 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java @@ -15,7 +15,8 @@ */ package org.asynchttpclient.providers.netty.handler; -import static io.netty.handler.codec.http.HttpResponseStatus.SWITCHING_PROTOCOLS; +import static io.netty.handler.codec.http.HttpResponseStatus.*; +import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpResponse; @@ -27,16 +28,17 @@ import java.io.IOException; +import org.asynchttpclient.AsyncHandler.STATE; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.HttpResponseHeaders; import org.asynchttpclient.HttpResponseStatus; import org.asynchttpclient.Request; -import org.asynchttpclient.AsyncHandler.STATE; import org.asynchttpclient.filter.FilterContext; import org.asynchttpclient.filter.FilterException; import org.asynchttpclient.filter.ResponseFilter; import org.asynchttpclient.providers.netty.Constants; import org.asynchttpclient.providers.netty.DiscardEvent; +import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.request.NettyRequestSender; @@ -54,8 +56,8 @@ final class WebSocketProtocol extends Protocol { private static final byte OPCODE_UNKNOWN = -1; protected byte pendingOpcode = OPCODE_UNKNOWN; - public WebSocketProtocol(Channels channels, AsyncHttpClientConfig config, NettyRequestSender requestSender) { - super(channels, config, requestSender); + public WebSocketProtocol(Channels channels, AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, NettyRequestSender requestSender) { + super(channels, config, nettyConfig, requestSender); } // We don't need to synchronize as replacing the "ws-decoder" will @@ -154,14 +156,15 @@ public void handle(ChannelHandlerContext ctx, NettyResponseFuture future, Object pendingOpcode = OPCODE_BINARY; } - if (frame.content() != null && frame.content().readableBytes() > 0) { - ResponseBodyPart rp = new ResponseBodyPart(frame.content(), frame.isFinalFragment()); + ByteBuf buf = frame.content(); + if (buf != null && buf.readableBytes() > 0) { + ResponseBodyPart rp = nettyConfig.getBodyPartFactory().newResponseBodyPart(buf, frame.isFinalFragment()); h.onBodyPartReceived(rp); if (pendingOpcode == OPCODE_BINARY) { webSocket.onBinaryFragment(rp.getBodyPartBytes(), frame.isFinalFragment()); } else { - webSocket.onTextFragment(frame.content().toString(Constants.UTF8), frame.isFinalFragment()); + webSocket.onTextFragment(buf.toString(Constants.UTF8), frame.isFinalFragment()); } } } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/DefaultResponseBodyPart.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/DefaultResponseBodyPart.java new file mode 100644 index 0000000000..80cc12396b --- /dev/null +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/DefaultResponseBodyPart.java @@ -0,0 +1,66 @@ +/* + * Copyright 2010 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package org.asynchttpclient.providers.netty.response; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +/** + * A callback class used when an HTTP response body is received. + */ +public class DefaultResponseBodyPart extends ResponseBodyPart { + + private final byte[] bytes; + + public DefaultResponseBodyPart(byte[] bytes, boolean last) { + super(last); + this.bytes = bytes; + } + + /** + * Return the response body's part bytes received. + * + * @return the response body's part bytes received. + */ + @Override + public byte[] getBodyPartBytes() { + return bytes; + } + + @Override + public InputStream readBodyPartBytes() { + return new ByteArrayInputStream(bytes); + } + + @Override + public int length() { + return bytes.length; + } + + @Override + public int writeTo(OutputStream outputStream) throws IOException { + outputStream.write(bytes); + return length(); + } + + @Override + public ByteBuffer getBodyByteBuffer() { + return ByteBuffer.wrap(bytes); + } +} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/LazyResponseBodyPart.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/LazyResponseBodyPart.java new file mode 100644 index 0000000000..eae64b291a --- /dev/null +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/LazyResponseBodyPart.java @@ -0,0 +1,73 @@ +/* + * Copyright 2010 Ning, Inc. + * + * Ning licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package org.asynchttpclient.providers.netty.response; + +import io.netty.buffer.ByteBuf; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +/** + * A callback class used when an HTTP response body is received. + */ +public class LazyResponseBodyPart extends ResponseBodyPart { + + private static final String ERROR_MESSAGE = "This implementation is intended for one to directly read from the underlying ByteBuf and release after usage. Not for the fainted heart!"; + + private final ByteBuf buf; + + public LazyResponseBodyPart(ByteBuf buf, boolean last) { + super(last); + buf.retain(); + this.buf = buf; + } + + public ByteBuf getBuf() { + return buf; + } + + /** + * Return the response body's part bytes received. + * + * @return the response body's part bytes received. + */ + @Override + public byte[] getBodyPartBytes() { + throw new UnsupportedOperationException(ERROR_MESSAGE); + } + + @Override + public InputStream readBodyPartBytes() { + throw new UnsupportedOperationException(ERROR_MESSAGE); + } + + @Override + public int length() { + throw new UnsupportedOperationException(ERROR_MESSAGE); + } + + @Override + public int writeTo(OutputStream outputStream) throws IOException { + throw new UnsupportedOperationException(ERROR_MESSAGE); + } + + @Override + public ByteBuffer getBodyByteBuffer() { + throw new UnsupportedOperationException(ERROR_MESSAGE); + } +} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseBodyPart.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseBodyPart.java index 5248c64d7d..749433d5c6 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseBodyPart.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseBodyPart.java @@ -15,62 +15,20 @@ */ package org.asynchttpclient.providers.netty.response; -import io.netty.buffer.ByteBuf; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; - import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.providers.netty.util.ByteBufUtil; /** * A callback class used when an HTTP response body is received. */ -public class ResponseBodyPart extends HttpResponseBodyPart { +public abstract class ResponseBodyPart extends HttpResponseBodyPart { - private final byte[] bytes; private final boolean last; - private boolean closeConnection = false; + private boolean closeConnection; - public ResponseBodyPart(ByteBuf buf, boolean last) { - bytes = ByteBufUtil.byteBuf2bytes(buf); + public ResponseBodyPart(boolean last) { this.last = last; } - /** - * Return the response body's part bytes received. - * - * @return the response body's part bytes received. - */ - @Override - public byte[] getBodyPartBytes() { - return bytes; - } - - @Override - public InputStream readBodyPartBytes() { - return new ByteArrayInputStream(bytes); - } - - @Override - public int length() { - return bytes.length; - } - - @Override - public int writeTo(OutputStream outputStream) throws IOException { - outputStream.write(bytes); - return length(); - } - - @Override - public ByteBuffer getBodyByteBuffer() { - return ByteBuffer.wrap(bytes); - } - /** * {@inheritDoc} */ @@ -83,7 +41,7 @@ public boolean isLast() { * {@inheritDoc} */ @Override - public void markUnderlyingConnectionAsClosed() { + public void markUnderlyingConnectionAsToBeClosed() { closeConnection = true; } @@ -91,7 +49,7 @@ public void markUnderlyingConnectionAsClosed() { * {@inheritDoc} */ @Override - public boolean closeUnderlyingConnection() { + public boolean isUnderlyingConnectionToBeClosed() { return closeConnection; } } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ByteBufUtil.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ByteBufUtil.java index fa64563e70..e6998fca21 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ByteBufUtil.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ByteBufUtil.java @@ -17,19 +17,48 @@ import io.netty.buffer.ByteBuf; +import java.util.List; + public class ByteBufUtil { - public static byte[] byteBuf2bytes(ByteBuf b) { - int readable = b.readableBytes(); - int readerIndex = b.readerIndex(); - if (b.hasArray()) { - byte[] array = b.array(); - if (b.arrayOffset() == 0 && readerIndex == 0 && array.length == readable) { + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + public static byte[] byteBuf2Bytes(ByteBuf buf) { + int readable = buf.readableBytes(); + int readerIndex = buf.readerIndex(); + if (buf.hasArray()) { + byte[] array = buf.array(); + if (buf.arrayOffset() == 0 && readerIndex == 0 && array.length == readable) { return array; } } byte[] array = new byte[readable]; - b.getBytes(readerIndex, array); + buf.getBytes(readerIndex, array); return array; } + + public static byte[] byteBufs2Bytes(List bufs) { + + if (bufs.isEmpty()) { + return EMPTY_BYTE_ARRAY; + + } else if (bufs.size() == 1) { + return byteBuf2Bytes(bufs.get(0)); + + } else { + int totalSize = 0; + for (ByteBuf buf : bufs) { + totalSize += buf.readableBytes(); + } + + byte[] bytes = new byte[totalSize]; + int offset = 0; + for (ByteBuf buf : bufs) { + int readable = buf.readableBytes(); + buf.getBytes(buf.readerIndex(), bytes, offset, readable); + offset += readable; + } + return bytes; + } + } } From 51f6ec2e213feee90d085cbb12e620c01934e053 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Oct 2013 17:56:57 +0200 Subject: [PATCH 0016/2254] Fix test: should define a provider --- .../SimpleAsyncClientErrorBehaviourTest.java | 21 +++++-------- ...lySimpleAsyncClientErrorBehaviourTest.java | 30 +++++++++++++++++++ .../GrizzlySimpleAsyncHttpClientTest.java | 9 ------ ...tySimpleAsyncClientErrorBehaviourTest.java | 30 +++++++++++++++++++ 4 files changed, 67 insertions(+), 23 deletions(-) create mode 100644 providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlySimpleAsyncClientErrorBehaviourTest.java create mode 100644 providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettySimpleAsyncClientErrorBehaviourTest.java diff --git a/api/src/test/java/org/asynchttpclient/async/SimpleAsyncClientErrorBehaviourTest.java b/api/src/test/java/org/asynchttpclient/async/SimpleAsyncClientErrorBehaviourTest.java index 76dac21427..b359058ac0 100644 --- a/api/src/test/java/org/asynchttpclient/async/SimpleAsyncClientErrorBehaviourTest.java +++ b/api/src/test/java/org/asynchttpclient/async/SimpleAsyncClientErrorBehaviourTest.java @@ -22,25 +22,24 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.testng.annotations.Test; - -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.Response; import org.asynchttpclient.SimpleAsyncHttpClient; import org.asynchttpclient.SimpleAsyncHttpClient.ErrorDocumentBehaviour; import org.asynchttpclient.consumers.OutputStreamBodyConsumer; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.testng.annotations.Test; /** * @author Benjamin Hanzelmann * */ -public class SimpleAsyncClientErrorBehaviourTest extends AbstractBasicTest { +public abstract class SimpleAsyncClientErrorBehaviourTest extends AbstractBasicTest { + + public abstract String getProviderClass(); @Test(groups = { "standalone", "default_provider" }) public void testAccumulateErrorBody() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setUrl(getTargetUrl() + "/nonexistent").setErrorDocumentBehaviour(ErrorDocumentBehaviour.ACCUMULATE).build(); + SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setUrl(getTargetUrl() + "/nonexistent").setErrorDocumentBehaviour(ErrorDocumentBehaviour.ACCUMULATE).build(); try { ByteArrayOutputStream o = new ByteArrayOutputStream(10); Future future = client.get(new OutputStreamBodyConsumer(o)); @@ -57,7 +56,7 @@ public void testAccumulateErrorBody() throws Exception { @Test(groups = { "standalone", "default_provider" }) public void testOmitErrorBody() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setUrl(getTargetUrl() + "/nonexistent").setErrorDocumentBehaviour(ErrorDocumentBehaviour.OMIT).build(); + SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setUrl(getTargetUrl() + "/nonexistent").setErrorDocumentBehaviour(ErrorDocumentBehaviour.OMIT).build(); try { ByteArrayOutputStream o = new ByteArrayOutputStream(10); Future future = client.get(new OutputStreamBodyConsumer(o)); @@ -72,12 +71,6 @@ public void testOmitErrorBody() throws Exception { } } - @Override - public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { - // disabled - return null; - } - @Override public AbstractHandler configureHandler() throws Exception { return new AbstractHandler() { diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlySimpleAsyncClientErrorBehaviourTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlySimpleAsyncClientErrorBehaviourTest.java new file mode 100644 index 0000000000..db9a6a1579 --- /dev/null +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlySimpleAsyncClientErrorBehaviourTest.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2012 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package org.asynchttpclient.providers.grizzly; + +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.async.SimpleAsyncClientErrorBehaviourTest; + +public class GrizzlySimpleAsyncClientErrorBehaviourTest extends SimpleAsyncClientErrorBehaviourTest { + + @Override + public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { + return GrizzlyProviderUtil.grizzlyProvider(config); + } + + public String getProviderClass() { + return GrizzlyAsyncHttpProvider.class.getName(); + } +} diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlySimpleAsyncHttpClientTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlySimpleAsyncHttpClientTest.java index af99dd39fb..dbd541b93b 100644 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlySimpleAsyncHttpClientTest.java +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlySimpleAsyncHttpClientTest.java @@ -13,18 +13,10 @@ package org.asynchttpclient.providers.grizzly; -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.asynchttpclient.Response; import org.asynchttpclient.async.SimpleAsyncHttpClientTest; -import java.io.IOException; -import java.util.concurrent.Future; - public class GrizzlySimpleAsyncHttpClientTest extends SimpleAsyncHttpClientTest { @Override @@ -35,5 +27,4 @@ public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { public String getProviderClass() { return GrizzlyAsyncHttpProvider.class.getName(); } - } diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettySimpleAsyncClientErrorBehaviourTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettySimpleAsyncClientErrorBehaviourTest.java new file mode 100644 index 0000000000..830e721d0d --- /dev/null +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettySimpleAsyncClientErrorBehaviourTest.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2012 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package org.asynchttpclient.providers.netty; + +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.async.SimpleAsyncClientErrorBehaviourTest; + +public class NettySimpleAsyncClientErrorBehaviourTest extends SimpleAsyncClientErrorBehaviourTest { + + @Override + public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { + return NettyProviderUtil.nettyProvider(config); + } + + public String getProviderClass() { + return NettyProviderUtil.class.getName(); + } +} From 937bc35035dbdadda120e0dc3c1218f8faaf23bc Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Oct 2013 18:13:14 +0200 Subject: [PATCH 0017/2254] Doh! --- .../netty/NettySimpleAsyncClientErrorBehaviourTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettySimpleAsyncClientErrorBehaviourTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettySimpleAsyncClientErrorBehaviourTest.java index 830e721d0d..c57b8b89d4 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettySimpleAsyncClientErrorBehaviourTest.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettySimpleAsyncClientErrorBehaviourTest.java @@ -25,6 +25,6 @@ public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { } public String getProviderClass() { - return NettyProviderUtil.class.getName(); + return NettyAsyncHttpProvider.class.getName(); } } From 2deeffba2284bc362e0e1d0a973c6ee279823fb9 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Oct 2013 22:51:25 +0200 Subject: [PATCH 0018/2254] No need to synchronize ResponseBuilder. bodyParts --- api/src/main/java/org/asynchttpclient/Response.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/Response.java b/api/src/main/java/org/asynchttpclient/Response.java index e8598013f4..bfb96bec5c 100644 --- a/api/src/main/java/org/asynchttpclient/Response.java +++ b/api/src/main/java/org/asynchttpclient/Response.java @@ -22,7 +22,6 @@ import java.net.URI; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** @@ -183,7 +182,7 @@ public interface Response { boolean hasResponseBody(); public static class ResponseBuilder { - private final List bodyParts = Collections.synchronizedList(new ArrayList()); + private final List bodyParts = new ArrayList(); private HttpResponseStatus status; private HttpResponseHeaders headers; From 3345697849c9265ecc311942e041f24b90388aed Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 3 Oct 2013 09:26:40 +0200 Subject: [PATCH 0019/2254] Uncomment @Override as AHC targets JDK6 --- .../AsyncCompletionHandlerBase.java | 2 +- .../org/asynchttpclient/ByteArrayPart.java | 2 +- .../FluentCaseInsensitiveStringsMap.java | 26 +++---- .../org/asynchttpclient/FluentStringsMap.java | 26 +++---- .../java/org/asynchttpclient/StringPart.java | 2 +- .../consumers/AppendableBodyConsumer.java | 4 +- .../consumers/ByteBufferBodyConsumer.java | 4 +- .../consumers/FileBodyConsumer.java | 8 +-- .../consumers/OutputStreamBodyConsumer.java | 4 +- .../extra/AsyncHandlerWrapper.java | 10 +-- .../extra/ThrottleRequestFilter.java | 2 +- .../generators/ByteArrayBodyGenerator.java | 2 +- .../generators/FileBodyGenerator.java | 2 +- .../generators/InputStreamBodyGenerator.java | 2 +- .../providers/ResponseBase.java | 70 +++++++++---------- .../PropertiesBasedResumableProcessor.java | 6 +- .../resumable/ResumableAsyncHandler.java | 10 +-- .../webdav/WebDavCompletionHandlerBase.java | 10 +-- .../webdav/WebDavResponse.java | 2 +- .../async/AsyncStreamHandlerTest.java | 10 +-- .../async/ByteBufferCapacityTest.java | 2 +- .../async/ConnectionPoolTest.java | 2 +- .../async/HostnameVerifierTest.java | 2 +- .../async/PostRedirectGetTest.java | 6 +- .../asynchttpclient/async/PostWithQSTest.java | 6 +- .../async/WebDavBasicTest.java | 2 +- .../netty/response/NettyResponse.java | 12 ++-- 27 files changed, 118 insertions(+), 118 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncCompletionHandlerBase.java b/api/src/main/java/org/asynchttpclient/AsyncCompletionHandlerBase.java index c98ee94507..cd5f0a6087 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncCompletionHandlerBase.java +++ b/api/src/main/java/org/asynchttpclient/AsyncCompletionHandlerBase.java @@ -36,7 +36,7 @@ public Response onCompleted(Response response) throws Exception { /** * {@inheritDoc} */ - /* @Override */ + @Override public void onThrowable(Throwable t) { log.debug(t.getMessage(), t); } diff --git a/api/src/main/java/org/asynchttpclient/ByteArrayPart.java b/api/src/main/java/org/asynchttpclient/ByteArrayPart.java index 87311f0f1b..3ed4d6e314 100644 --- a/api/src/main/java/org/asynchttpclient/ByteArrayPart.java +++ b/api/src/main/java/org/asynchttpclient/ByteArrayPart.java @@ -34,7 +34,7 @@ public ByteArrayPart(String name, String fileName, byte[] data, String mimeType, /** * {@inheritDoc} */ - /* @Override */ + @Override public String getName() { return name; } diff --git a/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java b/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java index 6e948c4d75..77a9e4e20f 100644 --- a/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java +++ b/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java @@ -232,7 +232,7 @@ public FluentCaseInsensitiveStringsMap replaceAll(Map put(String key, List value) { if (key == null) { throw new NullPointerException("Null keys are not allowed"); @@ -247,7 +247,7 @@ public List put(String key, List value) { /** * {@inheritDoc} */ - /* @Override */ + @Override public void putAll(Map> values) { replaceAll(values); } @@ -303,7 +303,7 @@ public FluentCaseInsensitiveStringsMap deleteAll(Collection keys) { /** * {@inheritDoc} */ - /* @Override */ + @Override public List remove(Object key) { if (key == null) { return null; @@ -318,7 +318,7 @@ public List remove(Object key) { /** * {@inheritDoc} */ - /* @Override */ + @Override public void clear() { keyLookup.clear(); values.clear(); @@ -327,7 +327,7 @@ public void clear() { /** * {@inheritDoc} */ - /* @Override */ + @Override public Iterator>> iterator() { return Collections.unmodifiableSet(values.entrySet()).iterator(); } @@ -335,7 +335,7 @@ public Iterator>> iterator() { /** * {@inheritDoc} */ - /* @Override */ + @Override public Set keySet() { return new LinkedHashSet(keyLookup.values()); } @@ -343,7 +343,7 @@ public Set keySet() { /** * {@inheritDoc} */ - /* @Override */ + @Override public Set>> entrySet() { return values.entrySet(); } @@ -351,7 +351,7 @@ public Set>> entrySet() { /** * {@inheritDoc} */ - /* @Override */ + @Override public int size() { return values.size(); } @@ -359,7 +359,7 @@ public int size() { /** * {@inheritDoc} */ - /* @Override */ + @Override public boolean isEmpty() { return values.isEmpty(); } @@ -367,7 +367,7 @@ public boolean isEmpty() { /** * {@inheritDoc} */ - /* @Override */ + @Override public boolean containsKey(Object key) { return key == null ? false : keyLookup.containsKey(key.toString().toLowerCase(Locale.ENGLISH)); } @@ -375,7 +375,7 @@ public boolean containsKey(Object key) { /** * {@inheritDoc} */ - /* @Override */ + @Override public boolean containsValue(Object value) { return values.containsValue(value); } @@ -428,7 +428,7 @@ public String getJoinedValue(String key, String delimiter) { /** * {@inheritDoc} */ - /* @Override */ + @Override public List get(Object key) { if (key == null) { return null; @@ -447,7 +447,7 @@ public List get(Object key) { /** * {@inheritDoc} */ - /* @Override */ + @Override public Collection> values() { return values.values(); } diff --git a/api/src/main/java/org/asynchttpclient/FluentStringsMap.java b/api/src/main/java/org/asynchttpclient/FluentStringsMap.java index 2001413e18..336ffbbc97 100644 --- a/api/src/main/java/org/asynchttpclient/FluentStringsMap.java +++ b/api/src/main/java/org/asynchttpclient/FluentStringsMap.java @@ -184,7 +184,7 @@ public FluentStringsMap replaceAll(Map put(String key, List value) { if (key == null) { throw new NullPointerException("Null keys are not allowed"); @@ -199,7 +199,7 @@ public List put(String key, List value) { /** * {@inheritDoc} */ - /* @Override */ + @Override public void putAll(Map> values) { replaceAll(values); } @@ -248,7 +248,7 @@ public FluentStringsMap deleteAll(Collection keys) { /** * {@inheritDoc} */ - /* @Override */ + @Override public List remove(Object key) { if (key == null) { return null; @@ -263,7 +263,7 @@ public List remove(Object key) { /** * {@inheritDoc} */ - /* @Override */ + @Override public void clear() { values.clear(); } @@ -271,7 +271,7 @@ public void clear() { /** * {@inheritDoc} */ - /* @Override */ + @Override public Iterator>> iterator() { return Collections.unmodifiableSet(values.entrySet()).iterator(); } @@ -279,7 +279,7 @@ public Iterator>> iterator() { /** * {@inheritDoc} */ - /* @Override */ + @Override public Set keySet() { return Collections.unmodifiableSet(values.keySet()); } @@ -287,7 +287,7 @@ public Set keySet() { /** * {@inheritDoc} */ - /* @Override */ + @Override public Set>> entrySet() { return values.entrySet(); } @@ -295,7 +295,7 @@ public Set>> entrySet() { /** * {@inheritDoc} */ - /* @Override */ + @Override public int size() { return values.size(); } @@ -303,7 +303,7 @@ public int size() { /** * {@inheritDoc} */ - /* @Override */ + @Override public boolean isEmpty() { return values.isEmpty(); } @@ -311,7 +311,7 @@ public boolean isEmpty() { /** * {@inheritDoc} */ - /* @Override */ + @Override public boolean containsKey(Object key) { return key == null ? false : values.containsKey(key.toString()); } @@ -319,7 +319,7 @@ public boolean containsKey(Object key) { /** * {@inheritDoc} */ - /* @Override */ + @Override public boolean containsValue(Object value) { return values.containsValue(value); } @@ -372,7 +372,7 @@ public String getJoinedValue(String key, String delimiter) { /** * {@inheritDoc} */ - /* @Override */ + @Override public List get(Object key) { if (key == null) { return null; @@ -384,7 +384,7 @@ public List get(Object key) { /** * {@inheritDoc} */ - /* @Override */ + @Override public Collection> values() { return values.values(); } diff --git a/api/src/main/java/org/asynchttpclient/StringPart.java b/api/src/main/java/org/asynchttpclient/StringPart.java index cd0c617c55..5323a67528 100644 --- a/api/src/main/java/org/asynchttpclient/StringPart.java +++ b/api/src/main/java/org/asynchttpclient/StringPart.java @@ -39,7 +39,7 @@ public StringPart(String name, String value) { /** * {@inheritDoc} */ - /* @Override */ + @Override public String getName() { return name; } diff --git a/api/src/main/java/org/asynchttpclient/consumers/AppendableBodyConsumer.java b/api/src/main/java/org/asynchttpclient/consumers/AppendableBodyConsumer.java index 4ddbde6f84..2f80f3428e 100644 --- a/api/src/main/java/org/asynchttpclient/consumers/AppendableBodyConsumer.java +++ b/api/src/main/java/org/asynchttpclient/consumers/AppendableBodyConsumer.java @@ -39,7 +39,7 @@ public AppendableBodyConsumer(Appendable appendable) { /** * {@inheritDoc} */ - /* @Override */ + @Override public void consume(ByteBuffer byteBuffer) throws IOException { appendable.append(new String(byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(), @@ -50,7 +50,7 @@ public void consume(ByteBuffer byteBuffer) throws IOException { /** * {@inheritDoc} */ - /* @Override */ + @Override public void close() throws IOException { if (appendable instanceof Closeable) { Closeable.class.cast(appendable).close(); diff --git a/api/src/main/java/org/asynchttpclient/consumers/ByteBufferBodyConsumer.java b/api/src/main/java/org/asynchttpclient/consumers/ByteBufferBodyConsumer.java index 8ab4641220..a8b4c748e9 100644 --- a/api/src/main/java/org/asynchttpclient/consumers/ByteBufferBodyConsumer.java +++ b/api/src/main/java/org/asynchttpclient/consumers/ByteBufferBodyConsumer.java @@ -31,7 +31,7 @@ public ByteBufferBodyConsumer(ByteBuffer byteBuffer) { /** * {@inheritDoc} */ - /* @Override */ + @Override public void consume(ByteBuffer byteBuffer) throws IOException { byteBuffer.put(byteBuffer); } @@ -39,7 +39,7 @@ public void consume(ByteBuffer byteBuffer) throws IOException { /** * {@inheritDoc} */ - /* @Override */ + @Override public void close() throws IOException { byteBuffer.flip(); } diff --git a/api/src/main/java/org/asynchttpclient/consumers/FileBodyConsumer.java b/api/src/main/java/org/asynchttpclient/consumers/FileBodyConsumer.java index 531228bccb..8c5660b80b 100644 --- a/api/src/main/java/org/asynchttpclient/consumers/FileBodyConsumer.java +++ b/api/src/main/java/org/asynchttpclient/consumers/FileBodyConsumer.java @@ -32,7 +32,7 @@ public FileBodyConsumer(RandomAccessFile file) { /** * {@inheritDoc} */ - /* @Override */ + @Override public void consume(ByteBuffer byteBuffer) throws IOException { // TODO: Channel.transferFrom may be a good idea to investigate. file.write(byteBuffer.array(), @@ -43,7 +43,7 @@ public void consume(ByteBuffer byteBuffer) throws IOException { /** * {@inheritDoc} */ - /* @Override */ + @Override public void close() throws IOException { file.close(); } @@ -51,7 +51,7 @@ public void close() throws IOException { /** * {@inheritDoc} */ - /* @Override */ + @Override public long getTransferredBytes() throws IOException { return file.length(); } @@ -59,7 +59,7 @@ public long getTransferredBytes() throws IOException { /** * {@inheritDoc} */ - /* @Override */ + @Override public void resume() throws IOException { file.seek(getTransferredBytes()); } diff --git a/api/src/main/java/org/asynchttpclient/consumers/OutputStreamBodyConsumer.java b/api/src/main/java/org/asynchttpclient/consumers/OutputStreamBodyConsumer.java index 7ab4d1184b..15f18ca0cc 100644 --- a/api/src/main/java/org/asynchttpclient/consumers/OutputStreamBodyConsumer.java +++ b/api/src/main/java/org/asynchttpclient/consumers/OutputStreamBodyConsumer.java @@ -32,7 +32,7 @@ public OutputStreamBodyConsumer(OutputStream outputStream) { /** * {@inheritDoc} */ - /* @Override */ + @Override public void consume(ByteBuffer byteBuffer) throws IOException { outputStream.write(byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(), @@ -42,7 +42,7 @@ public void consume(ByteBuffer byteBuffer) throws IOException { /** * {@inheritDoc} */ - /* @Override */ + @Override public void close() throws IOException { outputStream.close(); } diff --git a/api/src/main/java/org/asynchttpclient/extra/AsyncHandlerWrapper.java b/api/src/main/java/org/asynchttpclient/extra/AsyncHandlerWrapper.java index fcbce72bc4..a330dd00b2 100644 --- a/api/src/main/java/org/asynchttpclient/extra/AsyncHandlerWrapper.java +++ b/api/src/main/java/org/asynchttpclient/extra/AsyncHandlerWrapper.java @@ -23,7 +23,7 @@ public AsyncHandlerWrapper(AsyncHandler asyncHandler, Semaphore available) { /** * {@inheritDoc} */ - /* @Override */ + @Override public void onThrowable(Throwable t) { try { asyncHandler.onThrowable(t); @@ -38,7 +38,7 @@ public void onThrowable(Throwable t) { /** * {@inheritDoc} */ - /* @Override */ + @Override public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { return asyncHandler.onBodyPartReceived(bodyPart); } @@ -46,7 +46,7 @@ public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception /** * {@inheritDoc} */ - /* @Override */ + @Override public STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exception { return asyncHandler.onStatusReceived(responseStatus); } @@ -54,7 +54,7 @@ public STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exceptio /** * {@inheritDoc} */ - /* @Override */ + @Override public STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { return asyncHandler.onHeadersReceived(headers); } @@ -62,7 +62,7 @@ public STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { /** * {@inheritDoc} */ - /* @Override */ + @Override public T onCompleted() throws Exception { available.release(); if (logger.isDebugEnabled()) { diff --git a/api/src/main/java/org/asynchttpclient/extra/ThrottleRequestFilter.java b/api/src/main/java/org/asynchttpclient/extra/ThrottleRequestFilter.java index 2dcb6087e2..186547b168 100644 --- a/api/src/main/java/org/asynchttpclient/extra/ThrottleRequestFilter.java +++ b/api/src/main/java/org/asynchttpclient/extra/ThrottleRequestFilter.java @@ -42,7 +42,7 @@ public ThrottleRequestFilter(int maxConnections, int maxWait) { /** * {@inheritDoc} */ - /* @Override */ + @Override public FilterContext filter(FilterContext ctx) throws FilterException { try { diff --git a/api/src/main/java/org/asynchttpclient/generators/ByteArrayBodyGenerator.java b/api/src/main/java/org/asynchttpclient/generators/ByteArrayBodyGenerator.java index 0ff4a346e6..f1e0a97752 100644 --- a/api/src/main/java/org/asynchttpclient/generators/ByteArrayBodyGenerator.java +++ b/api/src/main/java/org/asynchttpclient/generators/ByteArrayBodyGenerator.java @@ -64,7 +64,7 @@ public void close() throws IOException { /** * {@inheritDoc} */ - /* @Override */ + @Override public Body createBody() throws IOException { return new ByteBody(); } diff --git a/api/src/main/java/org/asynchttpclient/generators/FileBodyGenerator.java b/api/src/main/java/org/asynchttpclient/generators/FileBodyGenerator.java index dd6e88dbf9..7f412be62a 100644 --- a/api/src/main/java/org/asynchttpclient/generators/FileBodyGenerator.java +++ b/api/src/main/java/org/asynchttpclient/generators/FileBodyGenerator.java @@ -53,7 +53,7 @@ public FileBodyGenerator(File file, long regionSeek, long regionLength) { /** * {@inheritDoc} */ - /* @Override */ + @Override public RandomAccessBody createBody() throws IOException { return new FileBody(file, regionSeek, regionLength); diff --git a/api/src/main/java/org/asynchttpclient/generators/InputStreamBodyGenerator.java b/api/src/main/java/org/asynchttpclient/generators/InputStreamBodyGenerator.java index fcb2176854..4a72f9a1ac 100644 --- a/api/src/main/java/org/asynchttpclient/generators/InputStreamBodyGenerator.java +++ b/api/src/main/java/org/asynchttpclient/generators/InputStreamBodyGenerator.java @@ -54,7 +54,7 @@ public InputStream getInputStream() { /** * {@inheritDoc} */ - /* @Override */ + @Override public Body createBody() throws IOException { return new ISBody(); } diff --git a/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java b/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java index b7f4dfd410..2a96b2880b 100644 --- a/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java +++ b/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java @@ -17,8 +17,7 @@ import org.asynchttpclient.Response; import org.asynchttpclient.util.AsyncHttpProviderUtils; -public abstract class ResponseBase implements Response -{ +public abstract class ResponseBase implements Response { protected final static String DEFAULT_CHARSET = "ISO-8859-1"; protected final List bodyParts; @@ -26,56 +25,65 @@ public abstract class ResponseBase implements Response protected final HttpResponseStatus status; private List cookies; - protected ResponseBase(HttpResponseStatus status, - HttpResponseHeaders headers, - List bodyParts) - { + protected ResponseBase(HttpResponseStatus status, HttpResponseHeaders headers, List bodyParts) { this.bodyParts = bodyParts; this.headers = headers; this.status = status; } - /* @Override */ + protected abstract List buildCookies(); + + protected String calculateCharset(String charset) { + + if (charset == null) { + String contentType = getContentType(); + if (contentType != null) + charset = AsyncHttpProviderUtils.parseCharset(contentType); // parseCharset can return null + } + return charset != null ? charset : DEFAULT_CHARSET; + } + + @Override public final int getStatusCode() { return status.getStatusCode(); } - /* @Override */ + @Override public final String getStatusText() { return status.getStatusText(); } - /* @Override */ + @Override public final URI getUri() { return status.getUri(); } - /* @Override */ + @Override public final String getContentType() { - return headers != null? getHeader("Content-Type"): null; + return headers != null ? getHeader("Content-Type") : null; } - /* @Override */ + @Override public final String getHeader(String name) { - return headers != null? getHeaders().getFirstValue(name): null; + return headers != null ? getHeaders().getFirstValue(name) : null; } - /* @Override */ + @Override public final List getHeaders(String name) { - return headers != null? getHeaders().get(name): null; + return headers != null ? getHeaders().get(name) : null; } - /* @Override */ + @Override public final FluentCaseInsensitiveStringsMap getHeaders() { - return headers != null? headers.getHeaders(): new FluentCaseInsensitiveStringsMap(); + return headers != null ? headers.getHeaders() : new FluentCaseInsensitiveStringsMap(); } - /* @Override */ + @Override public final boolean isRedirected() { return (status.getStatusCode() >= 300) && (status.getStatusCode() <= 399); } - - /* @Override */ + + @Override public byte[] getResponseBodyAsBytes() throws IOException { return AsyncHttpProviderUtils.contentToBytes(bodyParts); } @@ -84,7 +92,7 @@ public ByteBuffer getResponseBodyAsByteBuffer() throws IOException { return ByteBuffer.wrap(getResponseBodyAsBytes()); } - /* @Override */ + @Override public String getResponseBody() throws IOException { return getResponseBody(DEFAULT_CHARSET); } @@ -93,13 +101,12 @@ public String getResponseBody(String charset) throws IOException { return AsyncHttpProviderUtils.contentToString(bodyParts, calculateCharset(charset)); } - /* @Override */ + @Override public InputStream getResponseBodyAsStream() throws IOException { return AsyncHttpProviderUtils.contentAsStream(bodyParts); } - - protected abstract List buildCookies(); - + + @Override public List getCookies() { if (headers == null) { @@ -113,24 +120,17 @@ public List getCookies() { } - protected String calculateCharset(String charset) { - - if (charset == null) { - String contentType = getContentType(); - if (contentType != null) - charset = AsyncHttpProviderUtils.parseCharset(contentType); // parseCharset can return null - } - return charset != null? charset: DEFAULT_CHARSET; - } - + @Override public boolean hasResponseStatus() { return status != null; } + @Override public boolean hasResponseHeaders() { return headers != null && isNonEmpty(headers.getHeaders()); } + @Override public boolean hasResponseBody() { return isNonEmpty(bodyParts); } diff --git a/api/src/main/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcessor.java b/api/src/main/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcessor.java index 980869a5ad..85ebbeafdd 100644 --- a/api/src/main/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcessor.java +++ b/api/src/main/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcessor.java @@ -36,7 +36,7 @@ public class PropertiesBasedResumableProcessor implements ResumableAsyncHandler. /** * {@inheritDoc} */ - /* @Override */ + @Override public void put(String url, long transferredBytes) { properties.put(url, transferredBytes); } @@ -44,7 +44,7 @@ public void put(String url, long transferredBytes) { /** * {@inheritDoc} */ - /* @Override */ + @Override public void remove(String uri) { if (uri != null) { properties.remove(uri); @@ -54,7 +54,7 @@ public void remove(String uri) { /** * {@inheritDoc} */ - /* @Override */ + @Override public void save(Map map) { log.debug("Saving current download state {}", properties.toString()); FileOutputStream os = null; diff --git a/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java b/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java index 297ed0a0f4..446bc2403f 100644 --- a/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java +++ b/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java @@ -102,7 +102,7 @@ public ResumableAsyncHandler(ResumableProcessor resumableProcessor, boolean accu /** * {@inheritDoc} */ - /* @Override */ + @Override public AsyncHandler.STATE onStatusReceived(final HttpResponseStatus status) throws Exception { responseBuilder.accumulate(status); if (status.getStatusCode() == 200 || status.getStatusCode() == 206) { @@ -121,7 +121,7 @@ public AsyncHandler.STATE onStatusReceived(final HttpResponseStatus status) thro /** * {@inheritDoc} */ - /* @Override */ + @Override public void onThrowable(Throwable t) { if (decoratedAsyncHandler != null) { decoratedAsyncHandler.onThrowable(t); @@ -133,7 +133,7 @@ public void onThrowable(Throwable t) { /** * {@inheritDoc} */ - /* @Override */ + @Override public AsyncHandler.STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { if (accumulateBody) { @@ -160,7 +160,7 @@ public AsyncHandler.STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) thro /** * {@inheritDoc} */ - /* @Override */ + @Override public Response onCompleted() throws Exception { resumableProcessor.remove(url); resumableListener.onAllBytesReceived(); @@ -175,7 +175,7 @@ public Response onCompleted() throws Exception { /** * {@inheritDoc} */ - /* @Override */ + @Override public AsyncHandler.STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { responseBuilder.accumulate(headers); String contentLengthHeader = headers.getHeaders().getFirstValue("Content-Length"); diff --git a/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java b/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java index 1386adb160..fb99f6b63b 100644 --- a/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java +++ b/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java @@ -57,7 +57,7 @@ public abstract class WebDavCompletionHandlerBase implements AsyncHandler /** * {@inheritDoc} */ - /* @Override */ + @Override public final STATE onBodyPartReceived(final HttpResponseBodyPart content) throws Exception { bodies.add(content); return STATE.CONTINUE; @@ -66,7 +66,7 @@ public final STATE onBodyPartReceived(final HttpResponseBodyPart content) throws /** * {@inheritDoc} */ - /* @Override */ + @Override public final STATE onStatusReceived(final HttpResponseStatus status) throws Exception { this.status = status; return STATE.CONTINUE; @@ -75,7 +75,7 @@ public final STATE onStatusReceived(final HttpResponseStatus status) throws Exce /** * {@inheritDoc} */ - /* @Override */ + @Override public final STATE onHeadersReceived(final HttpResponseHeaders headers) throws Exception { this.headers = headers; return STATE.CONTINUE; @@ -84,7 +84,7 @@ public final STATE onHeadersReceived(final HttpResponseHeaders headers) throws E /** * {@inheritDoc} */ - /* @Override */ + @Override public final T onCompleted() throws Exception { if (status != null) { Response response = status.prepareResponse(headers, bodies); @@ -101,7 +101,7 @@ public final T onCompleted() throws Exception { /** * {@inheritDoc} */ - /* @Override */ + @Override public void onThrowable(Throwable t) { logger.debug(t.getMessage(), t); } diff --git a/api/src/main/java/org/asynchttpclient/webdav/WebDavResponse.java b/api/src/main/java/org/asynchttpclient/webdav/WebDavResponse.java index 340e4be2eb..2c39c43f6b 100644 --- a/api/src/main/java/org/asynchttpclient/webdav/WebDavResponse.java +++ b/api/src/main/java/org/asynchttpclient/webdav/WebDavResponse.java @@ -45,7 +45,7 @@ public String getStatusText() { return response.getStatusText(); } - /* @Override */ + @Override public byte[] getResponseBodyAsBytes() throws IOException { return response.getResponseBodyAsBytes(); } diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java index 7147487156..20c36c5745 100644 --- a/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java @@ -431,20 +431,20 @@ public void asyncStreamJustStatusLine() throws Exception { Future statusCode = client.prepareGet(getTargetUrl()).execute(new AsyncHandler() { private int status = -1; - /* @Override */ + @Override public void onThrowable(Throwable t) { whatCalled[OTHER] = true; latch.countDown(); } - /* @Override */ + @Override public STATE onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { whatCalled[OTHER] = true; latch.countDown(); return STATE.ABORT; } - /* @Override */ + @Override public STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exception { whatCalled[STATUS] = true; status = responseStatus.getStatusCode(); @@ -452,14 +452,14 @@ public STATE onStatusReceived(HttpResponseStatus responseStatus) throws Exceptio return STATE.ABORT; } - /* @Override */ + @Override public STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { whatCalled[OTHER] = true; latch.countDown(); return STATE.ABORT; } - /* @Override */ + @Override public Integer onCompleted() throws Exception { whatCalled[COMPLETED] = true; latch.countDown(); diff --git a/api/src/test/java/org/asynchttpclient/async/ByteBufferCapacityTest.java b/api/src/test/java/org/asynchttpclient/async/ByteBufferCapacityTest.java index 47c453e886..d0ccfcee03 100644 --- a/api/src/test/java/org/asynchttpclient/async/ByteBufferCapacityTest.java +++ b/api/src/test/java/org/asynchttpclient/async/ByteBufferCapacityTest.java @@ -80,7 +80,7 @@ public void basicByteBufferTest() throws Exception { try { Response response = c.preparePut(getTargetUrl()).setBody(largeFile).execute(new AsyncCompletionHandlerAdapter() { - /* @Override */ + @Override public STATE onBodyPartReceived(final HttpResponseBodyPart content) throws Exception { byteReceived.addAndGet(content.getBodyByteBuffer().capacity()); return super.onBodyPartReceived(content); diff --git a/api/src/test/java/org/asynchttpclient/async/ConnectionPoolTest.java b/api/src/test/java/org/asynchttpclient/async/ConnectionPoolTest.java index 8bce85b03f..107c8ed6a4 100644 --- a/api/src/test/java/org/asynchttpclient/async/ConnectionPoolTest.java +++ b/api/src/test/java/org/asynchttpclient/async/ConnectionPoolTest.java @@ -244,7 +244,7 @@ public Response onCompleted(Response response) throws Exception { }); client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandlerBase() { - /* @Override */ + @Override public void onThrowable(Throwable t) { if (t.getMessage() != null && t.getMessage().equalsIgnoreCase(THIS_IS_NOT_FOR_YOU)) { count.incrementAndGet(); diff --git a/api/src/test/java/org/asynchttpclient/async/HostnameVerifierTest.java b/api/src/test/java/org/asynchttpclient/async/HostnameVerifierTest.java index 0e2fb055b9..82fc7cd3c0 100644 --- a/api/src/test/java/org/asynchttpclient/async/HostnameVerifierTest.java +++ b/api/src/test/java/org/asynchttpclient/async/HostnameVerifierTest.java @@ -43,7 +43,7 @@ public abstract class HostnameVerifierTest extends AbstractBasicHttpsTest { public static class EchoHandler extends AbstractHandler { - /* @Override */ + @Override public void handle(String pathInContext, Request r, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException, IOException { httpResponse.setContentType(TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); diff --git a/api/src/test/java/org/asynchttpclient/async/PostRedirectGetTest.java b/api/src/test/java/org/asynchttpclient/async/PostRedirectGetTest.java index e6a338cfc0..af02a4ad17 100644 --- a/api/src/test/java/org/asynchttpclient/async/PostRedirectGetTest.java +++ b/api/src/test/java/org/asynchttpclient/async/PostRedirectGetTest.java @@ -94,7 +94,7 @@ public Integer onCompleted(Response response) throws Exception { return response.getStatusCode(); } - /* @Override */ + @Override public void onThrowable(Throwable t) { t.printStackTrace(); fail("Unexpected exception: " + t.getMessage(), t); @@ -129,7 +129,7 @@ public Integer onCompleted(Response response) throws Exception { return response.getStatusCode(); } - /* @Override */ + @Override public void onThrowable(Throwable t) { t.printStackTrace(); fail("Unexpected exception: " + t.getMessage(), t); @@ -149,7 +149,7 @@ public static class PostRedirectGetHandler extends AbstractHandler { final AtomicInteger counter = new AtomicInteger(); - /* @Override */ + @Override public void handle(String pathInContext, org.eclipse.jetty.server.Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { final boolean expectGet = (httpRequest.getHeader("x-expect-get") != null); diff --git a/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java b/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java index 26d85587ac..49e4b2b6ea 100644 --- a/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java +++ b/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java @@ -89,7 +89,7 @@ public void postWithNulParamQS() throws IOException, ExecutionException, Timeout try { Future f = client.preparePost("http://127.0.0.1/" + port1 + "/?a=").setBody("abc".getBytes()).execute(new AsyncCompletionHandlerBase() { - /* @Override */ + @Override public STATE onStatusReceived(final HttpResponseStatus status) throws Exception { if (!status.getUri().toURL().toString().equals("http://127.0.0.1/" + port1 + "/?a=")) { throw new IOException(status.getUri().toURL().toString()); @@ -112,7 +112,7 @@ public void postWithNulParamsQS() throws IOException, ExecutionException, Timeou try { Future f = client.preparePost("http://127.0.0.1/" + port1 + "/?a=b&c&d=e").setBody("abc".getBytes()).execute(new AsyncCompletionHandlerBase() { - /* @Override */ + @Override public STATE onStatusReceived(final HttpResponseStatus status) throws Exception { if (!status.getUri().toURL().toString().equals("http://127.0.0.1/" + port1 + "/?a=b&c&d=e")) { throw new IOException("failed to parse the query properly"); @@ -135,7 +135,7 @@ public void postWithEmptyParamsQS() throws IOException, ExecutionException, Time try { Future f = client.preparePost("http://127.0.0.1/" + port1 + "/?a=b&c=&d=e").setBody("abc".getBytes()).execute(new AsyncCompletionHandlerBase() { - /* @Override */ + @Override public STATE onStatusReceived(final HttpResponseStatus status) throws Exception { if (!status.getUri().toURL().toString().equals("http://127.0.0.1/" + port1 + "/?a=b&c=&d=e")) { throw new IOException("failed to parse the query properly"); diff --git a/api/src/test/java/org/asynchttpclient/async/WebDavBasicTest.java b/api/src/test/java/org/asynchttpclient/async/WebDavBasicTest.java index 8959d7a8bc..ef16be723a 100644 --- a/api/src/test/java/org/asynchttpclient/async/WebDavBasicTest.java +++ b/api/src/test/java/org/asynchttpclient/async/WebDavBasicTest.java @@ -174,7 +174,7 @@ public void propFindCompletionHandlerWebDavTest() throws InterruptedException, I /** * {@inheritDoc} */ - /* @Override */ + @Override public void onThrowable(Throwable t) { t.printStackTrace(); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponse.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponse.java index 61b5f13671..63abfcc5c9 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponse.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponse.java @@ -43,7 +43,7 @@ public NettyResponse(HttpResponseStatus status, super(status, headers, bodyParts); } - /* @Override */ + @Override public String getResponseBodyExcerpt(int maxLength) throws IOException { return getResponseBodyExcerpt(maxLength, null); } @@ -69,12 +69,12 @@ protected List buildCookies() { return Collections.unmodifiableList(cookies); } - /* @Override */ + @Override public byte[] getResponseBodyAsBytes() throws IOException { return getResponseBodyAsByteBuffer().array(); } - /* @Override */ + @Override public ByteBuffer getResponseBodyAsByteBuffer() throws IOException { int length = 0; @@ -88,17 +88,17 @@ public ByteBuffer getResponseBodyAsByteBuffer() throws IOException { return target; } - /* @Override */ + @Override public String getResponseBody() throws IOException { return getResponseBody(null); } - /* @Override */ + @Override public String getResponseBody(String charset) throws IOException { return new String(getResponseBodyAsBytes(), calculateCharset(charset)); } - /* @Override */ + @Override public InputStream getResponseBodyAsStream() throws IOException { return new ByteArrayInputStream(getResponseBodyAsBytes()); } From 6a2567e2b3dfc48425914bd3bb5b60de1bf59f93 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 3 Oct 2013 11:10:58 +0200 Subject: [PATCH 0020/2254] Drop Netty Provider OIO support, close #398 --- .../netty/NettyAsyncHttpProviderConfig.java | 13 ------- .../providers/netty/channel/Channels.java | 39 ++++++------------- 2 files changed, 12 insertions(+), 40 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java index da6af380e2..592c9ca582 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java @@ -40,11 +40,6 @@ public class NettyAsyncHttpProviderConfig implements AsyncHttpProviderConfig> propertiesSet() { return properties.entrySet(); } - public boolean isUseBlockingIO() { - return useBlockingIO; - } - - public void setUseBlockingIO(boolean useBlockingIO) { - this.useBlockingIO = useBlockingIO; - } - public EventLoopGroup getEventLoopGroup() { return eventLoopGroup; } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/Channels.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/Channels.java index 84e73efb71..bc1f091d02 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/Channels.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/Channels.java @@ -25,10 +25,7 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.group.ChannelGroup; import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.oio.OioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.channel.socket.oio.OioSocketChannel; import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpContentCompressor; import io.netty.handler.codec.http.HttpContentDecompressor; @@ -112,34 +109,22 @@ public Channels(final AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig this.config = config; this.asyncHttpProviderConfig = asyncHttpProviderConfig; - Class socketChannelClass = null; - if (asyncHttpProviderConfig.isUseBlockingIO()) { - socketChannelClass = OioSocketChannel.class; - eventLoopGroup = new OioEventLoopGroup(); - allowReleaseEventLoopGroup = true; + // check if external EventLoopGroup is defined + eventLoopGroup = asyncHttpProviderConfig.getEventLoopGroup(); + if (eventLoopGroup == null) { + eventLoopGroup = new NioEventLoopGroup(); + allowReleaseEventLoopGroup = true; } else { - // check if external EventLoopGroup is defined - eventLoopGroup = asyncHttpProviderConfig.getEventLoopGroup(); - if (eventLoopGroup instanceof OioEventLoopGroup) { - socketChannelClass = OioSocketChannel.class; - allowReleaseEventLoopGroup = false; - - } else if (eventLoopGroup instanceof NioEventLoopGroup) { - socketChannelClass = NioSocketChannel.class; - allowReleaseEventLoopGroup = false; - - } else { - socketChannelClass = NioSocketChannel.class; - eventLoopGroup = new NioEventLoopGroup(); - allowReleaseEventLoopGroup = true; - } + if (!(eventLoopGroup instanceof NioEventLoopGroup)) + throw new IllegalArgumentException("Only Nio is supported"); + allowReleaseEventLoopGroup = false; } - plainBootstrap = new Bootstrap().channel(socketChannelClass).group(eventLoopGroup); - secureBootstrap = new Bootstrap().channel(socketChannelClass).group(eventLoopGroup); - webSocketBootstrap = new Bootstrap().channel(socketChannelClass).group(eventLoopGroup); - secureWebSocketBootstrap = new Bootstrap().channel(socketChannelClass).group(eventLoopGroup); + plainBootstrap = new Bootstrap().channel(NioSocketChannel.class).group(eventLoopGroup); + secureBootstrap = new Bootstrap().channel(NioSocketChannel.class).group(eventLoopGroup); + webSocketBootstrap = new Bootstrap().channel(NioSocketChannel.class).group(eventLoopGroup); + secureWebSocketBootstrap = new Bootstrap().channel(NioSocketChannel.class).group(eventLoopGroup); // This is dangerous as we can't catch a wrong typed ConnectionsPool ConnectionsPool cp = (ConnectionsPool) config.getConnectionsPool(); From 5eb0fd7828aa2c97b0ca69f5303879a94692eb40 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 3 Oct 2013 11:12:23 +0200 Subject: [PATCH 0021/2254] Fix previous commit --- .../netty/RetryNonBlockingIssue.java | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/RetryNonBlockingIssue.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/RetryNonBlockingIssue.java index 09f89010cd..dd946b7bf1 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/RetryNonBlockingIssue.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/RetryNonBlockingIssue.java @@ -156,48 +156,6 @@ public void testRetryNonBlockingAsyncConnect() throws IOException, InterruptedEx } } - @Test - public void testRetryBlocking() throws IOException, InterruptedException, ExecutionException { - - NettyAsyncHttpProviderConfig nettyConfig = new NettyAsyncHttpProviderConfig(); - nettyConfig.setUseBlockingIO(true); - - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// - .setAllowPoolingConnection(true)// - .setMaximumConnectionsTotal(100)// - .setConnectionTimeoutInMs(60000)// - .setRequestTimeoutInMs(30000)// - .setAsyncHttpClientProviderConfig(nettyConfig)// - .build(); - - AsyncHttpClient client = getAsyncHttpClient(config); - - try { - List> res = new ArrayList>(); - for (int i = 0; i < 32; i++) { - res.add(testMethodRequest(client, 3, "servlet", UUID.randomUUID().toString())); - } - - StringBuilder b = new StringBuilder(); - for (ListenableFuture r : res) { - Response theres = r.get(); - assertEquals(theres.getStatusCode(), 200); - b.append("==============\r\n"); - b.append("Response Headers\r\n"); - Map> heads = theres.getHeaders(); - b.append(heads + "\r\n"); - b.append("==============\r\n"); - assertTrue(heads.size() > 0); - - } - System.out.println(b.toString()); - System.out.flush(); - - } finally { - client.close(); - } - } - @SuppressWarnings("serial") public class MockExceptionServlet extends HttpServlet { From 51597a7ef1eda2f2b569d65616863324bf50de62 Mon Sep 17 00:00:00 2001 From: Ryan Lubke Date: Thu, 3 Oct 2013 09:52:56 -0700 Subject: [PATCH 0022/2254] Honor the thread multiplier when a custom ExecutorService hasn't been provided. --- .../providers/grizzly/GrizzlyAsyncHttpProvider.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java index 9ef48ad96b..2639aadad3 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java @@ -440,6 +440,12 @@ private void doDefaultTransportConfig() { clientTransport.setIOStrategy(WorkerThreadIOStrategy.getInstance()); if (service != null) { clientTransport.setWorkerThreadPool(service); + } else { + final int multiplier = clientConfig.getIoThreadMultiplier(); + final int threadCount = multiplier * Runtime.getRuntime().availableProcessors(); + clientTransport.getWorkerThreadPoolConfig() + .setCorePoolSize(threadCount) + .setMaxPoolSize(threadCount); } } From 71f72c168ad3180f7f9a69250d65548b6e9b836a Mon Sep 17 00:00:00 2001 From: Ryan Lubke Date: Thu, 3 Oct 2013 10:58:42 -0700 Subject: [PATCH 0023/2254] Cleanup imports. --- .../providers/grizzly/GrizzlyAsyncHttpProvider.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java index 2639aadad3..efad63b22a 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpProvider.java @@ -16,13 +16,9 @@ import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; import org.asynchttpclient.ListenableFuture; import org.asynchttpclient.ProxyServer; import org.asynchttpclient.Request; -import org.asynchttpclient.Response; import org.asynchttpclient.ntlm.NTLMEngine; import org.asynchttpclient.providers.grizzly.filters.AsyncHttpClientEventFilter; import org.asynchttpclient.providers.grizzly.filters.AsyncHttpClientFilter; @@ -69,7 +65,6 @@ import javax.net.ssl.SSLEngine; import java.io.IOException; import java.util.LinkedHashSet; -import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; From 9bdc51fb37470bf1320ed95f364a9b6def2a26fd Mon Sep 17 00:00:00 2001 From: Ryan Lubke Date: Thu, 3 Oct 2013 11:50:03 -0700 Subject: [PATCH 0024/2254] Use HTTP Method references vs raw string. --- .../org/asynchttpclient/providers/grizzly/EventHandler.java | 3 ++- .../grizzly/statushandler/ProxyAuthorizationHandler.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java index 732697e3b5..a3f129cb5d 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java @@ -36,6 +36,7 @@ import org.glassfish.grizzly.http.HttpContent; import org.glassfish.grizzly.http.HttpHeader; import org.glassfish.grizzly.http.HttpResponsePacket; +import org.glassfish.grizzly.http.Method; import org.glassfish.grizzly.http.ProcessingState; import org.glassfish.grizzly.http.Protocol; import org.glassfish.grizzly.http.util.Header; @@ -471,7 +472,7 @@ public static Request newRequest(final URI uri, final RequestBuilder builder = new RequestBuilder(ctx.getRequest()); if (asGet) { - builder.setMethod("GET"); + builder.setMethod(Method.GET.getMethodString()); } builder.setUrl(uri.toString()); diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java index 4720763ca3..48d591566a 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java @@ -25,6 +25,7 @@ import org.glassfish.grizzly.filterchain.FilterChainContext; import org.glassfish.grizzly.http.HttpContext; import org.glassfish.grizzly.http.HttpResponsePacket; +import org.glassfish.grizzly.http.Method; import org.glassfish.grizzly.http.util.Header; import org.glassfish.grizzly.http.util.HttpStatus; import org.ietf.jgss.GSSContext; @@ -74,7 +75,7 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, Realm realm = new Realm.RealmBuilder().setPrincipal(principal) .setPassword(password) .setUri("/") - .setMethodName("CONNECT") + .setMethodName(Method.CONNECT.getMethodString()) .setUsePreemptiveAuth(true) .parseProxyAuthenticateHeader(proxyAuth) .build(); From d67e626d2cad29192d1476c73bd183773cf2e569 Mon Sep 17 00:00:00 2001 From: Ryan Lubke Date: Thu, 3 Oct 2013 11:53:37 -0700 Subject: [PATCH 0025/2254] Remove unused import. --- .../grizzly/statushandler/ProxyAuthorizationHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java index 48d591566a..59708dcc55 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java @@ -23,7 +23,6 @@ import org.asynchttpclient.util.Base64; import org.glassfish.grizzly.Connection; import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpContext; import org.glassfish.grizzly.http.HttpResponsePacket; import org.glassfish.grizzly.http.Method; import org.glassfish.grizzly.http.util.Header; From 7a79d25ca914f3e40341c57505608ba69a657047 Mon Sep 17 00:00:00 2001 From: Ryan Lubke Date: Thu, 3 Oct 2013 12:04:59 -0700 Subject: [PATCH 0026/2254] More cleanup: - unused imports - removed legacy classes --- .../providers/grizzly/EventHandler.java | 1 - .../grizzly/GrizzlyResponseBodyPart.java | 3 -- .../grizzly/GrizzlyResponseStatus.java | 2 +- .../filters/AsyncHttpClientEventFilter.java | 3 -- .../filters/AsyncHttpClientFilter.java | 1 - .../grizzly/filters/SwitchingSSLFilter.java | 49 +------------------ 6 files changed, 2 insertions(+), 57 deletions(-) diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java index a3f129cb5d..8389637833 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/EventHandler.java @@ -108,7 +108,6 @@ public void onHttpContentParsed(HttpContent content, try { context.setCurrentState(handler.onBodyPartReceived( new GrizzlyResponseBodyPart(content, - context.getRequest().getURI(), ctx.getConnection()))); } catch (Exception e) { handler.onThrowable(e); diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseBodyPart.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseBodyPart.java index b010cd3c3d..f5443ab868 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseBodyPart.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseBodyPart.java @@ -13,7 +13,6 @@ package org.asynchttpclient.providers.grizzly; -import org.asynchttpclient.AsyncHttpProvider; import org.asynchttpclient.HttpResponseBodyPart; import org.glassfish.grizzly.Buffer; @@ -24,7 +23,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.URI; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicReference; @@ -47,7 +45,6 @@ class GrizzlyResponseBodyPart extends HttpResponseBodyPart { public GrizzlyResponseBodyPart(final HttpContent content, - final URI uri, final Connection connection) { this.content = content; this.connection = connection; diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseStatus.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseStatus.java index 9bafb37be4..e24e9937f0 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseStatus.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponseStatus.java @@ -66,7 +66,7 @@ public Response prepareResponse(HttpResponseHeaders headers, List listeners = - new ConcurrentHashMap(); - - - // --------------------------------------- Method from HandshakeListener - - - @Override - public void onStart(Connection connection) { - // no-op - } - - @Override - public void onComplete(Connection connection) { - final HandshakeCompleteListener listener = listeners.get(connection); - if (listener != null) { - removeListener(connection); - listener.complete(); - } - } - - - // --------------------------------------------- Package Private Methods - - - public static void addListener(final Connection c, - final HandshakeCompleteListener listener) { - listeners.putIfAbsent(c, listener); - } - - static void removeListener(final Connection c) { - listeners.remove(c); - } - } - } From 71351fdbc528e3748a1e6d94c0e381af4f189305 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 3 Oct 2013 21:39:20 +0200 Subject: [PATCH 0027/2254] Upgrade Netty 4.0.10 --- providers/netty/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/netty/pom.xml b/providers/netty/pom.xml index 8905736367..773dac96f4 100644 --- a/providers/netty/pom.xml +++ b/providers/netty/pom.xml @@ -39,7 +39,7 @@ io.netty netty-all - 4.0.10.Final-SNAPSHOT + 4.0.10.Final org.javassist From 9ee456fb7ff308b60e9565c4dba2531792ad6984 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 4 Oct 2013 01:06:18 +0200 Subject: [PATCH 0028/2254] Fix Response.isRedirected, close #391 --- .../org/asynchttpclient/providers/ResponseBase.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java b/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java index 2a96b2880b..3f010557b7 100644 --- a/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java +++ b/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java @@ -80,7 +80,16 @@ public final FluentCaseInsensitiveStringsMap getHeaders() { @Override public final boolean isRedirected() { - return (status.getStatusCode() >= 300) && (status.getStatusCode() <= 399); + switch (status.getStatusCode()) { + case 301: + case 302: + case 303: + case 307: + case 308: + return true; + default: + return false; + } } @Override From 6e32eefd22688a6d1097b7f8194a51490be4a79a Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 8 Oct 2013 23:13:23 +0200 Subject: [PATCH 0029/2254] Latest release is 1.7.20 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f258f4b1ac..82921e4652 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Async Http Client library purpose is to allow Java applications to easily execut com.ning async-http-client - 1.7.19 + 1.7.20 ``` From 0da1f10df65d2a5b94e68cd705d94c1d9daf43d2 Mon Sep 17 00:00:00 2001 From: Ryan Lubke Date: Wed, 9 Oct 2013 13:29:00 -0700 Subject: [PATCH 0030/2254] Ensure onStatusReceived() is invoked when there is no realm. --- .../grizzly/statushandler/AuthorizationHandler.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/AuthorizationHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/AuthorizationHandler.java index d9bf914507..369a9b7b7f 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/AuthorizationHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/AuthorizationHandler.java @@ -58,6 +58,14 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, } if (realm == null) { httpTransactionContext.setInvocationStatus(STOP); + if (httpTransactionContext.getHandler() != null) { + try { + httpTransactionContext.getHandler().onStatusReceived( + httpTransactionContext.getResponseStatus()); + } catch (Exception e) { + httpTransactionContext.abort(e); + } + } return true; } From 221df62e719aa25e9bc800dd93f644b059b8fd05 Mon Sep 17 00:00:00 2001 From: Ryan Lubke Date: Thu, 10 Oct 2013 15:00:29 -0700 Subject: [PATCH 0031/2254] Workaround issue discovered by Stephane after upgrading to Jetty 9. --- .../statushandler/AuthorizationHandler.java | 5 +++-- .../statushandler/ProxyAuthorizationHandler.java | 14 +++++++------- .../providers/grizzly/GrizzlyBasicAuthTest.java | 11 ----------- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/AuthorizationHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/AuthorizationHandler.java index 369a9b7b7f..11b2a0c2df 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/AuthorizationHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/AuthorizationHandler.java @@ -135,12 +135,13 @@ private Connection getConnectionForNextRequest(final FilterChainContext ctx, final HttpResponsePacket response, final HttpTxContext httpCtx) throws Exception { + /* if (response.getProcessingState().isKeepAlive()) { return ctx.getConnection(); - } else { + } else { */ final ConnectionManager m = httpCtx.getProvider().getConnectionManager(); return m.obtainConnection(request, httpCtx.getFuture()); - } + /* } */ } } // END AuthorizationHandler diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java index 59708dcc55..b8530f6014 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/statushandler/ProxyAuthorizationHandler.java @@ -224,13 +224,13 @@ private Connection getConnectionForNextRequest(final FilterChainContext ctx, final HttpResponsePacket response, final HttpTxContext httpCtx) throws Exception { - if (response.getProcessingState().isKeepAlive()) { - return ctx.getConnection(); - } else { - final ConnectionManager m = - httpCtx.getProvider().getConnectionManager(); - return m.obtainConnection(request, httpCtx.getFuture()); - } + /* + if (response.getProcessingState().isKeepAlive()) { + return ctx.getConnection(); + } else { */ + final ConnectionManager m = httpCtx.getProvider().getConnectionManager(); + return m.obtainConnection(request, httpCtx.getFuture()); + /* } */ } diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyBasicAuthTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyBasicAuthTest.java index c7d077308f..b558e8cf61 100644 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyBasicAuthTest.java +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyBasicAuthTest.java @@ -29,16 +29,5 @@ public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { public String getProviderClass() { return GrizzlyAsyncHttpProvider.class.getName(); } - - @Test(groups = { "standalone", "default_provider" }, enabled = false) - @Override - public void basicAuthFileTest() throws Exception { - // FIXME - } - @Test(groups = { "standalone", "default_provider" }, enabled = false) - @Override - public void basicAuthFileNoKeepAliveTest() throws Exception { - // FIXME - } } From d6e5c1589bf1d0ecfd5301a056004c1f9357add7 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 14 Oct 2013 23:31:55 +0200 Subject: [PATCH 0032/2254] Support multi valued headers, close #403 --- .../netty/request/NettyRequests.java | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequests.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequests.java index ad2436db13..d5821471eb 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequests.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequests.java @@ -37,7 +37,6 @@ import java.util.Map.Entry; import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; import org.asynchttpclient.ProxyServer; import org.asynchttpclient.Realm; import org.asynchttpclient.Request; @@ -61,7 +60,7 @@ public static HttpRequest newNettyRequest(AsyncHttpClientConfig config, Request method = HttpMethod.CONNECT; else method = HttpMethod.valueOf(request.getMethod()); - + String host = null; HttpVersion httpVersion; String requestUri; @@ -107,18 +106,6 @@ else if (uri.getRawQuery() != null) } if (method != HttpMethod.CONNECT) { - FluentCaseInsensitiveStringsMap h = request.getHeaders(); - if (h != null) { - for (Entry> header : h) { - String name = header.getKey(); - if (!HttpHeaders.Names.HOST.equalsIgnoreCase(name)) { - for (String value : header.getValue()) { - headers.put(name, value); - } - } - } - } - if (config.isCompressionEnabled()) { headers.put(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP); } @@ -186,6 +173,7 @@ else if (uri.getRawQuery() != null) } if (proxyServer != null) { + // FIXME Wikipedia says that Proxy-Connection was a misunderstanding of Connection http://en.wikipedia.org/wiki/List_of_HTTP_header_fields if (!request.getHeaders().containsKey("Proxy-Connection")) { headers.put("Proxy-Connection", AsyncHttpProviderUtils.keepAliveHeaderValue(config)); } @@ -297,6 +285,15 @@ else if (uri.getRawQuery() != null) } else { nettyRequest = new DefaultFullHttpRequest(httpVersion, method, requestUri); } + + // assign headers as configured on request + if (method != HttpMethod.CONNECT) { + for (Entry> header : request.getHeaders()) { + nettyRequest.headers().set(header.getKey(), header.getValue()); + } + } + + // override with computed ones for (Entry header : headers.entrySet()) { nettyRequest.headers().set(header.getKey(), header.getValue()); } From 48b75f5f7a703df4560835c82f49209903b4a131 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 14 Oct 2013 23:43:14 +0200 Subject: [PATCH 0033/2254] Add RequestBuilder.setURI, like setUrl, close #405 --- .../asynchttpclient/RequestBuilderBase.java | 34 ++++--------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java index 0a0001f8bc..9b8f14baac 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -369,7 +369,12 @@ protected RequestBuilderBase(Class derived, Request prototype) { } public T setUrl(String url) { - request.originalUri = buildURI(url); + return setURI(URI.create(url)); + } + + public T setURI(URI uri) { + request.originalUri = uri; + addQueryParameters(request.originalUri); request.uri = null; request.rawUri = null; return derived.cast(this); @@ -385,31 +390,7 @@ public T setLocalInetAddress(InetAddress address) { return derived.cast(this); } - private URI buildURI(String url) { - URI uri = URI.create(url); - - if (uri.getRawPath() == null) { - // AHC-96 - // Let's try to derive it - StringBuilder buildedUrl = new StringBuilder(); - - if (uri.getScheme() != null) { - buildedUrl.append(uri.getScheme()); - buildedUrl.append("://"); - } - - if (uri.getAuthority() != null) { - buildedUrl.append(uri.getAuthority()); - } - if (url.indexOf("://") == -1) { - String s = buildedUrl.toString(); - url = s + url.substring(uri.getScheme().length() + 1); - return buildURI(url); - } else { - throw new IllegalArgumentException("Invalid url " + uri.toString()); - } - } - + private void addQueryParameters(URI uri) { if (isNonEmpty(uri.getRawQuery())) { String[] queries = uri.getRawQuery().split("&"); int pos; @@ -430,7 +411,6 @@ private URI buildURI(String url) { } } } - return uri; } public T setVirtualHost(String virtualHost) { From d55c4e01dbb1fcb71161df4d28a9bf96ab15a558 Mon Sep 17 00:00:00 2001 From: Ryan Lubke Date: Tue, 15 Oct 2013 10:47:48 -0700 Subject: [PATCH 0034/2254] Port changes from #402 to master. --- .../AsyncHttpClientConfig.java | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java index 3ce08af1ad..7cd7d7b027 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -503,12 +503,20 @@ public boolean isRemoveQueryParamOnRedirect() { } /** - * Return true if one of the {@link java.util.concurrent.ExecutorService} has been shutdown. - * - * @return true if one of the {@link java.util.concurrent.ExecutorService} has been shutdown. + * @return true if both the application and reaper thread pools + * haven't yet been shutdown. + * @since 1.7.21 */ - public boolean isClosed() { - return applicationThreadPool.isShutdown() || reaper.isShutdown(); + public boolean isValid() { + boolean atpRunning = true; + try { + atpRunning = applicationThreadPool.isShutdown(); + } catch (Exception ignore) { + // isShutdown() will thrown an exception in an EE7 environment + // when using a ManagedExecutorService. + // When this is the case, we assume it's running. + } + return (atpRunning && !reaper.isShutdown()); } /** @@ -1227,23 +1235,6 @@ public Thread newThread(Runnable r) { }); } -// if (applicationThreadPool == null) { -// applicationThreadPool = -// Executors.newCachedThreadPool(new ThreadFactory() { -// final AtomicInteger counter = new AtomicInteger(); -// public Thread newThread(Runnable r) { -// Thread t = new Thread(r, -// "AsyncHttpClient-Callback-" + counter.incrementAndGet()); -// t.setDaemon(true); -// return t; -// } -// }); -// } -// -// if (applicationThreadPool.isShutdown()) { -// throw new IllegalStateException("ExecutorServices closed"); -// } - if (proxyServerSelector == null && useProxySelector) { proxyServerSelector = ProxyUtils.getJdkDefaultProxyServerSelector(); } From 20deaa8344c47713a7b35c1101873db6a4bc008f Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 16 Oct 2013 08:28:29 +0200 Subject: [PATCH 0035/2254] Fix invalidUri test, crappy url format should be supported --- .../main/java/org/asynchttpclient/RequestBuilderBase.java | 2 ++ .../org/asynchttpclient/async/AsyncProvidersBasicTest.java | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java index 9b8f14baac..d737158ade 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -373,6 +373,8 @@ public T setUrl(String url) { } public T setURI(URI uri) { + if (uri.getPath() == null) + throw new IllegalArgumentException("Unsupported uri format: " + uri); request.originalUri = uri; addQueryParameters(request.originalUri); request.uri = null; diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java index 0472ef6ba6..e865796391 100755 --- a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java @@ -1556,12 +1556,11 @@ public void headShouldNotAllowBody() throws IllegalArgumentException, IOExceptio } } - @Test(groups = { "standalone", "default_provider" }) + @Test(groups = { "standalone", "default_provider" }, expectedExceptions = { IllegalArgumentException.class }) public void invalidUri() throws Exception { AsyncHttpClient client = getAsyncHttpClient(null); try { - Response response = client.executeRequest(client.prepareGet(String.format("http:127.0.0.1:%d/foo/test", port1)).build()).get(); - assertEquals(200, response.getStatusCode()); + client.prepareGet(String.format("http:127.0.0.1:%d/foo/test", port1)).build(); } finally { client.close(); } From 8add5878c241bddbf29bae6a5127abfb8f1cfc05 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 16 Oct 2013 12:55:05 +0200 Subject: [PATCH 0036/2254] Make sure a cancel exception won't hide the expected one --- .../netty/future/NettyResponseFuture.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java index 2a7868a9ff..7f0cb64a29 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/NettyResponseFuture.java @@ -234,12 +234,15 @@ public V get(long l, TimeUnit tu) throws InterruptedException, TimeoutException, TimeoutException te = new TimeoutException(String.format("No response received after %s %s", l, tu.name().toLowerCase())); if (!throwableCalled.getAndSet(true)) { try { - asyncHandler.onThrowable(te); - } catch (Throwable t) { - logger.debug("asyncHandler.onThrowable", t); + try { + asyncHandler.onThrowable(te); + } catch (Throwable t) { + logger.debug("asyncHandler.onThrowable", t); + } + throw new ExecutionException(te); + } finally { + cancelReaper(); } - cancelReaper(); - throw new ExecutionException(te); } } isDone.set(true); @@ -267,12 +270,15 @@ private V getContent() throws ExecutionException { } catch (Throwable ex) { if (!throwableCalled.getAndSet(true)) { try { - asyncHandler.onThrowable(ex); - } catch (Throwable t) { - logger.debug("asyncHandler.onThrowable", t); + try { + asyncHandler.onThrowable(ex); + } catch (Throwable t) { + logger.debug("asyncHandler.onThrowable", t); + } + throw new RuntimeException(ex); + } finally { + cancelReaper(); } - cancelReaper(); - throw new RuntimeException(ex); } } content.compareAndSet(null, update); From ece093ae64e74a40c434e78991b367bb477d9458 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 16 Oct 2013 22:34:27 +0200 Subject: [PATCH 0037/2254] Minor removeQuotes optim --- .../util/AsyncHttpProviderUtils.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java index 500ed4a290..e44b3a6c3c 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java @@ -46,6 +46,8 @@ import org.asynchttpclient.multipart.MultipartRequestEntity; import org.asynchttpclient.multipart.PartSource; +import com.ning.http.util.MiscUtil; + /** * {@link org.asynchttpclient.AsyncHttpProvider} common utilities. *

    @@ -530,13 +532,24 @@ public static int convertExpireField(String timestring) { throw new IllegalArgumentException("Not a valid expire field " + trimmedTimeString); } - private final static String removeQuote(String s) { + public final static String removeQuotes(String s) { if (MiscUtil.isNonEmpty(s)) { - if (s.charAt(0) == '"') - s = s.substring(1); + int start = 0; + int end = s.length(); + boolean changed = false; + + if (s.charAt(0) == '"') { + changed = true; + start++; + } + + if (s.charAt(s.length() - 1) == '"') { + changed = true; + end--; + } - if (s.charAt(s.length() - 1) == '"') - s = s.substring(0, s.length() - 1); + if (changed) + s = s.substring(start, end); } return s; } From 89410aa6c5cf375228fd85ab919d0d1497c99416 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 21 Oct 2013 10:38:19 +0200 Subject: [PATCH 0038/2254] Remove bad import --- .../java/org/asynchttpclient/util/AsyncHttpProviderUtils.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java index e44b3a6c3c..4558844044 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java @@ -46,8 +46,6 @@ import org.asynchttpclient.multipart.MultipartRequestEntity; import org.asynchttpclient.multipart.PartSource; -import com.ning.http.util.MiscUtil; - /** * {@link org.asynchttpclient.AsyncHttpProvider} common utilities. *

    From 466bd55e92d40e7193aa1ffd28c823d66aeb0ab6 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 21 Oct 2013 10:44:32 +0200 Subject: [PATCH 0039/2254] Fix build --- .../java/org/asynchttpclient/util/AsyncHttpProviderUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java index 4558844044..a0599eecf6 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java @@ -516,7 +516,7 @@ public static String parseCharset(String contentType) { } public static int convertExpireField(String timestring) { - String trimmedTimeString = removeQuote(timestring.trim()); + String trimmedTimeString = removeQuotes(timestring.trim()); for (SimpleDateFormat sdf : simpleDateFormat.get()) { Date date = sdf.parse(trimmedTimeString, new ParsePosition(0)); From 19cc544158cdb3f4c1fb98137ae40024f29939b6 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 21 Oct 2013 10:44:42 +0200 Subject: [PATCH 0040/2254] Upgrade Netty 4.0.11 --- providers/netty/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/netty/pom.xml b/providers/netty/pom.xml index 773dac96f4..471355e0a0 100644 --- a/providers/netty/pom.xml +++ b/providers/netty/pom.xml @@ -39,7 +39,7 @@ io.netty netty-all - 4.0.10.Final + 4.0.11.Final org.javassist From b0249470340022918fcc2b412e2347c4ba4ac7c5 Mon Sep 17 00:00:00 2001 From: Ryan Lubke Date: Thu, 24 Oct 2013 18:50:26 -0700 Subject: [PATCH 0041/2254] Fix for #407. --- .../providers/grizzly/bodyhandler/BodyGeneratorBodyHandler.java | 2 +- .../providers/grizzly/filters/AsyncHttpClientFilter.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyGeneratorBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyGeneratorBodyHandler.java index b8f3437c56..287f309da1 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyGeneratorBodyHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyGeneratorBodyHandler.java @@ -44,7 +44,7 @@ public boolean doHandle(final FilterChainContext ctx, final BodyGenerator generator = request.getBodyGenerator(); final Body bodyLocal = generator.createBody(); final long len = bodyLocal.getContentLength(); - if (len > 0) { + if (len >= 0) { requestPacket.setContentLengthLong(len); } else { requestPacket.setChunked(true); diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java index 4589f12261..942dc4208e 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncHttpClientFilter.java @@ -262,7 +262,7 @@ private boolean sendAsGrizzlyRequest(final RequestInfoHolder requestInfoHolder, if (Utils.requestHasEntityBody(request)) { final long contentLength = request.getContentLength(); - if (contentLength > 0) { + if (contentLength >= 0) { requestPacket.setContentLengthLong(contentLength); requestPacket.setChunked(false); } else { From d63637a844e4aa6d9141f6412885abbbdc5b6ed8 Mon Sep 17 00:00:00 2001 From: Ryan Lubke Date: Fri, 25 Oct 2013 09:47:30 -0700 Subject: [PATCH 0042/2254] Leverage utility class for determining thread type. --- .../providers/grizzly/FeedableBodyGenerator.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/FeedableBodyGenerator.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/FeedableBodyGenerator.java index e359626b0f..58dbc72099 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/FeedableBodyGenerator.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/FeedableBodyGenerator.java @@ -31,6 +31,7 @@ import org.glassfish.grizzly.impl.FutureImpl; import org.glassfish.grizzly.nio.NIOConnection; import org.glassfish.grizzly.nio.SelectorRunner; +import org.glassfish.grizzly.threadpool.Threads; import org.glassfish.grizzly.utils.Futures; import static java.lang.Boolean.TRUE; @@ -185,7 +186,7 @@ public void run() { // If the current thread is a selector thread, we need to execute // the remainder of the task on the worker thread to prevent // it from being blocked. - if (isCurrentThreadSelectorRunner()) { + if (isServiceThread()) { c.getTransport().getWorkerThreadPool().execute(r); } else { r.run(); @@ -196,10 +197,8 @@ public void run() { // --------------------------------------------------------- Private Methods - private boolean isCurrentThreadSelectorRunner() { - final NIOConnection c = (NIOConnection) context.getConnection(); - final SelectorRunner runner = c.getSelectorRunner(); - return (Thread.currentThread() == runner.getRunnerThread()); + private boolean isServiceThread() { + return Threads.isService(); } From edd600d162ff24b222d2a41c4397a3c712cad2cc Mon Sep 17 00:00:00 2001 From: Ryan Lubke Date: Wed, 30 Oct 2013 10:38:07 -0700 Subject: [PATCH 0043/2254] Integrate 2.3.7. --- providers/grizzly/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/grizzly/pom.xml b/providers/grizzly/pom.xml index ab9d594d5b..097a903510 100644 --- a/providers/grizzly/pom.xml +++ b/providers/grizzly/pom.xml @@ -14,7 +14,7 @@ - 2.3.7-SNAPSHOT + 2.3.7 1.0 From e4c5c7793595ca58810100f90fe0596b227ff774 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 6 Nov 2013 21:36:58 +0100 Subject: [PATCH 0044/2254] Port 6f60a45d9ebe4ad0ebd6881746e1671f3a3a8317 on master --- .../multipart/MultipartBody.java | 16 ++- .../multipart/MultipartBodyTest.java | 109 ++++++++++++++++++ 2 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java diff --git a/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java b/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java index 70d15e21e2..af9df966e2 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java +++ b/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java @@ -78,7 +78,7 @@ public long read(ByteBuffer buffer) throws IOException { try { int overallLength = 0; - int maxLength = buffer.capacity(); + int maxLength = buffer.remaining(); if (startPart == parts.size() && endWritten) { return overallLength; @@ -132,6 +132,7 @@ public long read(ByteBuffer buffer) throws IOException { initializeFileEnd(currentFilePart); } else if (fileLocation == FileLocation.END) { startPart++; + fileLocation = FileLocation.NONE; if (startPart == parts.size() && currentStream.available() == 0) { doneWritingParts = true; } @@ -146,6 +147,7 @@ public long read(ByteBuffer buffer) throws IOException { initializeFileEnd(currentFilePart); } else if (fileLocation == FileLocation.END) { startPart++; + fileLocation = FileLocation.NONE; if (startPart == parts.size() && currentStream.available() == 0) { doneWritingParts = true; } @@ -165,6 +167,7 @@ public long read(ByteBuffer buffer) throws IOException { initializeFileEnd(currentFilePart); } else if (fileLocation == FileLocation.END) { startPart++; + fileLocation = FileLocation.NONE; if (startPart == parts.size() && currentStream.available() == 0) { doneWritingParts = true; } @@ -202,7 +205,8 @@ public long read(ByteBuffer buffer) throws IOException { private void initializeByteArrayBody(FilePart filePart) throws IOException { - ByteArrayOutputStream output = generateByteArrayBody(filePart); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + filePart.sendData(output); initializeBuffer(output); @@ -388,15 +392,9 @@ private StringPart generateClientStringpart(org.asynchttpclient.Part part) { private long handleByteArrayPart(WritableByteChannel target, FilePart filePart, byte[] data) throws IOException { - ByteArrayOutputStream output = generateByteArrayBody(filePart); - return writeToTarget(target, output); - } - - private ByteArrayOutputStream generateByteArrayBody(FilePart filePart) - throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(); Part.sendPart(output, filePart, boundary); - return output; + return writeToTarget(target, output); } private long handleFileEnd(WritableByteChannel target, FilePart filePart) diff --git a/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java b/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java new file mode 100644 index 0000000000..9cbccb9ac0 --- /dev/null +++ b/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package org.asynchttpclient.multipart; + +import org.asynchttpclient.*; +import org.asynchttpclient.Part; +import org.asynchttpclient.util.AsyncHttpProviderUtils; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +public class MultipartBodyTest { + + @Test(groups = "fast") + public void testBasics() { + final List parts = new ArrayList(); + + // add a file + final File testFile = getTestfile(); + try { + parts.add(new FilePart("filePart", testFile)); + } catch (FileNotFoundException fne) { + Assert.fail("file not found: " + testFile); + } + + // add a byte array + try { + parts.add(new ByteArrayPart("baPart", "fileName", "testMultiPart".getBytes("utf-8"), "application/test", "utf-8")); + } catch (UnsupportedEncodingException ignore) { + } + + // add a string + parts.add(new StringPart("stringPart", "testString", "utf-8")); + + compareContentLength(parts); + } + + private static File getTestfile() { + final ClassLoader cl = MultipartBodyTest.class.getClassLoader(); + final URL url = cl.getResource("textfile.txt"); + Assert.assertNotNull(url); + File file = null; + try { + file = new File(url.toURI()); + } catch (URISyntaxException use) { + Assert.fail("uri syntax error"); + } + return file; + } + + private static void compareContentLength(final List parts) { + Assert.assertNotNull(parts); + // get expected values + MultipartRequestEntity mre = null; + try { + mre = AsyncHttpProviderUtils.createMultipartRequestEntity(parts, new FluentCaseInsensitiveStringsMap()); + } catch (FileNotFoundException fne) { + Assert.fail("file not found: " + parts); + } + final long expectedContentLength = mre.getContentLength(); + + // get real bytes + final Body multipartBody = new MultipartBody(parts, mre.getContentType(), String.valueOf(expectedContentLength)); + try { + final ByteBuffer buffer = ByteBuffer.allocate(8192); + boolean last = false; + long totalBytes = 0; + while (!last) { + long readBytes = 0; + try { + readBytes = multipartBody.read(buffer); + } catch (IOException ie) { + Assert.fail("read failure"); + } + if (readBytes >= 0) { + totalBytes += readBytes; + } else { + last = true; + } + buffer.clear(); + } + Assert.assertEquals(totalBytes, expectedContentLength); + } finally { + try { + multipartBody.close(); + } catch (IOException ignore) { + } + } + } +} From a4f00a057f9f71c496c26a87ca45cb2166f2bbdc Mon Sep 17 00:00:00 2001 From: Bongjae Chang Date: Thu, 7 Nov 2013 11:43:09 +0900 Subject: [PATCH 0045/2254] Fixed infinite loop of MultipartBodyTest for master branch. --- .../java/org/asynchttpclient/multipart/MultipartBodyTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java b/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java index 9cbccb9ac0..1e8ba2860c 100644 --- a/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java +++ b/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java @@ -91,7 +91,7 @@ private static void compareContentLength(final List parts) { } catch (IOException ie) { Assert.fail("read failure"); } - if (readBytes >= 0) { + if (readBytes > 0) { totalBytes += readBytes; } else { last = true; From 0f7615655c85f925cb81c377bd4205e9d5ed8bfb Mon Sep 17 00:00:00 2001 From: Bongjae Chang Date: Thu, 7 Nov 2013 12:24:52 +0900 Subject: [PATCH 0046/2254] MultipartBody#read() returns -1 when it meets EOF. Current MultipartBody#read() returns only 0 when it meets both Exception and EOF(endWritten). Then caller can't know whether the value is EOF or Error. --- .../main/java/org/asynchttpclient/multipart/MultipartBody.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java b/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java index af9df966e2..f72aaf62c1 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java +++ b/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java @@ -81,7 +81,7 @@ public long read(ByteBuffer buffer) throws IOException { int maxLength = buffer.remaining(); if (startPart == parts.size() && endWritten) { - return overallLength; + return -1; } boolean full = false; From 3df777f7e3230711570b30348c1cd09dcd15ad01 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 8 Nov 2013 09:33:18 +0100 Subject: [PATCH 0047/2254] Upgrade Netty 4.0.12 and javassist 3.18.1 --- providers/netty/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/netty/pom.xml b/providers/netty/pom.xml index 471355e0a0..9b884d134d 100644 --- a/providers/netty/pom.xml +++ b/providers/netty/pom.xml @@ -39,12 +39,12 @@ io.netty netty-all - 4.0.11.Final + 4.0.12.Final org.javassist javassist - 3.18.0-GA + 3.18.1-GA From 3286d44d4a3682ad9230c8e9b21fb821eb51488c Mon Sep 17 00:00:00 2001 From: Ryan Lubke Date: Mon, 11 Nov 2013 08:58:24 -0800 Subject: [PATCH 0048/2254] Port changes for #411 from 1.7 to master. --- .../grizzly/bodyhandler/PartsBodyHandler.java | 86 +++++++++++++++---- 1 file changed, 68 insertions(+), 18 deletions(-) diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/PartsBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/PartsBodyHandler.java index ba656d0d68..f4491f391f 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/PartsBodyHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/PartsBodyHandler.java @@ -13,17 +13,22 @@ package org.asynchttpclient.providers.grizzly.bodyhandler; +import org.asynchttpclient.Body; +import org.asynchttpclient.Part; import org.asynchttpclient.Request; +import org.asynchttpclient.multipart.MultipartBody; import org.asynchttpclient.multipart.MultipartRequestEntity; +import org.asynchttpclient.providers.grizzly.FeedableBodyGenerator; +import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; import org.asynchttpclient.util.AsyncHttpProviderUtils; import org.glassfish.grizzly.Buffer; import org.glassfish.grizzly.filterchain.FilterChainContext; -import org.glassfish.grizzly.http.HttpContent; import org.glassfish.grizzly.http.HttpRequestPacket; +import org.glassfish.grizzly.memory.Buffers; import org.glassfish.grizzly.memory.MemoryManager; -import org.glassfish.grizzly.utils.BufferOutputStream; import java.io.IOException; +import java.util.List; import static org.asynchttpclient.util.MiscUtil.isNonEmpty; @@ -42,25 +47,70 @@ public boolean doHandle(final FilterChainContext ctx, final HttpRequestPacket requestPacket) throws IOException { - MultipartRequestEntity mre = + final List parts = request.getParts(); + final MultipartRequestEntity mre = AsyncHttpProviderUtils.createMultipartRequestEntity( - request.getParts(), - request.getHeaders()); - requestPacket.setContentLengthLong(mre.getContentLength()); - requestPacket.setContentType(mre.getContentType()); - final MemoryManager mm = ctx.getMemoryManager(); - Buffer b = mm.allocate(512); - BufferOutputStream o = new BufferOutputStream(mm, b, true); - mre.writeRequest(o); - b = o.getBuffer(); - b.trim(); - if (b.hasRemaining()) { - final HttpContent content = requestPacket.httpContentBuilder().content(b).build(); - content.setLast(true); - ctx.write(content, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null)); + parts, request.getHeaders()); + final long contentLength = mre.getContentLength(); + final String contentType = mre.getContentType(); + requestPacket.setContentLengthLong(contentLength); + requestPacket.setContentType(contentType); + if (GrizzlyAsyncHttpProvider.LOGGER.isDebugEnabled()) { + GrizzlyAsyncHttpProvider.LOGGER.debug( + "REQUEST(modified): contentLength={}, contentType={}", + new Object[]{ + requestPacket.getContentLength(), + requestPacket.getContentType() + }); } - return true; + final FeedableBodyGenerator generator = new FeedableBodyGenerator() { + @Override + public Body createBody() throws IOException { + return new MultipartBody(parts, contentType, + String.valueOf(contentLength)); + } + }; + generator.setFeeder(new FeedableBodyGenerator.BaseFeeder(generator) { + @Override + public void flush() throws IOException { + final Body bodyLocal = feedableBodyGenerator.createBody(); + try { + final MemoryManager mm = ctx.getMemoryManager(); + boolean last = false; + while (!last) { + Buffer buffer = mm.allocate(BodyHandler.MAX_CHUNK_SIZE); + buffer.allowBufferDispose(true); + final long readBytes = + bodyLocal.read(buffer.toByteBuffer()); + if (readBytes > 0) { + buffer.position((int) readBytes); + buffer.trim(); + } else { + buffer.dispose(); + if (readBytes < 0) { + last = true; + buffer = Buffers.EMPTY_BUFFER; + } else { + throw new IllegalStateException( + "MultipartBody unexpectedly returned 0 bytes available"); + } + } + feed(buffer, last); + } + } finally { + if (bodyLocal != null) { + try { + bodyLocal.close(); + } catch (IOException ignore) { + } + } + } + } + }); + generator.initializeAsynchronousTransfer(ctx, requestPacket); + return false; + } } // END PartsBodyHandler From 6530497ddb512477d72b91b2ae87b8bd369d0b56 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 12 Nov 2013 11:34:08 +0100 Subject: [PATCH 0049/2254] Introduce a constant exception for Remotely Closed --- .../main/java/org/asynchttpclient/AsyncHttpClient.java | 2 ++ .../org/asynchttpclient/util/AsyncHttpProviderUtils.java | 9 ++++++++- .../java/org/asynchttpclient/async/AuthTimeoutTest.java | 3 ++- .../asynchttpclient/providers/grizzly/HttpTxContext.java | 3 ++- .../providers/netty/handler/NettyChannelHandler.java | 3 ++- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClient.java index 18ddaa77fe..85e19adc55 100755 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClient.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClient.java @@ -20,7 +20,9 @@ import org.asynchttpclient.filter.FilterException; import org.asynchttpclient.filter.RequestFilter; import org.asynchttpclient.resumable.ResumableAsyncHandler; + import java.io.Closeable; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java index a0599eecf6..bca98acabb 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java @@ -14,6 +14,7 @@ import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; import java.io.SequenceInputStream; import java.io.UnsupportedEncodingException; @@ -52,7 +53,13 @@ * The cookies's handling code is from the Netty framework. */ public class AsyncHttpProviderUtils { - + + public static final IOException REMOTELY_CLOSED_EXCEPTION = new IOException("Remotely Closed"); + + static { + REMOTELY_CLOSED_EXCEPTION.setStackTrace(new StackTraceElement[] {}); + } + private final static byte[] NO_BYTES = new byte[0]; public final static String DEFAULT_CHARSET = "ISO-8859-1"; diff --git a/api/src/test/java/org/asynchttpclient/async/AuthTimeoutTest.java b/api/src/test/java/org/asynchttpclient/async/AuthTimeoutTest.java index 962e996cd2..9cb3d6026b 100644 --- a/api/src/test/java/org/asynchttpclient/async/AuthTimeoutTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AuthTimeoutTest.java @@ -28,6 +28,7 @@ import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.Realm; import org.asynchttpclient.Response; +import org.asynchttpclient.util.AsyncHttpProviderUtils; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; @@ -195,7 +196,7 @@ public void digestFuturePreemptiveAuthTimeoutTest() throws Exception { protected void inspectException(Throwable t) { assertNotNull(t.getCause()); assertEquals(t.getCause().getClass(), IOException.class); - if (!t.getCause().getMessage().startsWith("Remotely Closed")) { + if (t.getCause() != AsyncHttpProviderUtils.REMOTELY_CLOSED_EXCEPTION) { fail(); } } diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/HttpTxContext.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/HttpTxContext.java index 3ea419bf67..ce4368dd97 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/HttpTxContext.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/HttpTxContext.java @@ -17,6 +17,7 @@ import org.asynchttpclient.Request; import org.asynchttpclient.providers.grizzly.bodyhandler.BodyHandler; import org.asynchttpclient.providers.grizzly.statushandler.StatusHandler; +import org.asynchttpclient.util.AsyncHttpProviderUtils; import org.asynchttpclient.websocket.WebSocket; import org.glassfish.grizzly.CloseListener; import org.glassfish.grizzly.CloseType; @@ -67,7 +68,7 @@ public final class HttpTxContext { public void onClosed(Closeable closeable, CloseType type) throws IOException { if (CloseType.REMOTELY.equals(type)) { - abort(new IOException("Remotely Closed")); + abort(AsyncHttpProviderUtils.REMOTELY_CLOSED_EXCEPTION); } } }; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/NettyChannelHandler.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/NettyChannelHandler.java index 03f68ae246..e2cacfeae7 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/NettyChannelHandler.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/NettyChannelHandler.java @@ -35,6 +35,7 @@ import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.future.NettyResponseFutures; import org.asynchttpclient.providers.netty.request.NettyRequestSender; +import org.asynchttpclient.util.AsyncHttpProviderUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -119,7 +120,7 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception { if (future != null && !future.isDone() && !future.isCancelled()) { if (!requestSender.retry(ctx.channel(), future)) { - channels.abort(future, new IOException("Remotely Closed")); + channels.abort(future, AsyncHttpProviderUtils.REMOTELY_CLOSED_EXCEPTION); } } else { channels.closeChannel(ctx); From 55989012bc109d03d95d4e74091616bf88fd35b5 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 12 Nov 2013 11:37:23 +0100 Subject: [PATCH 0050/2254] append to previous commit --- .../test/java/org/asynchttpclient/async/RetryRequestTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/src/test/java/org/asynchttpclient/async/RetryRequestTest.java b/api/src/test/java/org/asynchttpclient/async/RetryRequestTest.java index 6c27502c6e..fdbc1af6dc 100644 --- a/api/src/test/java/org/asynchttpclient/async/RetryRequestTest.java +++ b/api/src/test/java/org/asynchttpclient/async/RetryRequestTest.java @@ -14,6 +14,7 @@ import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.util.AsyncHttpProviderUtils; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; import org.testng.annotations.Test; @@ -21,6 +22,7 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + import java.io.IOException; import java.io.OutputStream; @@ -76,7 +78,7 @@ public void testMaxRetry() throws Exception { } catch (Exception t) { assertNotNull(t.getCause()); assertEquals(t.getCause().getClass(), IOException.class); - if (!t.getCause().getMessage().startsWith("Remotely Closed")) { + if (t.getCause() != AsyncHttpProviderUtils.REMOTELY_CLOSED_EXCEPTION) { fail(); } } finally { From d673a45fe5bd4ce890e061417daf6d2a720153b3 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 14 Nov 2013 14:55:42 +0100 Subject: [PATCH 0051/2254] All HTTP methods allow passing a body, close #421 --- .../main/java/org/asynchttpclient/RequestBuilderBase.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java index d737158ade..0a5f6789b1 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -614,7 +614,7 @@ public T setConnectionPoolKeyStrategy(ConnectionPoolKeyStrategy connectionPoolKe } public Request build() { - if ((request.length < 0) && (request.streamData == null) && allowBody(request.getMethod())) { + if (request.length < 0 && request.streamData == null) { // can't concatenate content-length String contentLength = null; if (request.headers != null && request.headers.isEmpty()) { @@ -635,10 +635,6 @@ public Request build() { return request; } - private boolean allowBody(String method) { - return !(method.equalsIgnoreCase("GET") || method.equalsIgnoreCase("OPTIONS") || method.equalsIgnoreCase("TRACE") || method.equalsIgnoreCase("HEAD")); - } - public T addOrReplaceCookie(Cookie cookie) { String cookieKey = cookie.getName(); boolean replace = false; From 40e2536d5336cd781c6dc59c36ccdeef87d70ad1 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 26 Nov 2013 09:43:15 +0100 Subject: [PATCH 0052/2254] Port #424 on master --- .../providers/netty/NettyAsyncHttpProvider.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java index d67821c497..4b820aab87 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java @@ -55,8 +55,9 @@ public NettyAsyncHttpProvider(AsyncHttpClientConfig config) { @Override public String toString() { + int availablePermits = channels.freeConnections != null ? channels.freeConnections.availablePermits() : 0; return String.format("NettyAsyncHttpProvider4:\n\t- maxConnections: %d\n\t- openChannels: %s\n\t- connectionPools: %s", config.getMaxTotalConnections() - - channels.freeConnections.availablePermits(), channels.openChannels.toString(), channels.connectionsPool.toString()); + - availablePermits, channels.openChannels.toString(), channels.connectionsPool.toString()); } @Override From aa42d85261c632049ed99747cd3705e5d6c842a4 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 28 Nov 2013 22:28:16 +0100 Subject: [PATCH 0053/2254] Upgrade Netty 4.0.13 --- providers/netty/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/netty/pom.xml b/providers/netty/pom.xml index 9b884d134d..818df789ec 100644 --- a/providers/netty/pom.xml +++ b/providers/netty/pom.xml @@ -39,7 +39,7 @@ io.netty netty-all - 4.0.12.Final + 4.0.13.Final org.javassist From 19644726ce7f7fe10d14a1e560d0cdc325cf973f Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 29 Nov 2013 15:35:22 +0100 Subject: [PATCH 0054/2254] Allow Multipart with unknown content length, close #427 --- .../org/asynchttpclient/multipart/MultipartBody.java | 4 ++-- .../providers/netty/request/NettyRequestSender.java | 9 ++++++++- .../providers/netty/request/NettyRequests.java | 4 +++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java b/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java index f72aaf62c1..99525d0a21 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java +++ b/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java @@ -49,9 +49,9 @@ public class MultipartBody implements RandomAccessBody { enum FileLocation {NONE, START, MIDDLE, END} - public MultipartBody(List parts, String contentType, String contentLength) { + public MultipartBody(List parts, String contentType, long contentLength) { this.boundary = MultipartEncodingUtil.getAsciiBytes(contentType.substring(contentType.indexOf("boundary=") + "boundary=".length())); - this.contentLength = Long.parseLong(contentLength); + this.contentLength = contentLength; this.parts = parts; files = new ArrayList(); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java index 5c049d70ba..3a83fcc695 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java @@ -439,7 +439,14 @@ private Body computeBody(HttpRequest nettyRequest, NettyResponseFuture future } } else if (future.getRequest().getParts() != null) { String contentType = headers.get(HttpHeaders.Names.CONTENT_TYPE); - String length = headers.get(HttpHeaders.Names.CONTENT_LENGTH); + String contentLength = nettyRequest.headers().get(HttpHeaders.Names.CONTENT_LENGTH); + + long length = -1; + if (contentLength != null) { + length = Long.parseLong(contentLength); + } else { + nettyRequest.headers().add(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); + } body = new MultipartBody(future.getRequest().getParts(), contentType, length); } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequests.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequests.java index d5821471eb..3827789185 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequests.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequests.java @@ -260,7 +260,9 @@ else if (uri.getRawQuery() != null) MultipartRequestEntity mre = AsyncHttpProviderUtils.createMultipartRequestEntity(request.getParts(), request.getHeaders()); headers.put(HttpHeaders.Names.CONTENT_TYPE, mre.getContentType()); - headers.put(HttpHeaders.Names.CONTENT_LENGTH, mre.getContentLength()); + if (mre.getContentLength() >= 0) { + headers.put(HttpHeaders.Names.CONTENT_LENGTH, mre.getContentLength()); + } hasDeferredContent = true; } else if (request.getFile() != null) { From 895adc259fd3b9ef2f1f63b7dd026a92b6e82b15 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 29 Nov 2013 15:38:33 +0100 Subject: [PATCH 0055/2254] dammit --- .../providers/grizzly/bodyhandler/PartsBodyHandler.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/PartsBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/PartsBodyHandler.java index f4491f391f..5dcc1bc710 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/PartsBodyHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/PartsBodyHandler.java @@ -67,8 +67,7 @@ public boolean doHandle(final FilterChainContext ctx, final FeedableBodyGenerator generator = new FeedableBodyGenerator() { @Override public Body createBody() throws IOException { - return new MultipartBody(parts, contentType, - String.valueOf(contentLength)); + return new MultipartBody(parts, contentType, contentLength); } }; generator.setFeeder(new FeedableBodyGenerator.BaseFeeder(generator) { From 81d59c1dd45a51359913d0b907542f6fe40a37c0 Mon Sep 17 00:00:00 2001 From: Jeanfrancois Arcand Date: Tue, 3 Dec 2013 16:24:29 -0500 Subject: [PATCH 0056/2254] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 82921e4652..1eb2503dad 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Async Http Client library purpose is to allow Java applications to easily execut com.ning async-http-client - 1.7.20 + 1.7.22 ``` From 01aa462959ef98e64f8019856d5009813fcb09d8 Mon Sep 17 00:00:00 2001 From: Ryan Lubke Date: Tue, 3 Dec 2013 19:41:54 -0800 Subject: [PATCH 0057/2254] Fix compilation error. --- .../java/org/asynchttpclient/multipart/MultipartBodyTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java b/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java index 1e8ba2860c..ceb62a8dd0 100644 --- a/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java +++ b/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java @@ -79,7 +79,7 @@ private static void compareContentLength(final List parts) { final long expectedContentLength = mre.getContentLength(); // get real bytes - final Body multipartBody = new MultipartBody(parts, mre.getContentType(), String.valueOf(expectedContentLength)); + final Body multipartBody = new MultipartBody(parts, mre.getContentType(), expectedContentLength); try { final ByteBuffer buffer = ByteBuffer.allocate(8192); boolean last = false; From 22cb35acf9f55526917a93c7bb5d37933d6e44da Mon Sep 17 00:00:00 2001 From: Ryan Lubke Date: Tue, 3 Dec 2013 19:45:17 -0800 Subject: [PATCH 0058/2254] Fix for #429. --- .../providers/grizzly/bodyhandler/ByteArrayBodyHandler.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ByteArrayBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ByteArrayBodyHandler.java index 9ed538e5fe..c995adef2c 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ByteArrayBodyHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ByteArrayBodyHandler.java @@ -46,11 +46,7 @@ public boolean doHandle(final FilterChainContext ctx, final HttpRequestPacket requestPacket) throws IOException { - String charset = request.getBodyEncoding(); - if (charset == null) { - charset = Charsets.ASCII_CHARSET.name(); - } - final byte[] data = new String(request.getByteData(), charset).getBytes(charset); + final byte[] data = request.getByteData(); final MemoryManager mm = ctx.getMemoryManager(); final Buffer gBuffer = Buffers.wrap(mm, data); if (requestPacket.getContentLength() == -1) { From ed9f9b3b2758ab6b545ff8f294f4eabb604bd338 Mon Sep 17 00:00:00 2001 From: Lukasz Kryger Date: Sun, 8 Dec 2013 22:32:07 +0000 Subject: [PATCH 0059/2254] Nitpicking: "it's" -> "its" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1eb2503dad..561933cd2f 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ Future f = c.prepareGet("http://www.ning.com/").execute(new AsyncHandler String bodyResponse = f.get(); ``` -Finally, you can also configure the AsyncHttpClient via it's AsyncHttpClientConfig object: +Finally, you can also configure the AsyncHttpClient via its AsyncHttpClientConfig object: ```java AsyncHttpClientConfig cf = new AsyncHttpClientConfig.Builder() From 8373cc31ac81498c45a6471163df43c6a0db6029 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 12 Dec 2013 12:14:26 +0100 Subject: [PATCH 0060/2254] Implement onRequestSent for Netty provider, close #434 --- .../AsyncHandlerExtensions.java | 38 +++++++++++++++++++ .../netty/request/NettyRequestSender.java | 4 ++ 2 files changed, 42 insertions(+) create mode 100644 api/src/main/java/org/asynchttpclient/AsyncHandlerExtensions.java diff --git a/api/src/main/java/org/asynchttpclient/AsyncHandlerExtensions.java b/api/src/main/java/org/asynchttpclient/AsyncHandlerExtensions.java new file mode 100644 index 0000000000..858d14201f --- /dev/null +++ b/api/src/main/java/org/asynchttpclient/AsyncHandlerExtensions.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package org.asynchttpclient; + +/** + * This interface hosts new low level callback methods on {@link AsyncHandler}. + * For now, those methods are in a dedicated interface in order not to break the existing API, + * but could be merged into one of the existing ones in AHC 2. + * + * More additional hooks might come, such as: + *

      + *
    • onRetry()
    • + *
    • onConnected()
    • + *
    • onConnectionClosed()
    • + *
    • onBytesSent(long numberOfBytes)
    • + *
    • onBytesReceived(long numberOfBytes)
    • + *
    + */ +public interface AsyncHandlerExtensions { + + /** + * Notify the callback when a request is being written on the wire. + * If the original request causes multiple requests to be sent, for example, because of authorization or retry, + * it will be notified multiple times. + * Currently only supported by the Netty provider. + */ + void onRequestSent(); +} \ No newline at end of file diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java index 3a83fcc695..364c36567a 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java @@ -45,6 +45,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.AsyncHandlerExtensions; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.Body; import org.asynchttpclient.BodyGenerator; @@ -501,6 +502,9 @@ public final void writeRequest(final Channel channel, final AsyncHttpClientC // FIXME That doesn't just leave to true, the set is always done? and what's the point of not having a is/get? if (future.getAndSetWriteHeaders(true)) { try { + if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) { + AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onRequestSent(); + } channel.writeAndFlush(nettyRequest, channel.newProgressivePromise()).addListener(new ProgressListener(config, true, future.getAsyncHandler(), future)); } catch (Throwable cause) { // FIXME why not notify? From 31b3f44079ff5cfa7be277d6e793e1c129e4189a Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 12 Dec 2013 12:17:07 +0100 Subject: [PATCH 0061/2254] Fix deprecation --- .../org/asynchttpclient/providers/netty/channel/Channels.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/Channels.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/Channels.java index bc1f091d02..b5bbbb51dc 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/Channels.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/Channels.java @@ -77,7 +77,7 @@ public class Channels { public static final String WS_DECODER_HANDLER = "ws-decoder"; public static final String WS_ENCODER_HANDLER = "ws-encoder"; - private static final AttributeKey DEFAULT_ATTRIBUTE = new AttributeKey("default"); + private static final AttributeKey DEFAULT_ATTRIBUTE = AttributeKey.valueOf("default"); private final AsyncHttpClientConfig config; private final NettyAsyncHttpProviderConfig asyncHttpProviderConfig; From 18b73b7433977f6a58cd17b67599342e99bbb8cb Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 12 Dec 2013 12:20:41 +0100 Subject: [PATCH 0062/2254] Add new onRetry callback method for #435 --- .../java/org/asynchttpclient/AsyncHandlerExtensions.java | 8 ++++++-- .../providers/netty/request/NettyRequestSender.java | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHandlerExtensions.java b/api/src/main/java/org/asynchttpclient/AsyncHandlerExtensions.java index 858d14201f..1991d0aba1 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHandlerExtensions.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHandlerExtensions.java @@ -19,7 +19,6 @@ * * More additional hooks might come, such as: *