From 8e4d787565b9bc3929c9e24c82accd6c8b7233e7 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Sun, 6 Apr 2014 17:52:28 -0700 Subject: [PATCH 0001/2020] Move the hostname verification to after the SSL handshake has completed. --- .../asynchttpclient/async/BasicHttpsTest.java | 9 +- .../asynchttpclient/async/util/TestUtils.java | 96 ++++++++++++------- .../providers/grizzly/ConnectionManager.java | 52 +++++----- .../grizzly/GrizzlyAsyncHttpProvider.java | 2 +- .../netty/request/NettyConnectListener.java | 40 ++++++-- 5 files changed, 130 insertions(+), 69 deletions(-) diff --git a/api/src/test/java/org/asynchttpclient/async/BasicHttpsTest.java b/api/src/test/java/org/asynchttpclient/async/BasicHttpsTest.java index 7bc7233783..24675057e7 100644 --- a/api/src/test/java/org/asynchttpclient/async/BasicHttpsTest.java +++ b/api/src/test/java/org/asynchttpclient/async/BasicHttpsTest.java @@ -30,6 +30,7 @@ import javax.net.ssl.SSLHandshakeException; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.net.ConnectException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -100,17 +101,19 @@ public void reconnectsAfterFailedCertificationPath() throws Exception { String body = "hello there"; // first request fails because server certificate is rejected + Throwable cause = null; try { c.preparePost(getTargetUrl()).setBody(body).setHeader("Content-Type", "text/html").execute().get(TIMEOUT, TimeUnit.SECONDS); } catch (final ExecutionException e) { - Throwable cause = e.getCause(); + cause = e.getCause(); if (cause instanceof ConnectException) { - assertNotNull(cause.getCause()); + //assertNotNull(cause.getCause()); assertTrue(cause.getCause() instanceof SSLHandshakeException, "Expected an SSLHandshakeException, got a " + cause.getCause()); } else { - assertTrue(cause instanceof SSLHandshakeException, "Expected an SSLHandshakeException, got a " + cause); + assertTrue(cause instanceof IOException, "Expected an IOException, got a " + cause); } } + assertNotNull(cause); trusted.set(true); diff --git a/api/src/test/java/org/asynchttpclient/async/util/TestUtils.java b/api/src/test/java/org/asynchttpclient/async/util/TestUtils.java index 75aab93fe0..7c9b46c50b 100644 --- a/api/src/test/java/org/asynchttpclient/async/util/TestUtils.java +++ b/api/src/test/java/org/asynchttpclient/async/util/TestUtils.java @@ -22,11 +22,7 @@ import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.util.ssl.SslContextFactory; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; +import javax.net.ssl.*; import java.io.File; import java.io.FileNotFoundException; @@ -38,8 +34,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.Charset; -import java.security.KeyStore; -import java.security.SecureRandom; +import java.security.*; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -149,7 +144,6 @@ public static void addHttpsConnector(Server server, int port) throws URISyntaxEx ServerConnector connector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"), new HttpConnectionFactory(httpsConfig)); connector.setPort(port); - server.addConnector(connector); server.addConnector(connector); } @@ -191,21 +185,38 @@ private static void addAuthHandler(Server server, String auth, LoginAuthenticato server.setHandler(security); } + private static KeyManager[] createKeyManagers() throws GeneralSecurityException, IOException { + InputStream keyStoreStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("ssltest-cacerts.jks"); + char[] keyStorePassword = "changeit".toCharArray(); + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(keyStoreStream, keyStorePassword); + assert(ks.size() > 0); + + // Set up key manager factory to use our key store + char[] certificatePassword = "changeit".toCharArray(); + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, certificatePassword); + + // Initialize the SSLContext to work with our key managers. + return kmf.getKeyManagers(); + } + + private static TrustManager[] createTrustManagers() throws GeneralSecurityException, IOException { + InputStream keyStoreStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("ssltest-keystore.jks"); + char[] keyStorePassword = "changeit".toCharArray(); + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(keyStoreStream, keyStorePassword); + assert(ks.size() > 0); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ks); + return tmf.getTrustManagers(); + } + public static SSLContext createSSLContext(AtomicBoolean trust) { try { - InputStream keyStoreStream = HostnameVerifierTest.class.getResourceAsStream("ssltest-cacerts.jks"); - char[] keyStorePassword = "changeit".toCharArray(); - KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(keyStoreStream, keyStorePassword); - - // Set up key manager factory to use our key store - char[] certificatePassword = "changeit".toCharArray(); - KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); - kmf.init(ks, certificatePassword); - - // Initialize the SSLContext to work with our key managers. - KeyManager[] keyManagers = kmf.getKeyManagers(); - TrustManager[] trustManagers = new TrustManager[] { dummyTrustManager(trust) }; + KeyManager[] keyManagers = createKeyManagers(); + TrustManager[] trustManagers = new TrustManager[] { dummyTrustManager(trust, (X509TrustManager) createTrustManagers()[0]) }; SecureRandom secureRandom = new SecureRandom(); SSLContext sslContext = SSLContext.getInstance("TLS"); @@ -217,21 +228,40 @@ public static SSLContext createSSLContext(AtomicBoolean trust) { } } - private static final TrustManager dummyTrustManager(final AtomicBoolean trust) { - return new X509TrustManager() { - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } + public static class DummyTrustManager implements X509TrustManager { - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - } + private final X509TrustManager tm; + private final AtomicBoolean trust; - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - if (!trust.get()) { - throw new CertificateException("Server certificate not trusted."); - } + public DummyTrustManager(final AtomicBoolean trust, final X509TrustManager tm) { + this.trust = trust; + this.tm = tm; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + tm.checkClientTrusted(chain, authType); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + if (!trust.get()) { + throw new CertificateException("Server certificate not trusted."); } - }; + tm.checkServerTrusted(chain, authType); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return tm.getAcceptedIssuers(); + } + } + + private static TrustManager dummyTrustManager(final AtomicBoolean trust, final X509TrustManager tm) { + return new DummyTrustManager(trust, tm); + } public static File getClasspathFile(String file) throws FileNotFoundException { diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java index 41984d449f..e90f418664 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java @@ -20,18 +20,22 @@ import org.asynchttpclient.ConnectionPoolKeyStrategy; import org.asynchttpclient.ProxyServer; import org.asynchttpclient.Request; +import org.asynchttpclient.util.Base64; import org.glassfish.grizzly.CompletionHandler; import org.glassfish.grizzly.Connection; -import org.glassfish.grizzly.EmptyCompletionHandler; import org.glassfish.grizzly.Grizzly; import org.glassfish.grizzly.GrizzlyFuture; import org.glassfish.grizzly.attributes.Attribute; import org.glassfish.grizzly.connectionpool.EndpointKey; import org.glassfish.grizzly.filterchain.FilterChainBuilder; import org.glassfish.grizzly.impl.FutureImpl; +import org.glassfish.grizzly.ssl.SSLBaseFilter; +import org.glassfish.grizzly.ssl.SSLFilter; import org.glassfish.grizzly.ssl.SSLUtils; import org.glassfish.grizzly.utils.Futures; import org.glassfish.grizzly.utils.IdleTimeoutFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSession; @@ -51,6 +55,8 @@ public class ConnectionManager { + private final static Logger LOGGER = LoggerFactory.getLogger(ConnectionManager.class); + private static final Attribute DO_NOT_CACHE = Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(ConnectionManager.class .getName()); private final ConnectionPool connectionPool; @@ -60,13 +66,15 @@ public class ConnectionManager { private final FilterChainBuilder secureBuilder; private final FilterChainBuilder nonSecureBuilder; private final boolean asyncConnect; + private final SSLFilter sslFilter; // ------------------------------------------------------------ Constructors ConnectionManager(final GrizzlyAsyncHttpProvider provider,// final ConnectionPool connectionPool,// final FilterChainBuilder secureBuilder,// - final FilterChainBuilder nonSecureBuilder) { + final FilterChainBuilder nonSecureBuilder,// + final SSLFilter sslFilter) { this.provider = provider; final AsyncHttpClientConfig config = provider.getClientConfig(); @@ -87,6 +95,7 @@ public class ConnectionManager { AsyncHttpProviderConfig providerConfig = config.getAsyncHttpProviderConfig(); asyncConnect = providerConfig instanceof GrizzlyAsyncHttpProviderConfig ? GrizzlyAsyncHttpProviderConfig.class.cast(providerConfig) .isAsyncConnectMode() : false; + this.sslFilter = sslFilter; } // ---------------------------------------------------------- Public Methods @@ -95,7 +104,7 @@ public void doTrackedConnection(final Request request,// final GrizzlyResponseFuture requestFuture,// final CompletionHandler connectHandler) throws IOException { final EndpointKey key = getEndPointKey(request, requestFuture.getProxyServer()); - CompletionHandler handler = wrapHandler(request, getVerifier(), connectHandler); + CompletionHandler handler = wrapHandler(request, getVerifier(), connectHandler, sslFilter); if (asyncConnect) { connectionPool.take(key, handler); } else { @@ -136,37 +145,32 @@ public Connection obtainConnection(final Request request, final GrizzlyResponseF // --------------------------------------------------Package Private Methods static CompletionHandler wrapHandler(final Request request, final HostnameVerifier verifier, - final CompletionHandler delegate) { + final CompletionHandler delegate, final SSLFilter sslFilter) { final URI uri = request.getURI(); if (Utils.isSecure(uri) && verifier != null) { - return new EmptyCompletionHandler() { + SSLBaseFilter.HandshakeListener handshakeListener = new SSLBaseFilter.HandshakeListener() { @Override - public void completed(Connection result) { - final String host = uri.getHost(); - final SSLSession session = SSLUtils.getSSLEngine(result).getSession(); - if (!verifier.verify(host, session)) { - failed(new ConnectException("Host name verification failed for host " + host)); - } else { - delegate.completed(result); - } - + public void onStart(Connection connection) { + // do nothing + LOGGER.debug("SSL Handshake onStart: "); } @Override - public void cancelled() { - delegate.cancelled(); - } + public void onComplete(Connection connection) { + sslFilter.removeHandshakeListener(this); - @Override - public void failed(Throwable throwable) { - delegate.failed(throwable); - } + final String host = uri.getHost(); + final SSLSession session = SSLUtils.getSSLEngine(connection).getSession(); + LOGGER.debug("SSL Handshake onComplete: session = {}, id = {}, isValid = {}, host = {}", session.toString(), Base64.encode(session.getId()), session.isValid(), host); - @Override - public void updated(Connection result) { - delegate.updated(result); + if (!verifier.verify(host, session)) { + connection.close(); // XXX what's the correct way to kill a connection? + IOException e = new ConnectException("Host name verification failed for host " + host); + delegate.failed(e); + } } }; + sslFilter.addHandshakeListener(handshakeListener); } return delegate; } 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 a7d221093f..722a1b7e88 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 @@ -334,7 +334,7 @@ public void onTimeout(Connection connection) { } else { pool = null; } - connectionManager = new ConnectionManager(this, pool, secure, nonSecure); + connectionManager = new ConnectionManager(this, pool, secure, nonSecure, filter); } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java index 7bfc6d8717..c023406e44 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java @@ -16,10 +16,13 @@ */ package org.asynchttpclient.providers.netty.request; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.future.StackTraceInspector; +import org.asynchttpclient.util.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,6 +31,10 @@ import io.netty.channel.ChannelFutureListener; import io.netty.handler.ssl.SslHandler; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLSession; import java.net.ConnectException; import java.nio.channels.ClosedChannelException; @@ -54,15 +61,32 @@ public NettyResponseFuture future() { public void onFutureSuccess(final Channel channel) throws ConnectException { Channels.setDefaultAttribute(channel, future); - SslHandler sslHandler = Channels.getSslHandler(channel); - - if (sslHandler != null && !config.getHostnameVerifier().verify(future.getURI().getHost(), sslHandler.engine().getSession())) { - ConnectException exception = new ConnectException("HostnameVerifier exception"); - future.abort(exception); - throw exception; + final HostnameVerifier hostnameVerifier = config.getHostnameVerifier(); + final SslHandler sslHandler = Channels.getSslHandler(channel); + if (hostnameVerifier != null && sslHandler != null) { + final String host = future.getURI().getHost(); + sslHandler.handshakeFuture().addListener(new GenericFutureListener>() { + @Override + public void operationComplete(Future handshakeFuture) throws Exception { + if (handshakeFuture.isSuccess()) { + Channel channel = (Channel) handshakeFuture.getNow(); + SSLEngine engine = sslHandler.engine(); + SSLSession session = engine.getSession(); + + LOGGER.debug("onFutureSuccess: session = {}, id = {}, isValid = {}, host = {}", session.toString(), Base64.encode(session.getId()), session.isValid(), host); + if (!hostnameVerifier.verify(host, session)) { + ConnectException exception = new ConnectException("HostnameVerifier exception"); + future.abort(exception); + throw exception; + } else { + requestSender.writeRequest(future, channel); + } + } + } + }); + } else { + requestSender.writeRequest(future, channel); } - - requestSender.writeRequest(future, channel); } public void onFutureFailure(Channel channel, Throwable cause) { From 3b41defac647ead03343da8b81c7fae9da8b5cb1 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Sat, 12 Apr 2014 22:54:03 +0200 Subject: [PATCH 0002/2020] MakeTimeoutsHolder threadsafe, close #534 --- .../providers/netty/request/timeout/TimeoutsHolder.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java index e9de57017c..309d4712a1 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java @@ -17,18 +17,19 @@ import io.netty.util.Timeout; +import java.util.concurrent.atomic.AtomicBoolean; + public class TimeoutsHolder { + private AtomicBoolean cancelled = new AtomicBoolean(); public volatile Timeout requestTimeout; public volatile Timeout idleConnectionTimeout; public void cancel() { - if (requestTimeout != null) { + if (cancelled.compareAndSet(false, true)) { requestTimeout.cancel(); - requestTimeout = null; - } - if (idleConnectionTimeout != null) { idleConnectionTimeout.cancel(); + requestTimeout = null; idleConnectionTimeout = null; } } From f96bcbbe906c1e472fbbd7cca5cfaf3778db02d6 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Sat, 12 Apr 2014 23:03:42 +0200 Subject: [PATCH 0003/2020] Release 1.8.7 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cfb1fcca24..ab674947c3 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.8.6 + 1.8.7 ``` From 56b7228f52e34300ce209c25fdc55775dff90e16 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 15 Apr 2014 14:59:30 +0200 Subject: [PATCH 0004/2020] Add remote channel ip address to read timeout exception, port #538 on master --- .../providers/netty/future/NettyResponseFuture.java | 5 +++++ .../netty/request/timeout/RequestTimeoutTimerTask.java | 5 +++-- .../providers/netty/NettyPerRequestTimeoutTest.java | 4 +++- 3 files changed, 11 insertions(+), 3 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 01739dc8a2..06f1d538e4 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 @@ -34,6 +34,7 @@ import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpResponse; +import java.net.SocketAddress; import java.net.URI; import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; @@ -468,6 +469,10 @@ public boolean canRetry() { return true; } + public SocketAddress getChannelRemoteAddress() { + return channel() != null? channel().remoteAddress(): null; + } + public void setRequest(Request request) { this.request = request; } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java index 65dcdb0387..98c5198895 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java @@ -45,8 +45,9 @@ public void run(Timeout timeout) throws Exception { } if (!nettyResponseFuture.isDone() && !nettyResponseFuture.isCancelled()) { - expire("Request timeout of " + nettyResponseFuture.getRequestTimeoutInMs() + " ms", - millisTime() - nettyResponseFuture.getStart()); + long age = millisTime() - nettyResponseFuture.getStart(); + expire("Request timed out to " + nettyResponseFuture.getChannelRemoteAddress() + " of " + + nettyResponseFuture.getRequestTimeoutInMs() + " ms after " + age + " ms", age); nettyResponseFuture.setRequestTimeoutReached(); } } 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 706baa3730..960a039d9a 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 @@ -22,7 +22,9 @@ public class NettyPerRequestTimeoutTest extends PerRequestTimeoutTest { @Override protected void checkTimeoutMessage(String message) { - assertTrue(message.equals("Request timeout of 100 ms")); + assertTrue(message.startsWith("Request timed out"), "error message indicates reason of error"); + assertTrue(message.contains("127.0.0.1"), "error message contains remote ip address"); + assertTrue(message.contains("of 100 ms"), "error message contains timeout configuration value"); } @Override From 63c51410d8a73e9406f996a06d4431ac70c14a9e Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 18 Apr 2014 10:32:33 +0200 Subject: [PATCH 0005/2020] idleConnectionTimeout might have been dropped or not set, close #534 --- .../netty/request/timeout/TimeoutsHolder.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java index 309d4712a1..a27e903510 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java @@ -21,16 +21,20 @@ public class TimeoutsHolder { - private AtomicBoolean cancelled = new AtomicBoolean(); + private final AtomicBoolean cancelled = new AtomicBoolean(); public volatile Timeout requestTimeout; public volatile Timeout idleConnectionTimeout; public void cancel() { if (cancelled.compareAndSet(false, true)) { - requestTimeout.cancel(); - idleConnectionTimeout.cancel(); - requestTimeout = null; - idleConnectionTimeout = null; + if (requestTimeout != null) { + requestTimeout.cancel(); + requestTimeout = null; + } + if (idleConnectionTimeout != null) { + idleConnectionTimeout.cancel(); + idleConnectionTimeout = null; + } } } } From 0f1a04f56e0bf94af22145d017cceab675f9a439 Mon Sep 17 00:00:00 2001 From: Yubao Liu Date: Mon, 21 Apr 2014 18:40:54 +0800 Subject: [PATCH 0006/2020] set correct default maxConnectionLifetimeInMs com.ning.http.client.AsyncHttpClientConfigBean doesn't use com.ning.http.client.AsyncHttpClientConfig.Builder thus doesn't pick many default values, unluckily maxConnectionLifeTimeInMs is one of them, it's wrongly initialized to zero instead of -1, this bug will make org.asynchttpclient.providers.netty.channel.DefaultChannelPool.offer() close the cached http connection too early because "createTime + maxConnectionLifeTimeInMs < millisTime()". public boolean offer(String uri, Channel channel) { if (closed.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; } --- .../java/org/asynchttpclient/AsyncHttpClientConfigBean.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java index f5042b37d3..394fe1aabc 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java @@ -51,6 +51,7 @@ void configureDefaults() { idleConnectionInPoolTimeoutInMs = Integer.getInteger(ASYNC_CLIENT + "defaultIdleConnectionInPoolTimeoutInMS", 60 * 1000); idleConnectionTimeoutInMs = Integer.getInteger(ASYNC_CLIENT + "defaultIdleConnectionTimeoutInMS", 60 * 1000); requestTimeoutInMs = Integer.getInteger(ASYNC_CLIENT + "defaultRequestTimeoutInMS", 60 * 1000); + maxConnectionLifeTimeInMs = Integer.getInteger(ASYNC_CLIENT + "defaultMaxConnectionLifeTimeInMs", -1); redirectEnabled = Boolean.getBoolean(ASYNC_CLIENT + "defaultRedirectsEnabled"); maxDefaultRedirects = Integer.getInteger(ASYNC_CLIENT + "defaultMaxRedirects", 5); compressionEnabled = Boolean.getBoolean(ASYNC_CLIENT + "compressionEnabled"); @@ -99,6 +100,11 @@ public AsyncHttpClientConfigBean setConnectionTimeOutInMs(int connectionTimeOutI return this; } + public AsyncHttpClientConfigBean setMaxConnectionLifeTimeInMs(int maxConnectionLifeTimeInMs) { + this.maxConnectionLifeTimeInMs = maxConnectionLifeTimeInMs; + return this; + } + public AsyncHttpClientConfigBean setIdleConnectionInPoolTimeoutInMs(int idleConnectionInPoolTimeoutInMs) { this.idleConnectionInPoolTimeoutInMs = idleConnectionInPoolTimeoutInMs; return this; From bdaddbc57234f9e650f4dc87b85bb1e464e3764b Mon Sep 17 00:00:00 2001 From: Yubao Liu Date: Mon, 21 Apr 2014 18:41:25 +0800 Subject: [PATCH 0007/2020] enable customization of strict302Handling in AsyncHttpClientConfigBean need this method for Spring bean configuration --- .../java/org/asynchttpclient/AsyncHttpClientConfigBean.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java index 394fe1aabc..e0e01b9943 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java @@ -130,6 +130,11 @@ public AsyncHttpClientConfigBean setMaxDefaultRedirects(int maxDefaultRedirects) return this; } + public AsyncHttpClientConfigBean setStrict302Handling(boolean strict302Handling) { + this.strict302Handling = strict302Handling; + return this; + } + public AsyncHttpClientConfigBean setCompressionEnabled(boolean compressionEnabled) { this.compressionEnabled = compressionEnabled; return this; From 0f5397d7291155917b2ec75c85fa07e41e4af0a4 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 28 Apr 2014 11:25:00 +0200 Subject: [PATCH 0008/2020] Bump 1.8.8 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ab674947c3..6b27e4180d 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.8.7 + 1.8.8 ``` From 9aba8ca3c6b28f14ccd93112a33db79c4e2f01ad Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 30 Apr 2014 21:49:53 +0200 Subject: [PATCH 0009/2020] Upgrade Netty 4.0.19 #546 --- 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 296cf8819d..3ecc938d35 100644 --- a/providers/netty/pom.xml +++ b/providers/netty/pom.xml @@ -39,7 +39,7 @@ io.netty netty-all - 4.0.15.Final + 4.0.19.Final org.javassist From 54869e733b3c5da8fc939192ce806e8ebff20b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20R=C3=A9mond?= Date: Tue, 6 May 2014 13:50:48 +0200 Subject: [PATCH 0010/2020] Uniformized timeout message --- .../netty/request/timeout/RequestTimeoutTimerTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java index 98c5198895..0044de23da 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java @@ -47,7 +47,7 @@ public void run(Timeout timeout) throws Exception { if (!nettyResponseFuture.isDone() && !nettyResponseFuture.isCancelled()) { long age = millisTime() - nettyResponseFuture.getStart(); expire("Request timed out to " + nettyResponseFuture.getChannelRemoteAddress() + " of " - + nettyResponseFuture.getRequestTimeoutInMs() + " ms after " + age + " ms", age); + + nettyResponseFuture.getRequestTimeoutInMs() + " ms", age); nettyResponseFuture.setRequestTimeoutReached(); } } From cc3d57f2b0f29f569bf5415ce0ee29bd86f3c9c0 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 6 May 2014 15:43:34 +0200 Subject: [PATCH 0011/2020] Have idle timeout also expose remote ip, just like request timeout #548 --- .../netty/request/timeout/IdleConnectionTimeoutTimerTask.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/IdleConnectionTimeoutTimerTask.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/IdleConnectionTimeoutTimerTask.java index 99ef4bf1dd..25a9d17dd8 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/IdleConnectionTimeoutTimerTask.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/IdleConnectionTimeoutTimerTask.java @@ -57,8 +57,9 @@ public void run(Timeout timeout) throws Exception { if (durationBeforeCurrentIdleConnectionTimeout <= 0L) { // idleConnectionTimeout reached + String message = "Idle connection timeout to " + nettyResponseFuture.getChannelRemoteAddress() + " of " + idleConnectionTimeout + " ms"; long durationSinceLastTouch = now - nettyResponseFuture.getLastTouch(); - expire("Idle connection timeout of " + idleConnectionTimeout + " ms", durationSinceLastTouch); + expire(message, durationSinceLastTouch); nettyResponseFuture.setIdleConnectionTimeoutReached(); } else if (currentIdleConnectionTimeoutInstant < requestTimeoutInstant) { From 3b4aa0c2f2907ee05ab3ccfbcfcd4dee4de5197b Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 6 May 2014 15:44:37 +0200 Subject: [PATCH 0012/2020] minor clean up --- .../netty/request/timeout/RequestTimeoutTimerTask.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java index 0044de23da..6035974823 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java @@ -45,9 +45,9 @@ public void run(Timeout timeout) throws Exception { } if (!nettyResponseFuture.isDone() && !nettyResponseFuture.isCancelled()) { + String message = "Request timed out to " + nettyResponseFuture.getChannelRemoteAddress() + " of " + nettyResponseFuture.getRequestTimeoutInMs() + " ms"; long age = millisTime() - nettyResponseFuture.getStart(); - expire("Request timed out to " + nettyResponseFuture.getChannelRemoteAddress() + " of " - + nettyResponseFuture.getRequestTimeoutInMs() + " ms", age); + expire(message, age); nettyResponseFuture.setRequestTimeoutReached(); } } From 368fb5753715f88d78e5c347d2fc8a9f1b2b0bb7 Mon Sep 17 00:00:00 2001 From: oleksiys Date: Wed, 14 May 2014 23:30:20 -0700 Subject: [PATCH 0013/2020] [master] + integrate Grizzly 2.3.12 --- .../grizzly/GrizzlyAsyncHttpProvider.java | 44 ++- .../filters/AsyncHttpClientFilter.java | 8 +- .../GrizzlyFeedableBodyGeneratorTest.java | 292 ++++++++++++++++++ 3 files changed, 325 insertions(+), 19 deletions(-) create mode 100644 providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java 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 722a1b7e88..6949ca9be3 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Sonatype, Inc. All rights reserved. + * Copyright (c) 2012-2014 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. @@ -73,6 +73,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.glassfish.grizzly.spdy.SpdyVersion; /** * A Grizzly 2.0-based implementation of {@link AsyncHttpProvider}. @@ -441,8 +442,9 @@ void timeout(final Connection c) { // ---------------------------------------------------------- Nested Classes private static final class ProtocolNegotiator implements ClientSideNegotiator { + private static final SpdyVersion[] SUPPORTED_SPDY_VERSIONS = + {SpdyVersion.SPDY_3_1, SpdyVersion.SPDY_3}; - private static final String SPDY = "spdy/3"; private static final String HTTP = "HTTP/1.1"; private final FilterChain spdyFilterChain; @@ -465,23 +467,31 @@ public boolean wantNegotiate(SSLEngine engine) { } @Override - public String selectProtocol(SSLEngine engine, LinkedHashSet strings) { - GrizzlyAsyncHttpProvider.LOGGER.info("ProtocolSelector::selectProtocol: " + strings); + public String selectProtocol(SSLEngine engine, LinkedHashSet protocols) { + GrizzlyAsyncHttpProvider.LOGGER.info("ProtocolSelector::selectProtocol: " + protocols); final Connection connection = NextProtoNegSupport.getConnection(engine); - // Give preference to SPDY/3. If not available, check for HTTP as a - // fallback - if (strings.contains(SPDY)) { - GrizzlyAsyncHttpProvider.LOGGER.info("ProtocolSelector::selecting: " + SPDY); - SSLConnectionContext sslCtx = SSLUtils.getSslConnectionContext(connection); - sslCtx.setNewConnectionFilterChain(spdyFilterChain); - final SpdySession spdySession = new SpdySession(connection, false, spdyHandlerFilter); - spdySession.setLocalInitialWindowSize(spdyHandlerFilter.getInitialWindowSize()); - spdySession.setLocalMaxConcurrentStreams(spdyHandlerFilter.getMaxConcurrentStreams()); - Utils.setSpdyConnection(connection); - SpdySession.bind(connection, spdySession); - return SPDY; - } else if (strings.contains(HTTP)) { + // Give preference to SPDY/3.1 or SPDY/3. If not available, check for HTTP as a + // fallback + for (SpdyVersion version : SUPPORTED_SPDY_VERSIONS) { + final String versionDef = version.toString(); + if (protocols.contains(versionDef)) { + GrizzlyAsyncHttpProvider.LOGGER.info("ProtocolSelector::selecting: " + versionDef); + SSLConnectionContext sslCtx = SSLUtils.getSslConnectionContext(connection); + sslCtx.setNewConnectionFilterChain(spdyFilterChain); + final SpdySession spdySession = + version.newSession(connection, false, spdyHandlerFilter); + + spdySession.setLocalStreamWindowSize(spdyHandlerFilter.getInitialWindowSize()); + spdySession.setLocalMaxConcurrentStreams(spdyHandlerFilter.getMaxConcurrentStreams()); + Utils.setSpdyConnection(connection); + SpdySession.bind(connection, spdySession); + + return versionDef; + } + } + + if (protocols.contains(HTTP)) { GrizzlyAsyncHttpProvider.LOGGER.info("ProtocolSelector::selecting: " + HTTP); // Use the default HTTP FilterChain. return HTTP; 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 490e38776d..53647c84d4 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 @@ -281,8 +281,9 @@ private boolean sendAsGrizzlyRequest(final RequestInfoHolder requestInfoHolder, sendingCtx = checkAndHandleFilterChainUpdate(ctx, sendingCtx); } final Connection c = ctx.getConnection(); + final HttpContext httpCtx; if (!Utils.isSpdyConnection(c)) { - HttpContext.newInstance(ctx, c, c, c); + httpCtx = HttpContext.newInstance(c, c, c, requestPacketLocal); } else { SpdySession session = SpdySession.get(c); final Lock lock = session.getNewClientStreamLock(); @@ -290,12 +291,15 @@ private boolean sendAsGrizzlyRequest(final RequestInfoHolder requestInfoHolder, lock.lock(); SpdyStream stream = session.openStream(requestPacketLocal, session.getNextLocalStreamId(), 0, 0, 0, false, !requestPacketLocal.isExpectContent()); - HttpContext.newInstance(ctx, stream, stream, stream); + httpCtx = HttpContext.newInstance(stream, stream, stream, requestPacketLocal); } finally { lock.unlock(); } } + httpCtx.attach(ctx); HttpTxContext.set(ctx, httpTxContext); + requestPacketLocal.getProcessingState().setHttpContext(httpCtx); + return sendRequest(sendingCtx, request, requestPacketLocal); } diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java new file mode 100644 index 0000000000..b80e6b724e --- /dev/null +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java @@ -0,0 +1,292 @@ +/* + * 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.AsyncCompletionHandler; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.RequestBuilder; +import org.glassfish.grizzly.Buffer; +import org.glassfish.grizzly.http.server.HttpHandler; +import org.glassfish.grizzly.http.server.HttpServer; +import org.glassfish.grizzly.http.server.NetworkListener; +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.grizzly.http.server.Response; +import org.glassfish.grizzly.memory.Buffers; +import org.glassfish.grizzly.ssl.SSLContextConfigurator; +import org.glassfish.grizzly.ssl.SSLEngineConfigurator; +import org.glassfish.grizzly.utils.Charsets; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.asynchttpclient.DefaultAsyncHttpClient; + +import static org.glassfish.grizzly.http.server.NetworkListener.DEFAULT_NETWORK_HOST; +import static org.glassfish.grizzly.memory.MemoryManager.DEFAULT_MEMORY_MANAGER; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.fail; +import static org.testng.AssertJUnit.assertEquals; + +public class GrizzlyFeedableBodyGeneratorTest { + + private static final byte[] DATA = + "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ".getBytes(Charsets.ASCII_CHARSET); + private static final int TEMP_FILE_SIZE = 2 * 1024 * 1024; + private static final int NON_SECURE_PORT = 9991; + private static final int SECURE_PORT = 9992; + + + private HttpServer server; + private File tempFile; + + + // ------------------------------------------------------------------- Setup + + + @BeforeTest + public void setup() throws Exception { + generateTempFile(); + server = new HttpServer(); + NetworkListener nonSecure = + new NetworkListener("nonsecure", + DEFAULT_NETWORK_HOST, + NON_SECURE_PORT); + NetworkListener secure = + new NetworkListener("secure", + DEFAULT_NETWORK_HOST, + SECURE_PORT); + secure.setSecure(true); + secure.setSSLEngineConfig(createSSLConfig()); + server.addListener(nonSecure); + server.addListener(secure); + server.getServerConfiguration().addHttpHandler(new ConsumingHandler(), "/test"); + server.start(); + } + + + // --------------------------------------------------------------- Tear Down + + + @AfterTest + public void tearDown() { + if (!tempFile.delete()) { + tempFile.deleteOnExit(); + } + tempFile = null; + server.shutdownNow(); + server = null; + } + + + // ------------------------------------------------------------ Test Methods + + + @Test + public void testSimpleFeederMultipleThreads() throws Exception { + doSimpleFeeder(false); + } + + @Test + public void testSimpleFeederOverSSLMultipleThreads() throws Exception { + doSimpleFeeder(true); + } + + + // --------------------------------------------------------- Private Methods + + + private void doSimpleFeeder(final boolean secure) { + final int threadCount = 10; + final CountDownLatch latch = new CountDownLatch(threadCount); + final int port = (secure ? SECURE_PORT : NON_SECURE_PORT); + final String scheme = (secure ? "https" : "http"); + ExecutorService service = Executors.newFixedThreadPool(threadCount); + + AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder() + .setMaximumConnectionsPerHost(60) + .setMaximumConnectionsTotal(60) + .build(); + final AsyncHttpClient client = + new DefaultAsyncHttpClient(new GrizzlyAsyncHttpProvider(config), config); + final int[] statusCodes = new int[threadCount]; + final int[] totalsReceived = new int[threadCount]; + final Throwable[] errors = new Throwable[threadCount]; + for (int i = 0; i < threadCount; i++) { + final int idx = i; + service.execute(new Runnable() { + @Override + public void run() { + FeedableBodyGenerator generator = + new FeedableBodyGenerator(); + FeedableBodyGenerator.SimpleFeeder simpleFeeder = + new FeedableBodyGenerator.SimpleFeeder(generator) { + @Override + public void flush() throws IOException { + FileInputStream in = null; + try { + final byte[] bytesIn = new byte[2048]; + in = new FileInputStream(tempFile); + int read; + while ((read = in.read(bytesIn)) != -1) { + final Buffer b = + Buffers.wrap( + DEFAULT_MEMORY_MANAGER, + bytesIn, + 0, + read); + feed(b, false); + } + feed(Buffers.EMPTY_BUFFER, true); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ignored) { + } + } + } + } + }; + generator.setFeeder(simpleFeeder); + generator.setMaxPendingBytes(10000); + + RequestBuilder builder = new RequestBuilder("POST"); + builder.setUrl(scheme + "://localhost:" + port + "/test"); + builder.setBody(generator); + try { + client.executeRequest(builder.build(), + new AsyncCompletionHandler() { + @Override + public org.asynchttpclient.Response onCompleted(org.asynchttpclient.Response response) + throws Exception { + try { + totalsReceived[idx] = Integer.parseInt(response.getHeader("x-total")); + } catch (Exception e) { + errors[idx] = e; + } + statusCodes[idx] = response.getStatusCode(); + latch.countDown(); + return response; + } + + @Override + public void onThrowable(Throwable t) { + errors[idx] = t; + t.printStackTrace(); + latch.countDown(); + } + }); + } catch (IOException e) { + errors[idx] = e; + latch.countDown(); + } + } + }); + } + + try { + latch.await(1, TimeUnit.MINUTES); + } catch (InterruptedException e) { + fail("Latch interrupted"); + } + + for (int i = 0; i < threadCount; i++) { + assertEquals(200, statusCodes[i]); + assertNull(errors[i]); + assertEquals(tempFile.length(), totalsReceived[i]); + } + } + + + private static SSLEngineConfigurator createSSLConfig() + throws Exception { + final SSLContextConfigurator sslContextConfigurator = + new SSLContextConfigurator(); + final ClassLoader cl = GrizzlyFeedableBodyGeneratorTest.class.getClassLoader(); + // override system properties + final URL cacertsUrl = cl.getResource("ssltest-cacerts.jks"); + if (cacertsUrl != null) { + sslContextConfigurator.setTrustStoreFile(cacertsUrl.getFile()); + sslContextConfigurator.setTrustStorePass("changeit"); + } + + // override system properties + final URL keystoreUrl = cl.getResource("ssltest-keystore.jks"); + if (keystoreUrl != null) { + sslContextConfigurator.setKeyStoreFile(keystoreUrl.getFile()); + sslContextConfigurator.setKeyStorePass("changeit"); + } + + return new SSLEngineConfigurator( + sslContextConfigurator.createSSLContext(), + false, false, false); + } + + + private void generateTempFile() throws IOException { + tempFile = File.createTempFile("feedable", null); + int total = 0; + byte[] chunk = new byte[1024]; + Random r = new Random(System.currentTimeMillis()); + FileOutputStream out = new FileOutputStream(tempFile); + while (total < TEMP_FILE_SIZE) { + for (int i = 0; i < chunk.length; i++) { + chunk[i] = DATA[r.nextInt(DATA.length)]; + } + out.write(chunk); + total += chunk.length; + } + out.flush(); + out.close(); + } + + + // ---------------------------------------------------------- Nested Classes + + + private static final class ConsumingHandler extends HttpHandler { + + + // -------------------------------------------- Methods from HttpHandler + + + @Override + public void service(Request request, Response response) + throws Exception { + int total = 0; + byte[] bytesIn = new byte[2048]; + InputStream in = request.getInputStream(); + int read; + while ((read = in.read(bytesIn)) != -1) { + total += read; + Thread.sleep(5); + } + response.addHeader("X-Total", Integer.toString(total)); + } + + } // END ConsumingHandler + +} From aaf7c17531618f8e075c98d0acf0c8cf42d92661 Mon Sep 17 00:00:00 2001 From: oleksiys Date: Thu, 15 May 2014 00:58:07 -0700 Subject: [PATCH 0014/2020] [master] fix issue #549 https://github.com/AsyncHttpClient/async-http-client/issues/549 "[grizzly-provider] connection abort, when neither transfer-encoding nor content-length is specified" --- .../providers/grizzly/EventHandler.java | 7 ++-- .../grizzly/GrizzlyResponseStatus.java | 14 ++++++- .../providers/grizzly/HttpTxContext.java | 27 ++++++++++-- .../filters/AsyncHttpClientEventFilter.java | 28 ++++++++++++- .../filters/AsyncHttpClientFilter.java | 3 +- .../filters/events/GracefulCloseEvent.java | 41 +++++++++++++++++++ 6 files changed, 110 insertions(+), 10 deletions(-) create mode 100644 providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/GracefulCloseEvent.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 2971f0a59c..fe956e4d79 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -163,8 +163,9 @@ public void onInitialLineParsed(HttpHeader httpHeader, FilterChainContext ctx) { } } } - final GrizzlyResponseStatus responseStatus = new GrizzlyResponseStatus((HttpResponsePacket) httpHeader, context.getRequest() - .getURI(), config); + final GrizzlyResponseStatus responseStatus = + new GrizzlyResponseStatus((HttpResponsePacket) httpHeader, + context.getRequest().getURI(), config); context.setResponseStatus(responseStatus); if (context.getStatusHandler() != null) { return; 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 65eb7dd1ad..8aa2e02cb4 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Sonatype, Inc. All rights reserved. + * Copyright (c) 2012-2014 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. @@ -38,7 +38,8 @@ public class GrizzlyResponseStatus extends HttpResponseStatus { private final int majorVersion; private final int minorVersion; private final String protocolText; - + private final HttpResponsePacket response; + // ------------------------------------------------------------ Constructors public GrizzlyResponseStatus(final HttpResponsePacket response, final URI uri, AsyncHttpClientConfig config) { @@ -49,6 +50,8 @@ public GrizzlyResponseStatus(final HttpResponsePacket response, final URI uri, A majorVersion = response.getProtocol().getMajorVersion(); minorVersion = response.getProtocol().getMinorVersion(); protocolText = response.getProtocolString(); + + this.response = response; } // ----------------------------------------- Methods from HttpResponseStatus @@ -105,4 +108,11 @@ public int getProtocolMinorVersion() { public String getProtocolText() { return protocolText; } + + /** + * @return internal Grizzly {@link HttpResponsePacket} + */ + public HttpResponsePacket getResponse() { + return response; + } } 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 8ca71b1ce3..72755014c7 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -33,6 +33,10 @@ import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import org.asynchttpclient.providers.grizzly.filters.events.GracefulCloseEvent; +import org.glassfish.grizzly.Connection; +import org.glassfish.grizzly.filterchain.FilterChain; +import org.glassfish.grizzly.http.HttpResponsePacket; public final class HttpTxContext { @@ -62,10 +66,21 @@ public final class HttpTxContext { private HandShake handshake; private ProtocolHandler protocolHandler; private WebSocket webSocket; - private CloseListener listener = new CloseListener() { + private final CloseListener listener = new CloseListener() { @Override public void onClosed(Closeable closeable, CloseType type) throws IOException { - if (CloseType.REMOTELY.equals(type)) { + if (isGracefullyFinishResponseOnClose()) { + // Connection was closed. + // This event is fired only for responses, which don't have + // associated transfer-encoding or content-length. + // We have to complete such a request-response processing gracefully. + final Connection c = responseStatus.getResponse() + .getRequest().getConnection(); + final FilterChain fc = (FilterChain) c.getProcessor(); + + fc.fireEventUpstream(c, + new GracefulCloseEvent(HttpTxContext.this), null); + } else if (CloseType.REMOTELY.equals(type)) { abort(AsyncHttpProviderUtils.REMOTELY_CLOSED_EXCEPTION); } } @@ -252,6 +267,12 @@ public void setWebSocket(WebSocket webSocket) { this.webSocket = webSocket; } + private boolean isGracefullyFinishResponseOnClose() { + final HttpResponsePacket response = responseStatus.getResponse(); + return !response.getProcessingState().isKeepAlive() && + !response.isChunked() && response.getContentLength() == -1; + } + // ------------------------------------------------- Package Private Methods public HttpTxContext copy() { 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 4de61df7d7..077437dd7b 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -21,6 +21,10 @@ import org.glassfish.grizzly.http.HttpHeader; import java.io.IOException; +import org.asynchttpclient.providers.grizzly.filters.events.GracefulCloseEvent; +import org.glassfish.grizzly.filterchain.FilterChainEvent; +import org.glassfish.grizzly.filterchain.NextAction; +import org.glassfish.grizzly.http.HttpResponsePacket; /** * Extension of the {@link HttpClientFilter} that is responsible for handling @@ -45,6 +49,28 @@ public AsyncHttpClientEventFilter(final EventHandler eventHandler, final int max this.eventHandler = eventHandler; } + @Override + public NextAction handleEvent(final FilterChainContext ctx, + final FilterChainEvent event) throws IOException { + if (event.type() == GracefulCloseEvent.class) { + // Connection was closed. + // This event is fired only for responses, which don't have + // associated transfer-encoding or content-length. + // We have to complete such a request-response processing gracefully. + final GracefulCloseEvent closeEvent = (GracefulCloseEvent) event; + final HttpResponsePacket response = closeEvent.getHttpTxContext() + .getResponseStatus().getResponse(); + response.getProcessingState().getHttpContext().attach(ctx); + + onHttpPacketParsed(response, ctx); + + return ctx.getStopAction(); + } + + return ctx.getInvokeAction(); + } + + @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 53647c84d4..951a206951 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -299,6 +299,7 @@ private boolean sendAsGrizzlyRequest(final RequestInfoHolder requestInfoHolder, httpCtx.attach(ctx); HttpTxContext.set(ctx, httpTxContext); requestPacketLocal.getProcessingState().setHttpContext(httpCtx); + requestPacketLocal.setConnection(c); return sendRequest(sendingCtx, request, requestPacketLocal); } diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/GracefulCloseEvent.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/GracefulCloseEvent.java new file mode 100644 index 0000000000..35252b610b --- /dev/null +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/GracefulCloseEvent.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2014 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.events; + +import org.asynchttpclient.providers.grizzly.HttpTxContext; +import org.glassfish.grizzly.filterchain.FilterChainEvent; + +/** + * {@link FilterChainEvent} to gracefully complete the request-response processing + * when {@link Connection} is getting closed by the remote host. + * + * @since 1.8.7 + * @author The Grizzly Team + */ +public class GracefulCloseEvent implements FilterChainEvent { + private final HttpTxContext httpTxContext; + + public GracefulCloseEvent(HttpTxContext httpTxContext) { + this.httpTxContext = httpTxContext; + } + + public HttpTxContext getHttpTxContext() { + return httpTxContext; + } + + @Override + public Object type() { + return GracefulCloseEvent.class; + } +} From 441d7188cf8abfb0c911a3235e18a2011e6f5b65 Mon Sep 17 00:00:00 2001 From: oleksiys Date: Thu, 15 May 2014 01:10:57 -0700 Subject: [PATCH 0015/2020] [master] (forgot to add files) fix issue #549 https://github.com/AsyncHttpClient/async-http-client/issues/549 "[grizzly-provider] connection abort, when neither transfer-encoding nor content-length is specified" --- providers/grizzly/pom.xml | 8 +- .../GrizzlyNoTransferEncodingTest.java | 108 ++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java diff --git a/providers/grizzly/pom.xml b/providers/grizzly/pom.xml index decae40aaf..678b60f7c7 100644 --- a/providers/grizzly/pom.xml +++ b/providers/grizzly/pom.xml @@ -14,7 +14,7 @@ - 2.3.11 + 2.3.12 1.1 @@ -39,6 +39,12 @@ grizzly-npn-api ${grizzly.npn.version} + + org.glassfish.grizzly + grizzly-http-server + ${grizzly.version} + test + diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java new file mode 100644 index 0000000000..d3b796fc0e --- /dev/null +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2014 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 java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import org.asynchttpclient.DefaultAsyncHttpClient; +import org.glassfish.grizzly.http.server.HttpHandler; +import org.glassfish.grizzly.http.server.HttpServer; +import org.glassfish.grizzly.http.server.NetworkListener; +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.grizzly.http.server.Response; +import org.testng.Assert; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; +import static org.glassfish.grizzly.http.server.NetworkListener.DEFAULT_NETWORK_HOST; + +public class GrizzlyNoTransferEncodingTest { + private static final String TEST_MESSAGE = "Hello World!"; + + private HttpServer server; + private int port; + // ------------------------------------------------------------------- Setup + + + @BeforeTest + public void setup() throws Exception { + server = new HttpServer(); + final NetworkListener listener = + new NetworkListener("server", + DEFAULT_NETWORK_HOST, + 0); + // disable chunking + listener.setChunkingEnabled(false); + server.addListener(listener); + server.getServerConfiguration().addHttpHandler( + new HttpHandler() { + + @Override + public void service(final Request request, + final Response response) throws Exception { + response.setContentType("plain/text;charset=\"utf-8\""); + // flush to make sure content-length will be missed + response.flush(); + + response.getWriter().write(TEST_MESSAGE); + } + }, "/test"); + + server.start(); + + port = listener.getPort(); + } + + + // --------------------------------------------------------------- Tear Down + + + @AfterTest + public void tearDown() { + server.shutdownNow(); + server = null; + } + + + // ------------------------------------------------------------ Test Methods + + + @Test + public void testNoTransferEncoding() throws Exception { + String url = "http://localhost/" + port + "/test"; + + AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder() + .setCompressionEnabled(true) + .setFollowRedirects(false) + .setConnectionTimeoutInMs(15000) + .setRequestTimeoutInMs(15000) + .setAllowPoolingConnection(false) + .setUseRawUrl(true) + .setIOThreadMultiplier(2) // 2 is default + .build(); + + AsyncHttpClient client = new DefaultAsyncHttpClient( + new GrizzlyAsyncHttpProvider(config), config); + + try { + Future f = client.prepareGet(url).execute(); + org.asynchttpclient.Response r = f.get(10, TimeUnit.SECONDS); + Assert.assertEquals(TEST_MESSAGE, r.getResponseBody()); + } finally { + client.close(); + } + } +} From ce46444e903a4b41906fa7224f0496a1c7a3ff25 Mon Sep 17 00:00:00 2001 From: oleksiys Date: Thu, 15 May 2014 23:06:20 -0700 Subject: [PATCH 0016/2020] [master] fix issue #499 (not all the proposed changes are taken) https://github.com/AsyncHttpClient/async-http-client/pull/499 "NonBlockingFeeder improvements" --- .../grizzly/FeedableBodyGenerator.java | 93 +++++++---- .../GrizzlyFeedableBodyGeneratorTest.java | 147 +++++++++++++++++- 2 files changed, 211 insertions(+), 29 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 cd6e52bb23..1717dcba18 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Sonatype, Inc. All rights reserved. + * Copyright (c) 2012-2014 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. @@ -24,11 +24,14 @@ import org.glassfish.grizzly.OutputSink; import org.glassfish.grizzly.WriteHandler; import org.glassfish.grizzly.WriteResult; +import org.glassfish.grizzly.filterchain.FilterChain; 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.ssl.SSLBaseFilter; +import org.glassfish.grizzly.ssl.SSLFilter; import org.glassfish.grizzly.threadpool.Threads; import org.glassfish.grizzly.utils.Futures; @@ -36,6 +39,8 @@ import java.nio.ByteBuffer; import java.util.concurrent.ExecutionException; +import static org.glassfish.grizzly.ssl.SSLUtils.getSSLEngine; + /** * {@link BodyGenerator} which may return just part of the payload at the time * handler is requesting it. If it happens - PartialBodyGenerator becomes responsible @@ -159,10 +164,14 @@ public synchronized void initializeAsynchronousTransfer(final FilterChainContext @Override public void run() { try { - feeder.flush(); + if (requestPacket.isSecure() && + (getSSLEngine(context.getConnection()) == null)) { + flushOnSSLHandshakeComplete(); + } else { + feeder.flush(); + } } catch (IOException ioe) { - HttpTxContext ctx = HttpTxContext.get(context); - ctx.abort(ioe); + throwError(ioe); } } }; @@ -183,6 +192,36 @@ private boolean isServiceThread() { return Threads.isService(); } + + private void flushOnSSLHandshakeComplete() throws IOException { + final FilterChain filterChain = context.getFilterChain(); + final int idx = filterChain.indexOfType(SSLFilter.class); + assert (idx != -1); + final SSLFilter filter = (SSLFilter) filterChain.get(idx); + final Connection c = context.getConnection(); + filter.addHandshakeListener(new SSLBaseFilter.HandshakeListener() { + public void onStart(Connection connection) { + } + + public void onComplete(Connection connection) { + if (c.equals(connection)) { + filter.removeHandshakeListener(this); + try { + feeder.flush(); + } catch (IOException ioe) { + throwError(ioe); + } + } + } + }); + filter.handshake(context.getConnection(), null); + } + + private void throwError(final Throwable t) { + HttpTxContext httpTxContext = HttpTxContext.get(context); + httpTxContext.abort(t); + } + // ----------------------------------------------------------- Inner Classes private final class EmptyBody implements Body { @@ -410,7 +449,7 @@ public NonBlockingFeeder(final FeedableBodyGenerator feedableBodyGenerator) { * It's important to only invoke {@link #feed(Buffer, boolean)} * once per invocation of {@link #canFeed()}. */ - public abstract void canFeed(); + public abstract void canFeed() throws IOException; /** * @return true if all data has been fed by this feeder, @@ -441,16 +480,15 @@ public NonBlockingFeeder(final FeedableBodyGenerator feedableBodyGenerator) { * {@inheritDoc} */ @Override - public synchronized void flush() { + public synchronized void flush() throws IOException { final HttpContext httpContext = HttpContext.get(feedableBodyGenerator.context); final OutputSink outputSink = httpContext.getOutputSink(); if (isReady()) { - writeUntilFullOrDone(outputSink); + final boolean notReady = writeUntilFullOrDone(outputSink); if (!isDone()) { - if (!isReady()) { + if (notReady) { notifyReadyToFeed(new ReadyToFeedListenerImpl()); - } - if (!outputSink.canWrite()) { + } else { // write queue is full, leverage WriteListener to let us know // when it is safe to write again. outputSink.notifyCanWrite(new WriteHandlerImpl()); @@ -463,15 +501,17 @@ public synchronized void flush() { // ----------------------------------------------------- Private Methods - private void writeUntilFullOrDone(final OutputSink outputSink) { + private boolean writeUntilFullOrDone(final OutputSink outputSink) + throws IOException { while (outputSink.canWrite()) { if (isReady()) { canFeed(); - } - if (!isReady()) { - break; + } else { + return true; } } + + return false; } // ------------------------------------------------------- Inner Classes @@ -505,17 +545,7 @@ private WriteHandlerImpl() { @Override public void onWritePossible() throws Exception { - writeUntilFullOrDone(c); - if (!isDone()) { - if (!isReady()) { - notifyReadyToFeed(new ReadyToFeedListenerImpl()); - } - if (!c.canWrite()) { - // write queue is full, leverage WriteListener to let us know - // when it is safe to write again. - c.notifyCanWrite(this); - } - } + flush(); } @Override @@ -523,8 +553,7 @@ public void onError(Throwable t) { if (!Utils.isSpdyConnection(c)) { c.setMaxAsyncWriteQueueSize(feedableBodyGenerator.origMaxPendingBytes); } - HttpTxContext httpTxContext = HttpTxContext.get(ctx); - httpTxContext.abort(t); + feedableBodyGenerator.throwError(t); } } // END WriteHandlerImpl @@ -535,7 +564,15 @@ private final class ReadyToFeedListenerImpl implements NonBlockingFeeder.ReadyTo @Override public void ready() { - flush(); + try { + flush(); + } catch (IOException e) { + final Connection c = feedableBodyGenerator.context.getConnection(); + if (!Utils.isSpdyConnection(c)) { + c.setMaxAsyncWriteQueueSize(feedableBodyGenerator.origMaxPendingBytes); + } + feedableBodyGenerator.throwError(e); + } } } // END ReadToFeedListenerImpl diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java index b80e6b724e..400ca9192d 100644 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -43,6 +43,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.asynchttpclient.DefaultAsyncHttpClient; +import org.asynchttpclient.providers.grizzly.FeedableBodyGenerator.NonBlockingFeeder; import static org.glassfish.grizzly.http.server.NetworkListener.DEFAULT_NETWORK_HOST; import static org.glassfish.grizzly.memory.MemoryManager.DEFAULT_MEMORY_MANAGER; @@ -114,6 +115,15 @@ public void testSimpleFeederOverSSLMultipleThreads() throws Exception { doSimpleFeeder(true); } + @Test + public void testNonBlockingFeederMultipleThreads() throws Exception { + doNonBlockingFeeder(false); + } + + @Test + public void testNonBlockingFeederOverSSLMultipleThreads() throws Exception { + doNonBlockingFeeder(true); + } // --------------------------------------------------------- Private Methods @@ -220,7 +230,142 @@ public void onThrowable(Throwable t) { } } + private void doNonBlockingFeeder(final boolean secure) { + final int threadCount = 10; + final CountDownLatch latch = new CountDownLatch(threadCount); + final int port = (secure ? SECURE_PORT : NON_SECURE_PORT); + final String scheme = (secure ? "https" : "http"); + final ExecutorService service = Executors.newCachedThreadPool(); + AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder() + .setMaximumConnectionsPerHost(60) + .setMaximumConnectionsTotal(60) + .build(); + final AsyncHttpClient client = + new DefaultAsyncHttpClient(new GrizzlyAsyncHttpProvider(config), config); + final int[] statusCodes = new int[threadCount]; + final int[] totalsReceived = new int[threadCount]; + final Throwable[] errors = new Throwable[threadCount]; + for (int i = 0; i < threadCount; i++) { + final int idx = i; + service.execute(new Runnable() { + @Override + public void run() { + FeedableBodyGenerator generator = + new FeedableBodyGenerator(); + FeedableBodyGenerator.NonBlockingFeeder nonBlockingFeeder = + new FeedableBodyGenerator.NonBlockingFeeder(generator) { + private final Random r = new Random(); + private final InputStream in; + private final byte[] bytesIn = new byte[2048]; + private boolean isDone; + + { + try { + in = new FileInputStream(tempFile); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public void canFeed() throws IOException { + final int read = in.read(bytesIn); + if (read == -1) { + isDone = true; + feed(Buffers.EMPTY_BUFFER, true); + return; + } + + final Buffer b = + Buffers.wrap( + DEFAULT_MEMORY_MANAGER, + bytesIn, + 0, + read); + feed(b, false); + } + + @Override + public boolean isDone() { + return isDone; + } + + @Override + public boolean isReady() { + // simulate real-life usecase, where data could not be ready + return r.nextInt(100) < 80; + } + + @Override + public void notifyReadyToFeed( + final NonBlockingFeeder.ReadyToFeedListener listener) { + service.execute(new Runnable() { + + public void run() { + try { + Thread.sleep(2); + } catch (InterruptedException e) { + } + + listener.ready(); + } + + }); + } + }; + generator.setFeeder(nonBlockingFeeder); + generator.setMaxPendingBytes(10000); + + RequestBuilder builder = new RequestBuilder("POST"); + builder.setUrl(scheme + "://localhost:" + port + "/test"); + builder.setBody(generator); + try { + client.executeRequest(builder.build(), + new AsyncCompletionHandler() { + @Override + public org.asynchttpclient.Response onCompleted(org.asynchttpclient.Response response) + throws Exception { + try { + totalsReceived[idx] = Integer.parseInt(response.getHeader("x-total")); + } catch (Exception e) { + errors[idx] = e; + } + statusCodes[idx] = response.getStatusCode(); + latch.countDown(); + return response; + } + + @Override + public void onThrowable(Throwable t) { + errors[idx] = t; + t.printStackTrace(); + latch.countDown(); + } + }); + } catch (IOException e) { + errors[idx] = e; + latch.countDown(); + } + } + }); + } + + try { + latch.await(1, TimeUnit.MINUTES); + } catch (InterruptedException e) { + fail("Latch interrupted"); + } finally { + service.shutdownNow(); + } + + for (int i = 0; i < threadCount; i++) { + assertEquals(200, statusCodes[i]); + assertNull(errors[i]); + assertEquals(tempFile.length(), totalsReceived[i]); + } + } + private static SSLEngineConfigurator createSSLConfig() throws Exception { final SSLContextConfigurator sslContextConfigurator = From 64140b52a44ae7ea10721b22fe72c4eb32847a0e Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 16 May 2014 15:45:42 +0200 Subject: [PATCH 0017/2020] Fix test: http://google.com/ now replies with 302 instead of 301 --- api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java b/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java index 521ce1d6bc..a6e08a2111 100644 --- a/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java +++ b/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java @@ -119,7 +119,7 @@ public void testGoogleComWithTimeout() throws Exception { try { Response response = c.prepareGet("http://google.com/").execute().get(10, TimeUnit.SECONDS); assertNotNull(response); - assertEquals(response.getStatusCode(), 301); + assertEquals(response.getStatusCode(), 302); } finally { c.close(); } From 1ec309268bbed966214ce1a4f3880f48d0ca25e3 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 16 May 2014 15:47:33 +0200 Subject: [PATCH 0018/2020] Fix test: http://google.com/ now replies with 302 instead of 301 --- .../java/org/asynchttpclient/async/AsyncStreamHandlerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java index 610f521cae..b38319e800 100644 --- a/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java @@ -347,7 +347,7 @@ public void asyncStream301WithBody() throws Exception { c.prepareGet("http://google.com/").execute(new AsyncHandlerAdapter() { public STATE onStatusReceived(HttpResponseStatus status) throws Exception { - assertEquals(301, status.getStatusCode()); + assertEquals(302, status.getStatusCode()); return STATE.CONTINUE; } From d2e455e388100eeb4075c8c338cc7be287373351 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 16 May 2014 16:37:13 +0200 Subject: [PATCH 0019/2020] Clean up damn tests --- .../async/AsyncStreamHandlerTest.java | 272 +++++++++--------- 1 file changed, 129 insertions(+), 143 deletions(-) diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java index b38319e800..80838a4f7e 100644 --- a/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java @@ -18,12 +18,14 @@ import static org.asynchttpclient.async.util.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.BoundRequestBuilder; import org.asynchttpclient.FluentCaseInsensitiveStringsMap; import org.asynchttpclient.HttpResponseBodyPart; import org.asynchttpclient.HttpResponseHeaders; @@ -32,15 +34,12 @@ import org.testng.annotations.Test; import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; import java.util.Locale; -import java.util.Map; import java.util.concurrent.CountDownLatch; 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.AtomicReference; public abstract class AsyncStreamHandlerTest extends AbstractBasicTest { @@ -50,15 +49,15 @@ public abstract class AsyncStreamHandlerTest extends AbstractBasicTest { public void asyncStreamGETTest() throws Exception { final CountDownLatch l = new CountDownLatch(1); AsyncHttpClient c = getAsyncHttpClient(null); + final AtomicReference responseHeaders = new AtomicReference(); + final AtomicReference throwable = new AtomicReference(); try { c.prepareGet(getTargetUrl()).execute(new AsyncHandlerAdapter() { @Override public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { try { - FluentCaseInsensitiveStringsMap h = content.getHeaders(); - assertNotNull(h); - assertEquals(h.getJoinedValue("content-type", ", "), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + responseHeaders.set(content.getHeaders()); return STATE.ABORT; } finally { l.countDown(); @@ -68,7 +67,7 @@ public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { @Override public void onThrowable(Throwable t) { try { - fail("", t); + throwable.set(t); } finally { l.countDown(); } @@ -78,6 +77,12 @@ public void onThrowable(Throwable t) { if (!l.await(5, TimeUnit.SECONDS)) { fail("Timeout out"); } + + FluentCaseInsensitiveStringsMap h = responseHeaders.get(); + assertNotNull(h, "No response headers"); + assertEquals(h.getJoinedValue("content-type", ", "), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET, "Unexpected content-type"); + assertNull(throwable.get(), "Unexpected exception"); + } finally { c.close(); } @@ -85,22 +90,20 @@ public void onThrowable(Throwable t) { @Test(groups = { "standalone", "default_provider" }) public void asyncStreamPOSTTest() throws Exception { - final CountDownLatch l = new CountDownLatch(1); - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - Map> m = new HashMap>(); - m.put("param_1", Arrays.asList("value_1")); + + final AtomicReference responseHeaders = new AtomicReference(); AsyncHttpClient c = getAsyncHttpClient(null); try { - c.preparePost(getTargetUrl()).setParameters(m).execute(new AsyncHandlerAdapter() { + Future f = c.preparePost(getTargetUrl())// + .setHeader("Content-Type", "application/x-www-form-urlencoded")// + .addParameter("param_1", "value_1")// + .execute(new AsyncHandlerAdapter() { private StringBuilder builder = new StringBuilder(); @Override public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - FluentCaseInsensitiveStringsMap h = content.getHeaders(); - assertNotNull(h); - assertEquals(h.getJoinedValue("content-type", ", "), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + responseHeaders.set(content.getHeaders()); return STATE.CONTINUE; } @@ -112,19 +115,16 @@ public STATE onBodyPartReceived(HttpResponseBodyPart content) throws Exception { @Override public String onCompleted() throws Exception { - try { - String r = builder.toString().trim(); - assertEquals(r, RESPONSE); - return r; - } finally { - l.countDown(); - } + return builder.toString().trim(); } }); - if (!l.await(10, TimeUnit.SECONDS)) { - fail("Timeout out"); - } + String responseBody = f.get(10, TimeUnit.SECONDS); + FluentCaseInsensitiveStringsMap h = responseHeaders.get(); + assertNotNull(h); + assertEquals(h.getJoinedValue("content-type", ", "), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + assertEquals(responseBody, RESPONSE); + } finally { c.close(); } @@ -133,44 +133,44 @@ public String onCompleted() throws Exception { @Test(groups = { "standalone", "default_provider" }) public void asyncStreamInterruptTest() throws Exception { final CountDownLatch l = new CountDownLatch(1); - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - Map> m = new HashMap>(); - m.put("param_1", Arrays.asList("value_1")); - - final AtomicBoolean a = new AtomicBoolean(true); AsyncHttpClient c = getAsyncHttpClient(null); + + final AtomicReference responseHeaders = new AtomicReference(); + final AtomicBoolean bodyReceived = new AtomicBoolean(false); + final AtomicReference throwable = new AtomicReference(); try { - c.preparePost(getTargetUrl()).setParameters(m).execute(new AsyncHandlerAdapter() { + c.preparePost(getTargetUrl())// + .setHeader("Content-Type", "application/x-www-form-urlencoded")// + .addParameter("param_1", "value_1")// + .execute(new AsyncHandlerAdapter() { @Override public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - FluentCaseInsensitiveStringsMap h = content.getHeaders(); - assertNotNull(h); - assertEquals(h.getJoinedValue("content-type", ", ").toLowerCase(Locale.ENGLISH), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + responseHeaders.set(content.getHeaders()); return STATE.ABORT; } @Override public STATE onBodyPartReceived(final HttpResponseBodyPart content) throws Exception { - a.set(false); - fail("Interrupted not working"); + bodyReceived.set(true); return STATE.ABORT; } @Override public void onThrowable(Throwable t) { - try { - fail("", t); - } finally { - l.countDown(); - } + throwable.set(t); + l.countDown(); } }); l.await(5, TimeUnit.SECONDS); - assertTrue(a.get()); + assertTrue(!bodyReceived.get(), "Interrupted not working"); + FluentCaseInsensitiveStringsMap h = responseHeaders.get(); + assertNotNull(h, "Should receive non null headers"); + assertEquals(h.getJoinedValue("content-type", ", ").toLowerCase(Locale.ENGLISH), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET.toLowerCase(Locale.ENGLISH), "Unexpected content-type"); + assertNull(throwable.get(), "Should get an exception"); + } finally { c.close(); } @@ -178,18 +178,16 @@ public void onThrowable(Throwable t) { @Test(groups = { "standalone", "default_provider" }) public void asyncStreamFutureTest() throws Exception { - Map> m = new HashMap>(); - m.put("param_1", Arrays.asList("value_1")); AsyncHttpClient c = getAsyncHttpClient(null); + final AtomicReference responseHeaders = new AtomicReference(); + final AtomicReference throwable = new AtomicReference(); try { - Future f = c.preparePost(getTargetUrl()).setParameters(m).execute(new AsyncHandlerAdapter() { + Future f = c.preparePost(getTargetUrl()).addParameter("param_1", "value_1").execute(new AsyncHandlerAdapter() { private StringBuilder builder = new StringBuilder(); @Override public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - FluentCaseInsensitiveStringsMap h = content.getHeaders(); - assertNotNull(h); - assertEquals(h.getJoinedValue("content-type", ", "), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + responseHeaders.set(content.getHeaders()); return STATE.CONTINUE; } @@ -201,24 +199,23 @@ public STATE onBodyPartReceived(HttpResponseBodyPart content) throws Exception { @Override public String onCompleted() throws Exception { - String r = builder.toString().trim(); - assertEquals(r, RESPONSE); - return r; + return builder.toString().trim(); } @Override public void onThrowable(Throwable t) { - fail("", t); + throwable.set(t); } }); - try { - String r = f.get(5, TimeUnit.SECONDS); - assertNotNull(r); - assertEquals(r.trim(), RESPONSE); - } catch (TimeoutException ex) { - fail(); - } + String responseBody = f.get(5, TimeUnit.SECONDS); + FluentCaseInsensitiveStringsMap h = responseHeaders.get(); + assertNotNull(h, "Should receive non null headers"); + assertEquals(h.getJoinedValue("content-type", ", ").toLowerCase(Locale.ENGLISH), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET.toLowerCase(Locale.ENGLISH), "Unexpected content-type"); + assertNotNull(responseBody, "No response body"); + assertEquals(responseBody.trim(), RESPONSE, "Unexpected response body"); + assertNull(throwable.get(), "Unexpected exception"); + } finally { c.close(); } @@ -259,22 +256,20 @@ public void onThrowable(Throwable t) { @Test(groups = { "standalone", "default_provider" }) public void asyncStreamReusePOSTTest() throws Exception { - final CountDownLatch l = new CountDownLatch(1); - FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); - h.add("Content-Type", "application/x-www-form-urlencoded"); - Map> m = new HashMap>(); - m.put("param_1", Arrays.asList("value_1")); AsyncHttpClient c = getAsyncHttpClient(null); + final AtomicReference responseHeaders = new AtomicReference(); try { - c.preparePost(getTargetUrl()).setParameters(m).execute(new AsyncHandlerAdapter() { + BoundRequestBuilder rb = c.preparePost(getTargetUrl())// + .setHeader("Content-Type", "application/x-www-form-urlencoded") + .addParameter("param_1", "value_1"); + + Future f = rb.execute(new AsyncHandlerAdapter() { private StringBuilder builder = new StringBuilder(); @Override public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - FluentCaseInsensitiveStringsMap h = content.getHeaders(); - assertNotNull(h); - assertEquals(h.getJoinedValue("content-type", ", "), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + responseHeaders.set(content.getHeaders()); return STATE.CONTINUE; } @@ -286,30 +281,26 @@ public STATE onBodyPartReceived(HttpResponseBodyPart content) throws Exception { @Override public String onCompleted() throws Exception { - try { - String r = builder.toString().trim(); - assertEquals(r, RESPONSE); - return r; - } finally { - l.countDown(); - } - + return builder.toString(); } }); - if (!l.await(20, TimeUnit.SECONDS)) { - fail("Timeout out"); - } + String r = f.get(5, TimeUnit.SECONDS); + FluentCaseInsensitiveStringsMap h = responseHeaders.get(); + assertNotNull(h, "Should receive non null headers"); + assertEquals(h.getJoinedValue("content-type", ", ").toLowerCase(Locale.ENGLISH), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET.toLowerCase(Locale.ENGLISH), "Unexpected content-type"); + assertNotNull(r, "No response body"); + assertEquals(r.trim(), RESPONSE, "Unexpected response body"); + + responseHeaders.set(null); // Let do the same again - c.preparePost(getTargetUrl()).setParameters(m).execute(new AsyncHandlerAdapter() { + f = rb.execute(new AsyncHandlerAdapter() { private StringBuilder builder = new StringBuilder(); @Override public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - FluentCaseInsensitiveStringsMap h = content.getHeaders(); - assertNotNull(h); - assertEquals(h.getJoinedValue("content-type", ", "), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET); + responseHeaders.set(content.getHeaders()); return STATE.CONTINUE; } @@ -321,41 +312,37 @@ public STATE onBodyPartReceived(HttpResponseBodyPart content) throws Exception { @Override public String onCompleted() throws Exception { - try { - String r = builder.toString().trim(); - assertEquals(r, RESPONSE); - return r; - } finally { - l.countDown(); - } + return builder.toString(); } }); - if (!l.await(20, TimeUnit.SECONDS)) { - fail("Timeout out"); - } + f.get(5, TimeUnit.SECONDS); + h = responseHeaders.get(); + assertNotNull(h, "Should receive non null headers"); + assertEquals(h.getJoinedValue("content-type", ", ").toLowerCase(Locale.ENGLISH), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET.toLowerCase(Locale.ENGLISH), "Unexpected content-type"); + assertNotNull(r, "No response body"); + assertEquals(r.trim(), RESPONSE, "Unexpected response body"); } finally { c.close(); } } @Test(groups = { "online", "default_provider" }) - public void asyncStream301WithBody() throws Exception { - final CountDownLatch l = new CountDownLatch(1); + public void asyncStream302WithBody() throws Exception { AsyncHttpClient c = getAsyncHttpClient(null); + final AtomicReference statusCode = new AtomicReference(0); + final AtomicReference headers = new AtomicReference(); try { - c.prepareGet("http://google.com/").execute(new AsyncHandlerAdapter() { + Future f = c.prepareGet("http://google.com/").execute(new AsyncHandlerAdapter() { public STATE onStatusReceived(HttpResponseStatus status) throws Exception { - assertEquals(302, status.getStatusCode()); + statusCode.set(status.getStatusCode()); return STATE.CONTINUE; } @Override public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - FluentCaseInsensitiveStringsMap h = content.getHeaders(); - assertNotNull(h); - assertEquals(h.getJoinedValue("content-type", ", ").toLowerCase(Locale.ENGLISH), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET.toLowerCase(Locale.ENGLISH)); + headers.set(content.getHeaders()); return STATE.CONTINUE; } @@ -366,57 +353,59 @@ public STATE onBodyPartReceived(HttpResponseBodyPart content) throws Exception { @Override public String onCompleted() throws Exception { - l.countDown(); return null; } }); - if (!l.await(20, TimeUnit.SECONDS)) { - fail("Timeout out"); - } + f.get(20, TimeUnit.SECONDS); + assertEquals(statusCode.get().intValue(), 302); + FluentCaseInsensitiveStringsMap h = headers.get(); + assertNotNull(h); + assertEquals(h.getJoinedValue("content-type", ", ").toLowerCase(Locale.ENGLISH), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET.toLowerCase(Locale.ENGLISH)); + } finally { c.close(); } } @Test(groups = { "online", "default_provider" }) - public void asyncStream301RedirectWithBody() throws Exception { - final CountDownLatch l = new CountDownLatch(1); + public void asyncStream302RedirectWithBody() throws Exception { AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirects(true).build()); + final AtomicReference statusCode = new AtomicReference(0); + final AtomicReference responseHeaders = new AtomicReference(); try { - c.prepareGet("http://google.com/").execute(new AsyncHandlerAdapter() { + Future f = c.prepareGet("http://google.com/").execute(new AsyncHandlerAdapter() { public STATE onStatusReceived(HttpResponseStatus status) throws Exception { - assertTrue(status.getStatusCode() != 301); + statusCode.set(status.getStatusCode()); return STATE.CONTINUE; } @Override public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - FluentCaseInsensitiveStringsMap h = content.getHeaders(); - assertNotNull(h); - assertEquals(h.getFirstValue("server"), "gws"); - // This assertion below is not an invariant, since implicitly contains locale-dependant settings - // and fails when run in country having own localized Google site and it's locale relies on something - // other than ISO-8859-1. - // In Hungary for example, http://google.com/ redirects to http://www.google.hu/, a localized - // Google site, that uses ISO-8892-2 encoding (default for HU). Similar is true for other - // non-ISO-8859-1 using countries that have "localized" google, like google.hr, google.rs, google.cz, google.sk etc. - // - // assertEquals(h.getJoinedValue("content-type", ", "), "text/html; charset=ISO-8859-1"); + responseHeaders.set(content.getHeaders()); return STATE.CONTINUE; } @Override public String onCompleted() throws Exception { - l.countDown(); return null; } }); - if (!l.await(20, TimeUnit.SECONDS)) { - fail("Timeout out"); - } + f.get(20, TimeUnit.SECONDS); + assertTrue(statusCode.get() != 302); + FluentCaseInsensitiveStringsMap h = responseHeaders.get(); + assertNotNull(h); + assertEquals(h.getFirstValue("server"), "gws"); + // This assertion below is not an invariant, since implicitly contains locale-dependant settings + // and fails when run in country having own localized Google site and it's locale relies on something + // other than ISO-8859-1. + // In Hungary for example, http://google.com/ redirects to http://www.google.hu/, a localized + // Google site, that uses ISO-8892-2 encoding (default for HU). Similar is true for other + // non-ISO-8859-1 using countries that have "localized" google, like google.hr, google.rs, google.cz, google.sk etc. + // + // assertEquals(h.getJoinedValue("content-type", ", "), "text/html; charset=ISO-8859-1"); } finally { c.close(); } @@ -493,37 +482,34 @@ public Integer onCompleted() throws Exception { @Test(groups = { "online", "default_provider" }) public void asyncOptionsTest() throws Exception { - final CountDownLatch l = new CountDownLatch(1); AsyncHttpClient c = getAsyncHttpClient(null); + final AtomicReference responseHeaders = new AtomicReference(); + try { final String[] expected = { "GET", "HEAD", "OPTIONS", "POST", "TRACE" }; - c.prepareOptions("http://www.apache.org/").execute(new AsyncHandlerAdapter() { + Future f = c.prepareOptions("http://www.apache.org/").execute(new AsyncHandlerAdapter() { @Override public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - FluentCaseInsensitiveStringsMap h = content.getHeaders(); - assertNotNull(h); - String[] values = h.get("Allow").get(0).split(",|, "); - assertNotNull(values); - assertEquals(values.length, expected.length); - Arrays.sort(values); - assertEquals(values, expected); + responseHeaders.set(content.getHeaders()); return STATE.ABORT; } @Override public String onCompleted() throws Exception { - try { - return "OK"; - } finally { - l.countDown(); - } + return "OK"; } }); - if (!l.await(20, TimeUnit.SECONDS)) { - fail("Timeout out"); - } + f.get(20, TimeUnit.SECONDS) ; + FluentCaseInsensitiveStringsMap h = responseHeaders.get(); + assertNotNull(h); + String[] values = h.get("Allow").get(0).split(",|, "); + assertNotNull(values); + assertEquals(values.length, expected.length); + Arrays.sort(values); + assertEquals(values, expected); + } finally { c.close(); } From 1e9ae842ca94f326215358917c620ac407323c81 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 20 May 2014 11:20:25 +0200 Subject: [PATCH 0020/2020] Make SignatureCalculator available on RequestBuilderBase, close #557 --- .../asynchttpclient/BoundRequestBuilder.java | 31 ++----------------- .../asynchttpclient/RequestBuilderBase.java | 31 +++++++++++++++++++ 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/BoundRequestBuilder.java b/api/src/main/java/org/asynchttpclient/BoundRequestBuilder.java index 7dacfb6cd3..b42f2e2eae 100644 --- a/api/src/main/java/org/asynchttpclient/BoundRequestBuilder.java +++ b/api/src/main/java/org/asynchttpclient/BoundRequestBuilder.java @@ -24,18 +24,6 @@ public class BoundRequestBuilder extends RequestBuilderBase private final AsyncHttpClient client; - /** - * Calculator used for calculating request signature for the request being - * built, if any. - */ - protected SignatureCalculator signatureCalculator; - - /** - * URL used as the base, not including possibly query parameters. Needed for - * signature calculation - */ - protected String baseURL; - public BoundRequestBuilder(AsyncHttpClient client, String reqType, boolean useRawUrl) { super(BoundRequestBuilder.class, reqType, useRawUrl); this.client = client; @@ -85,18 +73,6 @@ public BoundRequestBuilder addQueryParameter(String name, String value) { @Override public Request build() { - /* Let's first calculate and inject signature, before finalizing actual build - * (order does not matter with current implementation but may in future) - */ - if (signatureCalculator != null) { - String url = baseURL; - // Should not include query parameters, ensure: - int i = url.indexOf('?'); - if (i >= 0) { - url = url.substring(0, i); - } - signatureCalculator.calculateAndAddSignature(url, request, this); - } return super.build(); } @@ -142,7 +118,6 @@ public BoundRequestBuilder setParameters(FluentStringsMap parameters) { @Override public BoundRequestBuilder setUrl(String url) { - baseURL = url; return super.setUrl(url); } @@ -151,8 +126,8 @@ public BoundRequestBuilder setVirtualHost(String virtualHost) { return super.setVirtualHost(virtualHost); } + @Override public BoundRequestBuilder setSignatureCalculator(SignatureCalculator signatureCalculator) { - this.signatureCalculator = signatureCalculator; - return this; + return super.setSignatureCalculator(signatureCalculator); } -} \ No newline at end of file +} diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java index cd4cd2df1d..3b8fbfe04c 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -357,6 +357,17 @@ public boolean isUseRawUrl() { private final Class derived; protected final RequestImpl request; protected boolean useRawUrl = false; + /** + * Calculator used for calculating request signature for the request being + * built, if any. + */ + protected SignatureCalculator signatureCalculator; + + /** + * URL used as the base, not including possibly query parameters. Needed for + * signature calculation + */ + protected String baseURL; protected RequestBuilderBase(Class derived, String method, boolean rawUrls) { this.derived = derived; @@ -372,6 +383,7 @@ protected RequestBuilderBase(Class derived, Request prototype) { } public T setUrl(String url) { + baseURL = url; return setURI(URI.create(url)); } @@ -424,6 +436,11 @@ public T setVirtualHost(String virtualHost) { return derived.cast(this); } + public T setSignatureCalculator(SignatureCalculator signatureCalculator) { + this.signatureCalculator = signatureCalculator; + return derived.cast(this); + } + public T setHeader(String name, String value) { request.getHeaders().replace(name, value); return derived.cast(this); @@ -615,6 +632,20 @@ public T setConnectionPoolKeyStrategy(ConnectionPoolKeyStrategy connectionPoolKe } public Request build() { + + /* Let's first calculate and inject signature, before finalizing actual build + * (order does not matter with current implementation but may in future) + */ + if (signatureCalculator != null) { + String url = baseURL != null? baseURL : request.originalUri.toString(); + // Should not include query parameters, ensure: + int i = url.indexOf('?'); + if (i >= 0) { + url = url.substring(0, i); + } + signatureCalculator.calculateAndAddSignature(url, request, this); + } + if (request.length < 0 && request.streamData == null) { // can't concatenate content-length String contentLength = null; From 04e256ce9571e4239403b657126ce8eb30ad6776 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 20 May 2014 15:38:45 +0200 Subject: [PATCH 0021/2020] Introduce Realm useAbsoluteURI and omitQuery, close #553 --- .../main/java/org/asynchttpclient/Realm.java | 52 +++++++++++++++---- .../providers/netty/handler/HttpProtocol.java | 25 ++++++++- 2 files changed, 67 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/Realm.java b/api/src/main/java/org/asynchttpclient/Realm.java index cf2e07a3f6..3214651fac 100644 --- a/api/src/main/java/org/asynchttpclient/Realm.java +++ b/api/src/main/java/org/asynchttpclient/Realm.java @@ -50,6 +50,8 @@ public class Realm { private final boolean messageType2Received; private final String domain; private final Charset charset; + private final boolean useAbsoluteURI; + private final boolean omitQuery; public enum AuthScheme { DIGEST, BASIC, NTLM, SPNEGO, KERBEROS, NONE @@ -57,7 +59,7 @@ public enum AuthScheme { private Realm(AuthScheme scheme, String principal, String password, String realmName, String nonce, String algorithm, String response, String qop, String nc, String cnonce, String uri, String method, boolean usePreemptiveAuth, String domain, String enc, - String host, boolean messageType2Received, String opaque) { + String host, boolean messageType2Received, String opaque, boolean useAbsoluteURI, boolean omitQuery) { this.principal = principal; this.password = password; @@ -78,6 +80,8 @@ private Realm(AuthScheme scheme, String principal, String password, String realm this.host = host; this.messageType2Received = messageType2Received; this.charset = enc != null ? Charset.forName(enc) : null; + this.useAbsoluteURI = useAbsoluteURI; + this.omitQuery = omitQuery; } public String getPrincipal() { @@ -176,6 +180,14 @@ public boolean isNtlmMessageType2Received() { return messageType2Received; } + public boolean isUseAbsoluteURI() { + return useAbsoluteURI; + } + + public boolean isOmitQuery() { + return omitQuery; + } + @Override public boolean equals(Object o) { if (this == o) @@ -207,7 +219,8 @@ public boolean equals(Object o) { return false; if (uri != null ? !uri.equals(realm.uri) : realm.uri != null) return false; - + if (useAbsoluteURI != !realm.useAbsoluteURI) return false; + if (omitQuery != !realm.omitQuery) return false; return true; } @@ -215,7 +228,8 @@ public boolean equals(Object o) { public String toString() { return "Realm{" + "principal='" + principal + '\'' + ", scheme=" + scheme + ", realmName='" + realmName + '\'' + ", nonce='" + nonce + '\'' + ", algorithm='" + algorithm + '\'' + ", response='" + response + '\'' + ", qop='" + qop + '\'' + ", nc='" - + nc + '\'' + ", cnonce='" + cnonce + '\'' + ", uri='" + uri + '\'' + ", methodName='" + methodName + '\'' + '}'; + + nc + '\'' + ", cnonce='" + cnonce + '\'' + ", uri='" + uri + '\'' + ", methodName='" + methodName + '\'' + ", useAbsoluteURI='" + useAbsoluteURI + '\'' + + ", omitQuery='" + omitQuery + '\'' +'}'; } @Override @@ -261,6 +275,8 @@ public static class RealmBuilder { private String enc = StandardCharsets.UTF_8.name(); private String host = "localhost"; private boolean messageType2Received = false; + private boolean useAbsoluteURI = true; + private boolean omitQuery = false; public String getNtlmDomain() { return domain; @@ -397,6 +413,29 @@ public RealmBuilder setUsePreemptiveAuth(boolean usePreemptiveAuth) { return this; } + public RealmBuilder setNtlmMessageType2Received(boolean messageType2Received) { + this.messageType2Received = messageType2Received; + return this; + } + + public boolean isUseAbsoluteURI() { + return useAbsoluteURI; + } + + public RealmBuilder setUseAbsoluteURI(boolean useAbsoluteURI) { + this.useAbsoluteURI = useAbsoluteURI; + return this; + } + + public boolean isOmitQuery() { + return omitQuery; + } + + public RealmBuilder setOmitQuery(boolean omitQuery) { + this.omitQuery = omitQuery; + return this; + } + public RealmBuilder parseWWWAuthenticateHeader(String headerLine) { setRealmName(match(headerLine, "realm")); setNonce(match(headerLine, "nonce")); @@ -427,11 +466,6 @@ public RealmBuilder parseProxyAuthenticateHeader(String headerLine) { return this; } - public RealmBuilder setNtlmMessageType2Received(boolean messageType2Received) { - this.messageType2Received = messageType2Received; - return this; - } - public RealmBuilder clone(Realm clone) { setRealmName(clone.getRealmName()); setAlgorithm(clone.getAlgorithm()); @@ -574,7 +608,7 @@ public Realm build() { } return new Realm(scheme, principal, password, realmName, nonce, algorithm, response, qop, nc, cnonce, uri, methodName, - usePreemptive, domain, enc, host, messageType2Received, opaque); + usePreemptive, domain, enc, host, messageType2Received, opaque, useAbsoluteURI, omitQuery); } } } 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 032a3d9ae1..0845fe9691 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 @@ -61,6 +61,7 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; +import java.net.URISyntaxException; import java.util.List; final class HttpProtocol extends Protocol { @@ -202,6 +203,27 @@ private void markAsDone(NettyResponseFuture future, final Channel channel) th } } + private final String computeRealmURI(Realm realm, URI requestURI) throws URISyntaxException { + if (realm.isUseAbsoluteURI()) { + if (realm.isOmitQuery() && isNonEmpty(requestURI.getQuery())) { + return new URI( + requestURI.getScheme(), + requestURI.getAuthority(), + requestURI.getPath(), + null, + null).toString(); + } else { + return requestURI.toString(); + } + } else { + if (realm.isOmitQuery() && isNonEmpty(requestURI.getQuery())) { + return requestURI.getPath(); + } else { + return requestURI.getPath() + "?" + requestURI.getQuery(); + } + } + } + private boolean handleUnauthorizedAndExit(int statusCode, Realm realm, final Request request, HttpResponse response, final NettyResponseFuture future, ProxyServer proxyServer, final Channel channel) throws Exception { if (statusCode == UNAUTHORIZED.code() && realm != null) { @@ -227,7 +249,8 @@ private boolean handleUnauthorizedAndExit(int statusCode, Realm realm, final Req .parseWWWAuthenticateHeader(authenticateHeaders.get(0)).build(); } - Realm nr = new Realm.RealmBuilder().clone(newRealm).setUri(request.getURI().getPath()).build(); + String realmURI = computeRealmURI(newRealm, request.getURI()); + Realm nr = new Realm.RealmBuilder().clone(newRealm).setUri(realmURI).build(); final Request nextRequest = new RequestBuilder(future.getRequest()).setHeaders(request.getHeaders()).setRealm(nr).build(); LOGGER.debug("Sending authentication to {}", request.getUrl()); From 3c56c7f883a0c39196cf11d1d190811331e9b9d6 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 20 May 2014 16:07:09 +0200 Subject: [PATCH 0022/2020] Advertise 1.8.9 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b27e4180d..5a09dc329a 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.8.8 + 1.8.9 ``` From e84ee123f57958fc0cd8f7f2b1df195ec68f94d1 Mon Sep 17 00:00:00 2001 From: oleksiys Date: Tue, 20 May 2014 22:43:55 -0700 Subject: [PATCH 0023/2020] [master] + fix the issue #559 https://github.com/AsyncHttpClient/async-http-client/issues/559 GrizzlyAsyncHttpProvider hangs on Exception #559 --- .../providers/grizzly/EventHandler.java | 10 +++++++--- .../grizzly/filters/AsyncHttpClientEventFilter.java | 5 +++++ .../grizzly/filters/AsyncSpdyClientEventFilter.java | 8 +++++++- 3 files changed, 19 insertions(+), 4 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 fe956e4d79..6f7a6280df 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 @@ -196,10 +196,14 @@ public void onInitialLineParsed(HttpHeader httpHeader, FilterChainContext ctx) { public void onHttpHeaderError(final HttpHeader httpHeader, final FilterChainContext ctx, final Throwable t) { - t.printStackTrace(); httpHeader.setSkipRemainder(true); - final HttpTxContext context = HttpTxContext.get(ctx); - context.abort(t); + HttpTxContext.get(ctx).abort(t); + } + + public void onHttpContentError(final HttpHeader httpHeader, final FilterChainContext ctx, final Throwable t) { + + httpHeader.setSkipRemainder(true); + HttpTxContext.get(ctx).abort(t); } @SuppressWarnings({ "unchecked" }) 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 077437dd7b..6afb87197a 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 @@ -101,6 +101,11 @@ protected void onHttpHeaderError(HttpHeader httpHeader, FilterChainContext ctx, eventHandler.onHttpHeaderError(httpHeader, ctx, t); } + @Override + protected void onHttpContentError(HttpHeader httpHeader, FilterChainContext ctx, Throwable t) throws IOException { + eventHandler.onHttpContentError(httpHeader, ctx, t); + } + @Override protected void onHttpHeadersParsed(HttpHeader httpHeader, FilterChainContext ctx) { eventHandler.onHttpHeadersParsed(httpHeader, ctx); diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncSpdyClientEventFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncSpdyClientEventFilter.java index 98a5f823b3..758c6e7783 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncSpdyClientEventFilter.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/AsyncSpdyClientEventFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -72,6 +72,12 @@ protected void onHttpHeaderError(HttpHeader httpHeader, FilterChainContext ctx, eventHandler.onHttpHeaderError(httpHeader, ctx, t); } + @Override + protected void onHttpContentError(HttpHeader httpHeader, FilterChainContext ctx, Throwable t) throws IOException { + eventHandler.onHttpContentError(httpHeader, ctx, t); + } + + @Override protected void onHttpHeadersParsed(HttpHeader httpHeader, FilterChainContext ctx) { eventHandler.onHttpHeadersParsed(httpHeader, ctx); From fc71f653a056f2bf4404b3d3934d446ec69535fe Mon Sep 17 00:00:00 2001 From: oleksiys Date: Thu, 22 May 2014 18:12:34 -0700 Subject: [PATCH 0024/2020] [master] + minor updates --- .../providers/grizzly/EventHandler.java | 3 +-- .../providers/grizzly/HttpTxContext.java | 15 ++++++++++----- 2 files changed, 11 insertions(+), 7 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 6f7a6280df..ec251b8955 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 @@ -379,8 +379,7 @@ private static boolean isRedirectAllowed(final HttpTxContext ctx) { private static HttpTxContext cleanup(final FilterChainContext ctx) { final Connection c = ctx.getConnection(); - final HttpTxContext context = HttpTxContext.get(ctx); - HttpTxContext.remove(ctx, context); + final HttpTxContext context = HttpTxContext.remove(ctx); 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/HttpTxContext.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/HttpTxContext.java index 72755014c7..a12fc65748 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 @@ -69,7 +69,8 @@ public final class HttpTxContext { private final CloseListener listener = new CloseListener() { @Override public void onClosed(Closeable closeable, CloseType type) throws IOException { - if (isGracefullyFinishResponseOnClose()) { + if (responseStatus != null && // responseStatus==null if request wasn't even sent + isGracefullyFinishResponseOnClose()) { // Connection was closed. // This event is fired only for responses, which don't have // associated transfer-encoding or content-length. @@ -107,10 +108,14 @@ public static void set(final FilterChainContext ctx, final HttpTxContext httpTxC REQUEST_STATE_ATTR.set(httpContext, httpTxContext); } - 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 remove(final FilterChainContext ctx) { + final HttpContext httpContext = HttpContext.get(ctx); + final HttpTxContext httpTxContext = REQUEST_STATE_ATTR.remove(httpContext); + if (httpTxContext != null) { + httpContext.getCloseable().removeCloseListener(httpTxContext.listener); + } + + return httpTxContext; } public static HttpTxContext get(FilterChainContext ctx) { From 7dae1e8a2aa9fa7c00a0f66f5afbb014440e3368 Mon Sep 17 00:00:00 2001 From: oleksiys Date: Tue, 27 May 2014 13:09:52 -0700 Subject: [PATCH 0025/2020] [master] + integrate Grizzly 2.3.13 --- 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 678b60f7c7..9de0ff62a0 100644 --- a/providers/grizzly/pom.xml +++ b/providers/grizzly/pom.xml @@ -14,7 +14,7 @@ - 2.3.12 + 2.3.13 1.1 From aaaced7a2e975dce073863ef6e7c8292c216e241 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 3 Jun 2014 11:14:54 +0200 Subject: [PATCH 0026/2020] Port #563 on master --- .../providers/netty/handler/HttpProtocol.java | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 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 0845fe9691..57f81b6142 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 @@ -78,7 +78,7 @@ private Realm.RealmBuilder newRealmBuilder(Realm realm) { } private Realm kerberosChallenge(List proxyAuth, Request request, ProxyServer proxyServer, - FluentCaseInsensitiveStringsMap headers, Realm realm, NettyResponseFuture future) throws NTLMEngineException { + FluentCaseInsensitiveStringsMap headers, Realm realm, NettyResponseFuture future, boolean proxyInd) throws NTLMEngineException { URI uri = request.getURI(); String host = request.getVirtualHost() == null ? AsyncHttpProviderUtils.getHost(uri) : request.getVirtualHost(); @@ -96,19 +96,23 @@ private Realm kerberosChallenge(List proxyAuth, Request request, ProxySe } catch (Throwable throwable) { if (isNTLM(proxyAuth)) { - return ntlmChallenge(proxyAuth, request, proxyServer, headers, realm, future); + return ntlmChallenge(proxyAuth, request, proxyServer, headers, realm, future, proxyInd); } channels.abort(future, throwable); return null; } } - private void addNTLMAuthorizationHeader(FluentCaseInsensitiveStringsMap headers, String challengeHeader) { - headers.add(HttpHeaders.Names.AUTHORIZATION, "NTLM " + challengeHeader); + private String authorizationHeaderName(boolean proxyInd) { + return proxyInd? HttpHeaders.Names.PROXY_AUTHORIZATION: HttpHeaders.Names.AUTHORIZATION; + } + + private void addNTLMAuthorizationHeader(FluentCaseInsensitiveStringsMap headers, String challengeHeader, boolean proxyInd) { + headers.add(authorizationHeaderName(proxyInd), "NTLM " + challengeHeader); } private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, - Realm realm, NettyResponseFuture future) throws NTLMEngineException { + Realm realm, NettyResponseFuture future, boolean proxyInd) throws NTLMEngineException { boolean useRealm = proxyServer == null && realm != null; @@ -121,7 +125,7 @@ private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer p String challengeHeader = NTLMEngine.INSTANCE.generateType1Msg(ntlmDomain, ntlmHost); URI uri = request.getURI(); - addNTLMAuthorizationHeader(headers, challengeHeader); + addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd); future.getAndSetAuth(false); return newRealmBuilder(realm)// .setScheme(realm.getAuthScheme())// @@ -131,7 +135,7 @@ private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer p .build(); } else { - addType3NTLMAuthorizationHeader(wwwAuth, headers, principal, password, ntlmDomain, ntlmHost); + addType3NTLMAuthorizationHeader(wwwAuth, headers, principal, password, ntlmDomain, ntlmHost, proxyInd); Realm.AuthScheme authScheme = realm != null ? realm.getAuthScheme() : Realm.AuthScheme.NTLM; return newRealmBuilder(realm)// .setScheme(authScheme)// @@ -142,12 +146,12 @@ private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer p } private Realm ntlmProxyChallenge(List wwwAuth, Request request, ProxyServer proxyServer, - FluentCaseInsensitiveStringsMap headers, Realm realm, NettyResponseFuture future) throws NTLMEngineException { + FluentCaseInsensitiveStringsMap headers, Realm realm, NettyResponseFuture future, boolean proxyInd) throws NTLMEngineException { future.getAndSetAuth(false); headers.remove(HttpHeaders.Names.PROXY_AUTHORIZATION); addType3NTLMAuthorizationHeader(wwwAuth, headers, proxyServer.getPrincipal(), proxyServer.getPassword(), - proxyServer.getNtlmDomain(), proxyServer.getHost()); + proxyServer.getNtlmDomain(), proxyServer.getHost(), proxyInd); return newRealmBuilder(realm)// // .setScheme(realm.getAuthScheme()) @@ -156,13 +160,13 @@ private Realm ntlmProxyChallenge(List wwwAuth, Request request, ProxySer } private void addType3NTLMAuthorizationHeader(List auth, FluentCaseInsensitiveStringsMap headers, String username, - String password, String domain, String workstation) throws NTLMEngineException { - headers.remove(HttpHeaders.Names.AUTHORIZATION); + String password, String domain, String workstation, boolean proxyInd) throws NTLMEngineException { + headers.remove(authorizationHeaderName(proxyInd)); if (isNonEmpty(auth) && auth.get(0).startsWith("NTLM ")) { String serverChallenge = auth.get(0).trim().substring("NTLM ".length()); String challengeHeader = NTLMEngine.INSTANCE.generateType3Msg(username, password, domain, workstation, serverChallenge); - addNTLMAuthorizationHeader(headers, challengeHeader); + addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd); } } @@ -236,10 +240,10 @@ private boolean handleUnauthorizedAndExit(int statusCode, Realm realm, final Req // NTLM boolean negociate = authenticateHeaders.contains("Negotiate"); if (!authenticateHeaders.contains("Kerberos") && (isNTLM(authenticateHeaders) || negociate)) { - newRealm = ntlmChallenge(authenticateHeaders, request, proxyServer, request.getHeaders(), realm, future); + newRealm = ntlmChallenge(authenticateHeaders, request, proxyServer, request.getHeaders(), realm, future, false); // SPNEGO KERBEROS } else if (negociate) { - newRealm = kerberosChallenge(authenticateHeaders, request, proxyServer, request.getHeaders(), realm, future); + newRealm = kerberosChallenge(authenticateHeaders, request, proxyServer, request.getHeaders(), realm, future, false); if (newRealm == null) { return true; } @@ -305,10 +309,10 @@ private boolean handleProxyAuthenticationRequiredAndExit(int statusCode,// boolean negociate = proxyAuthenticateHeaders.contains("Negotiate"); if (!proxyAuthenticateHeaders.contains("Kerberos") && (isNTLM(proxyAuthenticateHeaders) || negociate)) { - newRealm = ntlmProxyChallenge(proxyAuthenticateHeaders, request, proxyServer, request.getHeaders(), realm, future); + newRealm = ntlmProxyChallenge(proxyAuthenticateHeaders, request, proxyServer, request.getHeaders(), realm, future, true); // SPNEGO KERBEROS } else if (negociate) { - newRealm = kerberosChallenge(proxyAuthenticateHeaders, request, proxyServer, request.getHeaders(), realm, future); + newRealm = kerberosChallenge(proxyAuthenticateHeaders, request, proxyServer, request.getHeaders(), realm, future, true); if (newRealm == null) return true; } else { @@ -459,10 +463,12 @@ && handleResponseAndExit(channel, future, handler, nettyRequest.getHttpRequest() try { channels.abort(future, t); + } catch (Exception abortException) { + LOGGER.debug("Abort failed", abortException); } finally { finishUpdate(future, channel, false); - throw t; } + throw t; } } From e1e8e34310ff2d1f5e0207d53eaab1b57b4729aa Mon Sep 17 00:00:00 2001 From: jfarcand Date: Tue, 3 Jun 2014 15:47:32 -0400 Subject: [PATCH 0027/2020] New URL --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a09dc329a..a2c150d501 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ You can also download the artifact [Maven Search](http://search.maven.org) -Then in your code you can simply do ([Javadoc](http://sonatype.github.com/async-http-client/apidocs/index.html)) +Then in your code you can simply do [Javadoc](http://asynchttpclient.github.io/async-http-client/apidocs/reference/packages.html) ```java import com.ning.http.client.*; From c90cf8628bdcf1bfca6cb512c08ead143914b82f Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 4 Jun 2014 08:51:04 +0200 Subject: [PATCH 0028/2020] Have one central place for configuration defaults, close #565 --- .../AsyncHttpClientConfig.java | 202 ++++++++---------- .../AsyncHttpClientConfigBean.java | 60 +++--- .../AsyncHttpClientConfigDefaults.java | 136 ++++++++++++ .../SimpleAsyncHttpClient.java | 12 +- .../async/AsyncProvidersBasicTest.java | 2 +- .../asynchttpclient/async/BasicAuthTest.java | 2 +- .../asynchttpclient/async/BodyChunkTest.java | 2 +- .../asynchttpclient/async/ChunkingTest.java | 4 +- .../async/ConnectionPoolTest.java | 8 +- .../async/HttpToHttpsRedirectTest.java | 6 +- .../async/MaxConnectionsInThreads.java | 2 +- .../async/MaxTotalConnectionTest.java | 6 +- .../async/NoNullResponseTest.java | 2 +- .../org/asynchttpclient/async/RC10KTest.java | 2 +- .../async/RedirectConnectionUsageTest.java | 4 +- .../asynchttpclient/async/RemoteSiteTest.java | 2 +- .../async/SimpleAsyncHttpClientTest.java | 8 +- .../grizzly/GrizzlyConnectionPoolTest.java | 2 +- .../GrizzlyFeedableBodyGeneratorTest.java | 8 +- .../NettyRequestThrottleTimeoutTest.java | 2 +- .../netty/RetryNonBlockingIssue.java | 5 +- 21 files changed, 290 insertions(+), 187 deletions(-) create mode 100644 api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java index c352c7f43c..babb8cbd9e 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -15,13 +15,12 @@ */ package org.asynchttpclient; -import static org.asynchttpclient.util.MiscUtil.getBoolean; +import static org.asynchttpclient.AsyncHttpClientConfigDefaults.*; import org.asynchttpclient.date.TimeConverter; import org.asynchttpclient.filter.IOExceptionFilter; import org.asynchttpclient.filter.RequestFilter; import org.asynchttpclient.filter.ResponseFilter; -import org.asynchttpclient.util.DefaultHostnameVerifier; import org.asynchttpclient.util.ProxyUtils; import javax.net.ssl.HostnameVerifier; @@ -46,14 +45,14 @@ * -Dorg.asynchttpclient.AsyncHttpClientConfig.nameOfTheProperty * ex: *

- * -Dorg.asynchttpclient.AsyncHttpClientConfig.defaultMaxTotalConnections - * -Dorg.asynchttpclient.AsyncHttpClientConfig.defaultMaxTotalConnections - * -Dorg.asynchttpclient.AsyncHttpClientConfig.defaultMaxConnectionsPerHost - * -Dorg.asynchttpclient.AsyncHttpClientConfig.defaultConnectionTimeoutInMS - * -Dorg.asynchttpclient.AsyncHttpClientConfig.defaultIdleConnectionInPoolTimeoutInMS - * -Dorg.asynchttpclient.AsyncHttpClientConfig.defaultRequestTimeoutInMS - * -Dorg.asynchttpclient.AsyncHttpClientConfig.defaultRedirectsEnabled - * -Dorg.asynchttpclient.AsyncHttpClientConfig.defaultMaxRedirects + * -Dorg.asynchttpclient.AsyncHttpClientConfig.maxTotalConnections + * -Dorg.asynchttpclient.AsyncHttpClientConfig.maxTotalConnections + * -Dorg.asynchttpclient.AsyncHttpClientConfig.maxConnectionsPerHost + * -Dorg.asynchttpclient.AsyncHttpClientConfig.connectionTimeoutInMs + * -Dorg.asynchttpclient.AsyncHttpClientConfig.idleConnectionInPoolTimeoutInMs + * -Dorg.asynchttpclient.AsyncHttpClientConfig.requestTimeoutInMs + * -Dorg.asynchttpclient.AsyncHttpClientConfig.redirectsEnabled + * -Dorg.asynchttpclient.AsyncHttpClientConfig.maxRedirects */ public class AsyncHttpClientConfig { @@ -88,7 +87,7 @@ public class AsyncHttpClientConfig { protected int idleConnectionTimeoutInMs; protected int requestTimeoutInMs; protected boolean redirectEnabled; - protected int maxDefaultRedirects; + protected int maxRedirects; protected boolean compressionEnabled; protected String userAgent; protected boolean allowPoolingConnection; @@ -128,7 +127,7 @@ private AsyncHttpClientConfig(int maxTotalConnections, // int requestTimeoutInMs, // int connectionMaxLifeTimeInMs, // boolean redirectEnabled, // - int maxDefaultRedirects, // + int maxRedirects, // boolean compressionEnabled, // String userAgent, // boolean keepAlive, // @@ -154,8 +153,6 @@ private AsyncHttpClientConfig(int maxTotalConnections, // boolean spdyEnabled, // int spdyInitialWindowSize, // int spdyMaxConcurrentStreams, // - boolean rfc6265CookieEncoding, // - boolean asyncConnectMode, // TimeConverter timeConverter) { this.maxTotalConnections = maxTotalConnections; @@ -167,7 +164,7 @@ private AsyncHttpClientConfig(int maxTotalConnections, // this.requestTimeoutInMs = requestTimeoutInMs; this.maxConnectionLifeTimeInMs = connectionMaxLifeTimeInMs; this.redirectEnabled = redirectEnabled; - this.maxDefaultRedirects = maxDefaultRedirects; + this.maxRedirects = maxRedirects; this.compressionEnabled = compressionEnabled; this.userAgent = userAgent; this.allowPoolingConnection = keepAlive; @@ -193,6 +190,7 @@ private AsyncHttpClientConfig(int maxTotalConnections, // this.spdyInitialWindowSize = spdyInitialWindowSize; this.spdyMaxConcurrentStreams = spdyMaxConcurrentStreams; this.timeConverter = timeConverter; + } /** @@ -274,7 +272,7 @@ public boolean isRedirectEnabled() { * @return the maximum number of HTTP redirect */ public int getMaxRedirects() { - return maxDefaultRedirects; + return maxRedirects; } /** @@ -564,23 +562,34 @@ public TimeConverter getTimeConverter() { * Builder for an {@link AsyncHttpClient} */ public static class Builder { - private int defaultMaxTotalConnections = Integer.getInteger(ASYNC_CLIENT + "defaultMaxTotalConnections", -1); - private int defaultMaxConnectionPerHost = Integer.getInteger(ASYNC_CLIENT + "defaultMaxConnectionsPerHost", -1); - private int defaultConnectionTimeOutInMs = Integer.getInteger(ASYNC_CLIENT + "defaultConnectionTimeoutInMS", 60 * 1000); - private int defaultWebsocketIdleTimeoutInMs = Integer.getInteger(ASYNC_CLIENT + "defaultWebsocketTimoutInMS", 15 * 60 * 1000); - private int defaultIdleConnectionInPoolTimeoutInMs = Integer.getInteger(ASYNC_CLIENT + "defaultIdleConnectionInPoolTimeoutInMS", - 60 * 1000); - private int defaultIdleConnectionTimeoutInMs = Integer.getInteger(ASYNC_CLIENT + "defaultIdleConnectionTimeoutInMS", 60 * 1000); - private int defaultRequestTimeoutInMs = Integer.getInteger(ASYNC_CLIENT + "defaultRequestTimeoutInMS", 60 * 1000); - private int defaultMaxConnectionLifeTimeInMs = Integer.getInteger(ASYNC_CLIENT + "defaultMaxConnectionLifeTimeInMs", -1); - private boolean redirectEnabled = Boolean.getBoolean(ASYNC_CLIENT + "defaultRedirectsEnabled"); - private int maxDefaultRedirects = Integer.getInteger(ASYNC_CLIENT + "defaultMaxRedirects", 5); - private boolean compressionEnabled = Boolean.getBoolean(ASYNC_CLIENT + "compressionEnabled"); - private String userAgent = System.getProperty(ASYNC_CLIENT + "userAgent", "AsyncHttpClient/" + AHC_VERSION); - private boolean useProxyProperties = Boolean.getBoolean(ASYNC_CLIENT + "useProxyProperties"); - private boolean useProxySelector = Boolean.getBoolean(ASYNC_CLIENT + "useProxySelector"); - private boolean allowPoolingConnection = true; - private boolean useRelativeURIsWithSSLProxies = getBoolean(ASYNC_CLIENT + "useRelativeURIsWithSSLProxies", true); + private int maxTotalConnections = defaultMaxTotalConnections(); + private int maxConnectionPerHost = defaultMaxConnectionPerHost(); + private int connectionTimeOutInMs = defaultConnectionTimeOutInMs(); + private int webSocketIdleTimeoutInMs = defaultWebSocketIdleTimeoutInMs(); + private int idleConnectionInPoolTimeoutInMs = defaultIdleConnectionInPoolTimeoutInMs(); + private int idleConnectionTimeoutInMs = defaultIdleConnectionTimeoutInMs(); + private int requestTimeoutInMs = defaultRequestTimeoutInMs(); + private int maxConnectionLifeTimeInMs = defaultMaxConnectionLifeTimeInMs(); + private boolean redirectEnabled = defaultRedirectEnabled(); + private int maxRedirects = defaultMaxRedirects(); + private boolean compressionEnabled = defaultCompressionEnabled(); + private String userAgent = defaultUserAgent(); + private boolean useProxyProperties = defaultUseProxyProperties(); + private boolean useProxySelector = defaultUseProxySelector(); + private boolean allowPoolingConnection = defaultAllowPoolingConnection(); + private boolean useRelativeURIsWithSSLProxies = defaultUseRelativeURIsWithSSLProxies(); + private int requestCompressionLevel = defaultRequestCompressionLevel(); + private int maxRequestRetry = defaultMaxRequestRetry(); + private int ioThreadMultiplier = defaultIoThreadMultiplier(); + private boolean allowSslConnectionPool = defaultAllowSslConnectionPool(); + private boolean useRawUrl = defaultUseRawUrl(); + private boolean removeQueryParamOnRedirect = defaultRemoveQueryParamOnRedirect(); + private boolean strict302Handling = defaultStrict302Handling(); + private HostnameVerifier hostnameVerifier = defaultHostnameVerifier(); + private boolean spdyEnabled = defaultSpdyEnabled(); + private int spdyInitialWindowSize = defaultSpdyInitialWindowSize(); + private int spdyMaxConcurrentStreams = defaultSpdyMaxConcurrentStreams(); + private ScheduledExecutorService reaper; private ExecutorService applicationThreadPool; private ProxyServerSelector proxyServerSelector = null; @@ -588,22 +597,9 @@ public static class Builder { private SSLEngineFactory sslEngineFactory; private AsyncHttpProviderConfig providerConfig; private Realm realm; - private int requestCompressionLevel = -1; - private int maxRequestRetry = 5; private final List requestFilters = new LinkedList(); private final List responseFilters = new LinkedList(); private final List ioExceptionFilters = new LinkedList(); - private boolean allowSslConnectionPool = true; - private boolean useRawUrl = false; - private boolean removeQueryParamOnRedirect = true; - private HostnameVerifier hostnameVerifier = new DefaultHostnameVerifier(); - private int ioThreadMultiplier = 2; - private boolean strict302Handling; - private boolean spdyEnabled; - private int spdyInitialWindowSize = 10 * 1024 * 1024; - private int spdyMaxConcurrentStreams = 100; - private boolean rfc6265CookieEncoding = true; - private boolean asyncConnectMode; private TimeConverter timeConverter; public Builder() { @@ -612,57 +608,57 @@ public Builder() { /** * Set the maximum number of connections an {@link AsyncHttpClient} can handle. * - * @param defaultMaxTotalConnections the maximum number of connections an {@link AsyncHttpClient} can handle. + * @param maxTotalConnections the maximum number of connections an {@link AsyncHttpClient} can handle. * @return a {@link Builder} */ - public Builder setMaximumConnectionsTotal(int defaultMaxTotalConnections) { - this.defaultMaxTotalConnections = defaultMaxTotalConnections; + public Builder setMaxConnectionsTotal(int maxTotalConnections) { + this.maxTotalConnections = maxTotalConnections; return this; } /** * Set the maximum number of connections per hosts an {@link AsyncHttpClient} can handle. * - * @param defaultMaxConnectionPerHost the maximum number of connections per host an {@link AsyncHttpClient} can handle. + * @param maxConnectionPerHost the maximum number of connections per host an {@link AsyncHttpClient} can handle. * @return a {@link Builder} */ - public Builder setMaximumConnectionsPerHost(int defaultMaxConnectionPerHost) { - this.defaultMaxConnectionPerHost = defaultMaxConnectionPerHost; + public Builder setMaxConnectionsPerHost(int maxConnectionPerHost) { + this.maxConnectionPerHost = maxConnectionPerHost; return this; } /** * Set the maximum time in millisecond an {@link AsyncHttpClient} can wait when connecting to a remote host * - * @param defaultConnectionTimeOutInMs the maximum time in millisecond an {@link AsyncHttpClient} can wait when connecting to a remote host + * @param connectionTimeOutInMs the maximum time in millisecond an {@link AsyncHttpClient} can wait when connecting to a remote host * @return a {@link Builder} */ - public Builder setConnectionTimeoutInMs(int defaultConnectionTimeOutInMs) { - this.defaultConnectionTimeOutInMs = defaultConnectionTimeOutInMs; + public Builder setConnectionTimeoutInMs(int connectionTimeOutInMs) { + this.connectionTimeOutInMs = connectionTimeOutInMs; return this; } /** * Set the maximum time in millisecond an {@link org.asynchttpclient.websocket.WebSocket} can stay idle. * - * @param defaultWebSocketIdleTimeoutInMs + * @param webSocketIdleTimeoutInMs * the maximum time in millisecond an {@link org.asynchttpclient.websocket.WebSocket} can stay idle. * @return a {@link Builder} */ - public Builder setWebSocketIdleTimeoutInMs(int defaultWebSocketIdleTimeoutInMs) { - this.defaultWebsocketIdleTimeoutInMs = defaultWebSocketIdleTimeoutInMs; + public Builder setWebSocketIdleTimeoutInMs(int webSocketIdleTimeoutInMs) { + this.webSocketIdleTimeoutInMs = webSocketIdleTimeoutInMs; return this; } /** * Set the maximum time in millisecond an {@link AsyncHttpClient} can stay idle. * - * @param defaultIdleConnectionTimeoutInMs + * @param idleConnectionTimeoutInMs * the maximum time in millisecond an {@link AsyncHttpClient} can stay idle. * @return a {@link Builder} */ - public Builder setIdleConnectionTimeoutInMs(int defaultIdleConnectionTimeoutInMs) { - this.defaultIdleConnectionTimeoutInMs = defaultIdleConnectionTimeoutInMs; + public Builder setIdleConnectionTimeoutInMs(int idleConnectionTimeoutInMs) { + this.idleConnectionTimeoutInMs = idleConnectionTimeoutInMs; return this; } @@ -670,24 +666,24 @@ public Builder setIdleConnectionTimeoutInMs(int defaultIdleConnectionTimeoutInMs * Set the maximum time in millisecond an {@link AsyncHttpClient} will keep connection * idle in pool. * - * @param defaultIdleConnectionInPoolTimeoutInMs + * @param idleConnectionInPoolTimeoutInMs * the maximum time in millisecond an {@link AsyncHttpClient} will keep connection * idle in pool. * @return a {@link Builder} */ - public Builder setIdleConnectionInPoolTimeoutInMs(int defaultIdleConnectionInPoolTimeoutInMs) { - this.defaultIdleConnectionInPoolTimeoutInMs = defaultIdleConnectionInPoolTimeoutInMs; + public Builder setIdleConnectionInPoolTimeoutInMs(int idleConnectionInPoolTimeoutInMs) { + this.idleConnectionInPoolTimeoutInMs = idleConnectionInPoolTimeoutInMs; return this; } /** * Set the maximum time in millisecond an {@link AsyncHttpClient} wait for a response * - * @param defaultRequestTimeoutInMs the maximum time in millisecond an {@link AsyncHttpClient} wait for a response + * @param requestTimeoutInMs the maximum time in millisecond an {@link AsyncHttpClient} wait for a response * @return a {@link Builder} */ - public Builder setRequestTimeoutInMs(int defaultRequestTimeoutInMs) { - this.defaultRequestTimeoutInMs = defaultRequestTimeoutInMs; + public Builder setRequestTimeoutInMs(int requestTimeoutInMs) { + this.requestTimeoutInMs = requestTimeoutInMs; return this; } @@ -705,11 +701,11 @@ public Builder setFollowRedirects(boolean redirectEnabled) { /** * Set the maximum number of HTTP redirect * - * @param maxDefaultRedirects the maximum number of HTTP redirect + * @param maxRedirects the maximum number of HTTP redirect * @return a {@link Builder} */ - public Builder setMaximumNumberOfRedirects(int maxDefaultRedirects) { - this.maxDefaultRedirects = maxDefaultRedirects; + public Builder setMaxRedirects(int maxRedirects) { + this.maxRedirects = maxRedirects; return this; } @@ -1040,7 +1036,7 @@ public Builder setStrict302Handling(final boolean strict302Handling) { * @return a {@link Builder} */ public Builder setMaxConnectionLifeTimeInMs(int maxConnectionLifeTimeInMs) { - this.defaultMaxConnectionLifeTimeInMs = maxConnectionLifeTimeInMs; + this.maxConnectionLifeTimeInMs = maxConnectionLifeTimeInMs; return this; } @@ -1102,34 +1098,6 @@ public Builder setSpdyMaxConcurrentStreams(int spdyMaxConcurrentStreams) { return this; } - /** - * Configures this AHC instance to use RFC 6265 cookie encoding style - * - * @param rfc6265CookieEncoding - * @return this - * - * @since 1.7.18 - */ - public Builder setRfc6265CookieEncoding(boolean rfc6265CookieEncoding) { - this.rfc6265CookieEncoding = rfc6265CookieEncoding; - return this; - } - - /** - * Configures how the underlying providers make new connections. By default, - * connections will be made synchronously. - * - * @param asyncConnectMode pass true to enable async connect mode. - * - * @return this - * - * @since 2.0.0 - */ - public Builder setAsyncConnectMode(boolean asyncConnectMode) { - this.asyncConnectMode = asyncConnectMode; - return this; - } - public Builder setTimeConverter(TimeConverter timeConverter) { this.timeConverter = timeConverter; return this; @@ -1143,16 +1111,16 @@ public Builder setTimeConverter(TimeConverter timeConverter) { public Builder(AsyncHttpClientConfig prototype) { allowPoolingConnection = prototype.getAllowPoolingConnection(); providerConfig = prototype.getAsyncHttpProviderConfig(); - defaultConnectionTimeOutInMs = prototype.getConnectionTimeoutInMs(); - defaultIdleConnectionInPoolTimeoutInMs = prototype.getIdleConnectionInPoolTimeoutInMs(); - defaultIdleConnectionTimeoutInMs = prototype.getIdleConnectionTimeoutInMs(); - defaultMaxConnectionPerHost = prototype.getMaxConnectionPerHost(); - defaultMaxConnectionLifeTimeInMs = prototype.getMaxConnectionLifeTimeInMs(); - maxDefaultRedirects = prototype.getMaxRedirects(); - defaultMaxTotalConnections = prototype.getMaxTotalConnections(); + connectionTimeOutInMs = prototype.getConnectionTimeoutInMs(); + idleConnectionInPoolTimeoutInMs = prototype.getIdleConnectionInPoolTimeoutInMs(); + idleConnectionTimeoutInMs = prototype.getIdleConnectionTimeoutInMs(); + maxConnectionPerHost = prototype.getMaxConnectionPerHost(); + maxConnectionLifeTimeInMs = prototype.getMaxConnectionLifeTimeInMs(); + maxRedirects = prototype.getMaxRedirects(); + maxTotalConnections = prototype.getMaxTotalConnections(); proxyServerSelector = prototype.getProxyServerSelector(); realm = prototype.getRealm(); - defaultRequestTimeoutInMs = prototype.getRequestTimeoutInMs(); + requestTimeoutInMs = prototype.getRequestTimeoutInMs(); sslContext = prototype.getSSLContext(); sslEngineFactory = prototype.getSSLEngineFactory(); userAgent = prototype.getUserAgent(); @@ -1209,16 +1177,16 @@ public Thread newThread(Runnable r) { proxyServerSelector = ProxyServerSelector.NO_PROXY_SELECTOR; } - return new AsyncHttpClientConfig(defaultMaxTotalConnections, // - defaultMaxConnectionPerHost, // - defaultConnectionTimeOutInMs, // - defaultWebsocketIdleTimeoutInMs, // - defaultIdleConnectionInPoolTimeoutInMs, // - defaultIdleConnectionTimeoutInMs, // - defaultRequestTimeoutInMs, // - defaultMaxConnectionLifeTimeInMs, // + return new AsyncHttpClientConfig(maxTotalConnections, // + maxConnectionPerHost, // + connectionTimeOutInMs, // + webSocketIdleTimeoutInMs, // + idleConnectionInPoolTimeoutInMs, // + idleConnectionTimeoutInMs, // + requestTimeoutInMs, // + maxConnectionLifeTimeInMs, // redirectEnabled, // - maxDefaultRedirects, // + maxRedirects, // compressionEnabled, // userAgent, // allowPoolingConnection, // @@ -1244,8 +1212,6 @@ public Thread newThread(Runnable r) { spdyEnabled, // spdyInitialWindowSize, // spdyMaxConcurrentStreams, // - rfc6265CookieEncoding, // - asyncConnectMode, // timeConverter); } } diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java index e0e01b9943..98ab988ebb 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java @@ -12,15 +12,15 @@ */ package org.asynchttpclient; +import static org.asynchttpclient.AsyncHttpClientConfigDefaults.*; + import org.asynchttpclient.filter.IOExceptionFilter; import org.asynchttpclient.filter.RequestFilter; import org.asynchttpclient.filter.ResponseFilter; -import org.asynchttpclient.util.DefaultHostnameVerifier; import org.asynchttpclient.util.ProxyUtils; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; import java.util.LinkedList; import java.util.concurrent.ExecutorService; @@ -45,34 +45,36 @@ void configureFilters() { } void configureDefaults() { - maxTotalConnections = Integer.getInteger(ASYNC_CLIENT + "defaultMaxTotalConnections", -1); - maxConnectionPerHost = Integer.getInteger(ASYNC_CLIENT + "defaultMaxConnectionsPerHost", -1); - connectionTimeOutInMs = Integer.getInteger(ASYNC_CLIENT + "defaultConnectionTimeoutInMS", 60 * 1000); - idleConnectionInPoolTimeoutInMs = Integer.getInteger(ASYNC_CLIENT + "defaultIdleConnectionInPoolTimeoutInMS", 60 * 1000); - idleConnectionTimeoutInMs = Integer.getInteger(ASYNC_CLIENT + "defaultIdleConnectionTimeoutInMS", 60 * 1000); - requestTimeoutInMs = Integer.getInteger(ASYNC_CLIENT + "defaultRequestTimeoutInMS", 60 * 1000); - maxConnectionLifeTimeInMs = Integer.getInteger(ASYNC_CLIENT + "defaultMaxConnectionLifeTimeInMs", -1); - redirectEnabled = Boolean.getBoolean(ASYNC_CLIENT + "defaultRedirectsEnabled"); - maxDefaultRedirects = Integer.getInteger(ASYNC_CLIENT + "defaultMaxRedirects", 5); - compressionEnabled = Boolean.getBoolean(ASYNC_CLIENT + "compressionEnabled"); - userAgent = System.getProperty(ASYNC_CLIENT + "userAgent", "AsyncHttpClient/" + AHC_VERSION); - ioThreadMultiplier = Integer.getInteger(ASYNC_CLIENT + "ioThreadMultiplier", 2); - - boolean useProxySelector = Boolean.getBoolean(ASYNC_CLIENT + "useProxySelector"); - boolean useProxyProperties = Boolean.getBoolean(ASYNC_CLIENT + "useProxyProperties"); - if (useProxySelector) { + maxTotalConnections = defaultMaxTotalConnections(); + maxConnectionPerHost = defaultMaxConnectionPerHost(); + connectionTimeOutInMs = defaultConnectionTimeOutInMs(); + webSocketIdleTimeoutInMs = defaultWebSocketIdleTimeoutInMs(); + idleConnectionInPoolTimeoutInMs = defaultIdleConnectionInPoolTimeoutInMs(); + idleConnectionTimeoutInMs = defaultIdleConnectionTimeoutInMs(); + requestTimeoutInMs = defaultRequestTimeoutInMs(); + maxConnectionLifeTimeInMs = defaultMaxConnectionLifeTimeInMs(); + redirectEnabled = defaultRedirectEnabled(); + maxRedirects = defaultMaxRedirects(); + compressionEnabled = defaultCompressionEnabled(); + userAgent = defaultUserAgent(); + allowPoolingConnection = defaultAllowPoolingConnection(); + useRelativeURIsWithSSLProxies = defaultUseRelativeURIsWithSSLProxies(); + requestCompressionLevel = defaultRequestCompressionLevel(); + maxRequestRetry = defaultMaxRequestRetry(); + ioThreadMultiplier = defaultIoThreadMultiplier(); + allowSslConnectionPool = defaultAllowSslConnectionPool(); + useRawUrl = defaultUseRawUrl(); + removeQueryParamOnRedirect = defaultRemoveQueryParamOnRedirect(); + strict302Handling = defaultStrict302Handling(); + hostnameVerifier = defaultHostnameVerifier(); + spdyEnabled = defaultSpdyEnabled(); + spdyInitialWindowSize = defaultSpdyInitialWindowSize(); + spdyMaxConcurrentStreams = defaultSpdyMaxConcurrentStreams(); + if (defaultUseProxySelector()) { proxyServerSelector = ProxyUtils.getJdkDefaultProxyServerSelector(); - } else if (useProxyProperties) { + } else if (defaultUseProxyProperties()) { proxyServerSelector = ProxyUtils.createProxyServerSelector(System.getProperties()); } - - allowPoolingConnection = true; - requestCompressionLevel = -1; - maxRequestRetry = 5; - allowSslConnectionPool = true; - useRawUrl = false; - removeQueryParamOnRedirect = true; - hostnameVerifier = new DefaultHostnameVerifier(); } void configureExecutors() { @@ -125,8 +127,8 @@ public AsyncHttpClientConfigBean setRedirectEnabled(boolean redirectEnabled) { return this; } - public AsyncHttpClientConfigBean setMaxDefaultRedirects(int maxDefaultRedirects) { - this.maxDefaultRedirects = maxDefaultRedirects; + public AsyncHttpClientConfigBean setMaxRedirects(int maxRedirects) { + this.maxRedirects = maxRedirects; return this; } diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java new file mode 100644 index 0000000000..edd51169a1 --- /dev/null +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2014 AsyncHttpClient Project. 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; + +import static org.asynchttpclient.util.MiscUtil.getBoolean; + +import org.asynchttpclient.util.DefaultHostnameVerifier; + +import javax.net.ssl.HostnameVerifier; + +public final class AsyncHttpClientConfigDefaults { + + private AsyncHttpClientConfigDefaults() { + } + + public static final String ASYNC_CLIENT = AsyncHttpClientConfig.class.getName() + "."; + + public static int defaultMaxTotalConnections() { + return Integer.getInteger(ASYNC_CLIENT + "maxTotalConnections", -1); + } + + public static int defaultMaxConnectionPerHost() { + return Integer.getInteger(ASYNC_CLIENT + "maxConnectionsPerHost", -1); + } + + public static int defaultConnectionTimeOutInMs() { + return Integer.getInteger(ASYNC_CLIENT + "connectionTimeoutInMs", 60 * 1000); + } + + public static int defaultIdleConnectionInPoolTimeoutInMs() { + return Integer.getInteger(ASYNC_CLIENT + "idleConnectionInPoolTimeoutInMs", 60 * 1000); + } + + public static int defaultIdleConnectionTimeoutInMs() { + return Integer.getInteger(ASYNC_CLIENT + "idleConnectionTimeoutInMs", 60 * 1000); + } + + public static int defaultRequestTimeoutInMs() { + return Integer.getInteger(ASYNC_CLIENT + "requestTimeoutInMs", 60 * 1000); + } + + public static int defaultWebSocketIdleTimeoutInMs() { + return Integer.getInteger(ASYNC_CLIENT + "webSocketTimoutInMS", 15 * 60 * 1000); + } + + public static int defaultMaxConnectionLifeTimeInMs() { + return Integer.getInteger(ASYNC_CLIENT + "maxConnectionLifeTimeInMs", -1); + } + + public static boolean defaultRedirectEnabled() { + return Boolean.getBoolean(ASYNC_CLIENT + "redirectsEnabled"); + } + + public static int defaultMaxRedirects() { + return Integer.getInteger(ASYNC_CLIENT + "maxRedirects", 5); + } + + public static boolean defaultCompressionEnabled() { + return Boolean.getBoolean(ASYNC_CLIENT + "compressionEnabled"); + } + + public static String defaultUserAgent() { + return System.getProperty(ASYNC_CLIENT + "userAgent", "NING/1.0"); + } + + public static int defaultIoThreadMultiplier() { + return Integer.getInteger(ASYNC_CLIENT + "ioThreadMultiplier", 2); + } + + public static boolean defaultUseProxySelector() { + return Boolean.getBoolean(ASYNC_CLIENT + "useProxySelector"); + } + + public static boolean defaultUseProxyProperties() { + return Boolean.getBoolean(ASYNC_CLIENT + "useProxyProperties"); + } + + public static boolean defaultStrict302Handling() { + return Boolean.getBoolean(ASYNC_CLIENT + "strict302Handling"); + } + + public static boolean defaultAllowPoolingConnection() { + return getBoolean(ASYNC_CLIENT + "allowPoolingConnection", true); + } + + public static boolean defaultUseRelativeURIsWithSSLProxies() { + return getBoolean(ASYNC_CLIENT + "useRelativeURIsWithSSLProxies", true); + } + + // unused/broken, left there for compatibility, fixed in Netty 4 + public static int defaultRequestCompressionLevel() { + return Integer.getInteger(ASYNC_CLIENT + "requestCompressionLevel", -1); + } + + public static int defaultMaxRequestRetry() { + return Integer.getInteger(ASYNC_CLIENT + "maxRequestRetry", 5); + } + + public static boolean defaultAllowSslConnectionPool() { + return getBoolean(ASYNC_CLIENT + "allowSslConnectionPool", true); + } + + public static boolean defaultUseRawUrl() { + return Boolean.getBoolean(ASYNC_CLIENT + "useRawUrl"); + } + + public static boolean defaultRemoveQueryParamOnRedirect() { + return getBoolean(ASYNC_CLIENT + "removeQueryParamOnRedirect", true); + } + + public static HostnameVerifier defaultHostnameVerifier() { + return new DefaultHostnameVerifier(); + } + + public static boolean defaultSpdyEnabled() { + return Boolean.getBoolean(ASYNC_CLIENT + "spdyEnabled"); + } + + public static int defaultSpdyInitialWindowSize() { + return Integer.getInteger(ASYNC_CLIENT + "spdyInitialWindowSize", 10 * 1024 * 1024); + } + + public static int defaultSpdyMaxConcurrentStreams() { + return Integer.getInteger(ASYNC_CLIENT + "spdyMaxConcurrentStreams", 100); + } +} diff --git a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java index 271a352ed6..3bbf84d7fb 100644 --- a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java +++ b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java @@ -493,13 +493,13 @@ public Builder setFollowRedirects(boolean followRedirects) { return this; } - public Builder setMaximumConnectionsTotal(int defaultMaxTotalConnections) { - configBuilder.setMaximumConnectionsTotal(defaultMaxTotalConnections); + public Builder setMaxConnectionsTotal(int defaultMaxTotalConnections) { + configBuilder.setMaxConnectionsTotal(defaultMaxTotalConnections); return this; } - public Builder setMaximumConnectionsPerHost(int defaultMaxConnectionPerHost) { - configBuilder.setMaximumConnectionsPerHost(defaultMaxConnectionPerHost); + public Builder setMaxConnectionsPerHost(int defaultMaxConnectionPerHost) { + configBuilder.setMaxConnectionsPerHost(defaultMaxConnectionPerHost); return this; } @@ -518,8 +518,8 @@ public Builder setRequestTimeoutInMs(int defaultRequestTimeoutInMs) { return this; } - public Builder setMaximumNumberOfRedirects(int maxDefaultRedirects) { - configBuilder.setMaximumNumberOfRedirects(maxDefaultRedirects); + public Builder setMaxRedirects(int maxRedirects) { + configBuilder.setMaxRedirects(maxRedirects); return this; } diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java index ece7196939..00a1653678 100755 --- a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java @@ -1343,7 +1343,7 @@ public Response onCompleted(Response response) throws Exception { @Test(groups = { "online", "default_provider", "async" }) public void asyncDoGetMaxRedirectTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new Builder().setMaximumNumberOfRedirects(0).setFollowRedirects(true).build()); + AsyncHttpClient client = getAsyncHttpClient(new Builder().setMaxRedirects(0).setFollowRedirects(true).build()); try { // Use a l in case the assert fail final CountDownLatch l = new CountDownLatch(1); diff --git a/api/src/test/java/org/asynchttpclient/async/BasicAuthTest.java b/api/src/test/java/org/asynchttpclient/async/BasicAuthTest.java index 2f5b89073b..bb388d908e 100644 --- a/api/src/test/java/org/asynchttpclient/async/BasicAuthTest.java +++ b/api/src/test/java/org/asynchttpclient/async/BasicAuthTest.java @@ -197,7 +197,7 @@ public void basicAuthTest() throws IOException, ExecutionException, TimeoutExcep @Test(groups = { "standalone", "default_provider" }) public void redirectAndBasicAuthTest() throws Exception, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirects(true).setMaximumNumberOfRedirects(10).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirects(true).setMaxRedirects(10).build()); try { Future f = client.prepareGet(getTargetUrl2())// .setRealm((new Realm.RealmBuilder()).setPrincipal(USER).setPassword(ADMIN).build())// diff --git a/api/src/test/java/org/asynchttpclient/async/BodyChunkTest.java b/api/src/test/java/org/asynchttpclient/async/BodyChunkTest.java index 917c8a58dd..4dbc04bfb9 100644 --- a/api/src/test/java/org/asynchttpclient/async/BodyChunkTest.java +++ b/api/src/test/java/org/asynchttpclient/async/BodyChunkTest.java @@ -36,7 +36,7 @@ public void negativeContentTypeTest() throws Exception { AsyncHttpClientConfig.Builder confbuilder = new AsyncHttpClientConfig.Builder(); confbuilder = confbuilder.setConnectionTimeoutInMs(100); - confbuilder = confbuilder.setMaximumConnectionsTotal(50); + confbuilder = confbuilder.setMaxConnectionsTotal(50); confbuilder = confbuilder.setRequestTimeoutInMs(5 * 60 * 1000); // 5 minutes // Create client diff --git a/api/src/test/java/org/asynchttpclient/async/ChunkingTest.java b/api/src/test/java/org/asynchttpclient/async/ChunkingTest.java index 013940ee3c..c590a94a3c 100644 --- a/api/src/test/java/org/asynchttpclient/async/ChunkingTest.java +++ b/api/src/test/java/org/asynchttpclient/async/ChunkingTest.java @@ -46,8 +46,8 @@ public void testCustomChunking() throws Exception { AsyncHttpClientConfig.Builder bc = new AsyncHttpClientConfig.Builder(); bc.setAllowPoolingConnection(true); - bc.setMaximumConnectionsPerHost(1); - bc.setMaximumConnectionsTotal(1); + bc.setMaxConnectionsPerHost(1); + bc.setMaxConnectionsTotal(1); bc.setConnectionTimeoutInMs(1000); bc.setRequestTimeoutInMs(1000); bc.setFollowRedirects(true); diff --git a/api/src/test/java/org/asynchttpclient/async/ConnectionPoolTest.java b/api/src/test/java/org/asynchttpclient/async/ConnectionPoolTest.java index 792d436d76..be3dc83020 100644 --- a/api/src/test/java/org/asynchttpclient/async/ConnectionPoolTest.java +++ b/api/src/test/java/org/asynchttpclient/async/ConnectionPoolTest.java @@ -42,7 +42,7 @@ public abstract class ConnectionPoolTest extends AbstractBasicTest { @Test(groups = { "standalone", "default_provider" }) public void testMaxTotalConnections() { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAllowPoolingConnection(true).setMaximumConnectionsTotal(1).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAllowPoolingConnection(true).setMaxConnectionsTotal(1).build()); try { String url = getTargetUrl(); int i; @@ -64,7 +64,7 @@ public void testMaxTotalConnections() { @Test(groups = { "standalone", "default_provider" }) public void testMaxTotalConnectionsException() { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAllowPoolingConnection(true).setMaximumConnectionsTotal(1).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAllowPoolingConnection(true).setMaxConnectionsTotal(1).build()); try { String url = getTargetUrl(); int i; @@ -132,7 +132,7 @@ public Response onCompleted(Response response) throws Exception { @Test(groups = { "standalone", "default_provider" }) public void multipleMaxConnectionOpenTest() throws Exception { - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setAllowPoolingConnection(true).setConnectionTimeoutInMs(5000).setMaximumConnectionsTotal(1).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setAllowPoolingConnection(true).setConnectionTimeoutInMs(5000).setMaxConnectionsTotal(1).build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { String body = "hello there"; @@ -160,7 +160,7 @@ public void multipleMaxConnectionOpenTest() throws Exception { @Test(groups = { "standalone", "default_provider" }) public void multipleMaxConnectionOpenTestWithQuery() throws Exception { - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setAllowPoolingConnection(true).setConnectionTimeoutInMs(5000).setMaximumConnectionsTotal(1).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setAllowPoolingConnection(true).setConnectionTimeoutInMs(5000).setMaxConnectionsTotal(1).build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { String body = "hello there"; diff --git a/api/src/test/java/org/asynchttpclient/async/HttpToHttpsRedirectTest.java b/api/src/test/java/org/asynchttpclient/async/HttpToHttpsRedirectTest.java index 40ebd79f87..f754ac83a5 100644 --- a/api/src/test/java/org/asynchttpclient/async/HttpToHttpsRedirectTest.java +++ b/api/src/test/java/org/asynchttpclient/async/HttpToHttpsRedirectTest.java @@ -96,7 +96,7 @@ public void runAllSequentiallyBecauseNotThreadSafe() throws Exception { public void httpToHttpsRedirect() throws Exception { redirectDone.getAndSet(false); - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setMaximumNumberOfRedirects(5).setFollowRedirects(true).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setMaxRedirects(5).setFollowRedirects(true).build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", getTargetUrl2()).execute().get(); @@ -112,7 +112,7 @@ public void httpToHttpsRedirect() throws Exception { public void httpToHttpsProperConfig() throws Exception { redirectDone.getAndSet(false); - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setMaximumNumberOfRedirects(5).setFollowRedirects(true).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setMaxRedirects(5).setFollowRedirects(true).build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", getTargetUrl2() + "/test2").execute().get(); @@ -134,7 +134,7 @@ public void httpToHttpsProperConfig() throws Exception { public void relativeLocationUrl() throws Exception { redirectDone.getAndSet(false); - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setMaximumNumberOfRedirects(5).setFollowRedirects(true).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setMaxRedirects(5).setFollowRedirects(true).build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", "/foo/test").execute().get(); diff --git a/api/src/test/java/org/asynchttpclient/async/MaxConnectionsInThreads.java b/api/src/test/java/org/asynchttpclient/async/MaxConnectionsInThreads.java index 1d6369762b..31b6ddc359 100644 --- a/api/src/test/java/org/asynchttpclient/async/MaxConnectionsInThreads.java +++ b/api/src/test/java/org/asynchttpclient/async/MaxConnectionsInThreads.java @@ -52,7 +52,7 @@ public void testMaxConnectionsWithinThreads() { String[] urls = new String[] { servletEndpointUri.toString(), servletEndpointUri.toString() }; final AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeoutInMs(1000).setRequestTimeoutInMs(5000) - .setAllowPoolingConnection(true).setMaximumConnectionsTotal(1).setMaximumConnectionsPerHost(1).build()); + .setAllowPoolingConnection(true).setMaxConnectionsTotal(1).setMaxConnectionsPerHost(1).build()); try { final Boolean[] caughtError = new Boolean[] { Boolean.FALSE }; diff --git a/api/src/test/java/org/asynchttpclient/async/MaxTotalConnectionTest.java b/api/src/test/java/org/asynchttpclient/async/MaxTotalConnectionTest.java index d24c61f4a3..245abb9de9 100644 --- a/api/src/test/java/org/asynchttpclient/async/MaxTotalConnectionTest.java +++ b/api/src/test/java/org/asynchttpclient/async/MaxTotalConnectionTest.java @@ -39,7 +39,7 @@ public abstract class MaxTotalConnectionTest extends AbstractBasicTest { public void testMaxTotalConnectionsExceedingException() { String[] urls = new String[] { "http://google.com/", "http://github.com/" }; - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeoutInMs(1000).setRequestTimeoutInMs(5000).setAllowPoolingConnection(false).setMaximumConnectionsTotal(1).setMaximumConnectionsPerHost(1).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeoutInMs(1000).setRequestTimeoutInMs(5000).setAllowPoolingConnection(false).setMaxConnectionsTotal(1).setMaxConnectionsPerHost(1).build()); try { boolean caughtError = false; for (int i = 0; i < urls.length; i++) { @@ -61,7 +61,7 @@ public void testMaxTotalConnectionsExceedingException() { public void testMaxTotalConnections() { String[] urls = new String[] { "http://google.com/", "http://lenta.ru/" }; - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeoutInMs(1000).setRequestTimeoutInMs(5000).setAllowPoolingConnection(false).setMaximumConnectionsTotal(2).setMaximumConnectionsPerHost(1).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeoutInMs(1000).setRequestTimeoutInMs(5000).setAllowPoolingConnection(false).setMaxConnectionsTotal(2).setMaxConnectionsPerHost(1).build()); try { for (String url : urls) { try { @@ -82,7 +82,7 @@ public void testMaxTotalConnections() { public void testMaxTotalConnectionsCorrectExceptionHandling() { String[] urls = new String[] { "http://google.com/", "http://github.com/" }; - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeoutInMs(1000).setRequestTimeoutInMs(5000).setAllowPoolingConnection(false).setMaximumConnectionsTotal(1).setMaximumConnectionsPerHost(1).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeoutInMs(1000).setRequestTimeoutInMs(5000).setAllowPoolingConnection(false).setMaxConnectionsTotal(1).setMaxConnectionsPerHost(1).build()); try { List> futures = new ArrayList>(); boolean caughtError = false; diff --git a/api/src/test/java/org/asynchttpclient/async/NoNullResponseTest.java b/api/src/test/java/org/asynchttpclient/async/NoNullResponseTest.java index 01411d9906..2b78e7972a 100644 --- a/api/src/test/java/org/asynchttpclient/async/NoNullResponseTest.java +++ b/api/src/test/java/org/asynchttpclient/async/NoNullResponseTest.java @@ -57,7 +57,7 @@ public void multipleSslRequestsWithDelayAndKeepAlive() throws Exception { private AsyncHttpClient create() throws GeneralSecurityException { final AsyncHttpClientConfig.Builder configBuilder = new AsyncHttpClientConfig.Builder().setCompressionEnabled(true).setFollowRedirects(true).setSSLContext(getSSLContext()).setAllowPoolingConnection(true).setConnectionTimeoutInMs(10000) - .setIdleConnectionInPoolTimeoutInMs(60000).setRequestTimeoutInMs(10000).setMaximumConnectionsPerHost(-1).setMaximumConnectionsTotal(-1); + .setIdleConnectionInPoolTimeoutInMs(60000).setRequestTimeoutInMs(10000).setMaxConnectionsPerHost(-1).setMaxConnectionsTotal(-1); return getAsyncHttpClient(configBuilder.build()); } diff --git a/api/src/test/java/org/asynchttpclient/async/RC10KTest.java b/api/src/test/java/org/asynchttpclient/async/RC10KTest.java index 64c4e2e8ec..a52ccd4157 100644 --- a/api/src/test/java/org/asynchttpclient/async/RC10KTest.java +++ b/api/src/test/java/org/asynchttpclient/async/RC10KTest.java @@ -99,7 +99,7 @@ public void handle(String s, Request r, HttpServletRequest req, HttpServletRespo @Test(timeOut = 10 * 60 * 1000, groups = "scalability") public void rc10kProblem() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient ahc = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setMaximumConnectionsPerHost(C10K).setAllowPoolingConnection(true).build()); + AsyncHttpClient ahc = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setMaxConnectionsPerHost(C10K).setAllowPoolingConnection(true).build()); try { List> resps = new ArrayList>(C10K); int i = 0; diff --git a/api/src/test/java/org/asynchttpclient/async/RedirectConnectionUsageTest.java b/api/src/test/java/org/asynchttpclient/async/RedirectConnectionUsageTest.java index 22bdb6b02d..6849615298 100644 --- a/api/src/test/java/org/asynchttpclient/async/RedirectConnectionUsageTest.java +++ b/api/src/test/java/org/asynchttpclient/async/RedirectConnectionUsageTest.java @@ -75,8 +75,8 @@ public void testGetRedirectFinalUrl() throws Exception { AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// .setAllowPoolingConnection(true)// - .setMaximumConnectionsPerHost(1)// - .setMaximumConnectionsTotal(1)// + .setMaxConnectionsPerHost(1)// + .setMaxConnectionsTotal(1)// .setConnectionTimeoutInMs(1000)// .setRequestTimeoutInMs(1000)// .setFollowRedirects(true)// diff --git a/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java b/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java index a6e08a2111..d03c666900 100644 --- a/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java +++ b/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java @@ -155,7 +155,7 @@ public Response onCompleted(Response response) throws Exception { @Test(groups = { "online", "default_provider" }, enabled = false) public void invalidStreamTest2() throws Exception { AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(10000).setFollowRedirects(true) - .setAllowPoolingConnection(false).setMaximumNumberOfRedirects(6).build(); + .setAllowPoolingConnection(false).setMaxRedirects(6).build(); AsyncHttpClient c = getAsyncHttpClient(config); try { diff --git a/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java b/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java index df07e31922..895144f989 100644 --- a/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java +++ b/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java @@ -49,7 +49,7 @@ public abstract class SimpleAsyncHttpClientTest extends AbstractBasicTest { 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(); + .setMaxConnectionsTotal(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()))); @@ -65,7 +65,7 @@ public void inpuStreamBodyConsumerTest() throws Exception { 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(); + .setMaxConnectionsTotal(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)); @@ -82,7 +82,7 @@ public void stringBuilderBodyConsumerTest() throws Exception { 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(); + .setMaxConnectionsTotal(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)); @@ -117,7 +117,7 @@ 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") + .setMaxConnectionsTotal(50).setRequestTimeoutInMs(5 * 1000).setUrl(getTargetUrl() + "/testPutZeroBytesFileTest.txt").setHeader("Content-Type", "text/plain") .build(); try { File tmpfile = File.createTempFile("testPutZeroBytesFile", ".tmp"); diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyConnectionPoolTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyConnectionPoolTest.java index 1beb63e76e..acf6c92a3a 100644 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyConnectionPoolTest.java +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyConnectionPoolTest.java @@ -41,7 +41,7 @@ public void testMaxTotalConnectionsException() { @Test public void multipleMaxConnectionOpenTest() throws Exception { AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setAllowPoolingConnection(true).setConnectionTimeoutInMs(5000) - .setMaximumConnectionsTotal(1).build(); + .setMaxConnectionsTotal(1).build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { String body = "hello there"; diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java index 400ca9192d..620eb6ea66 100644 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java @@ -136,8 +136,8 @@ private void doSimpleFeeder(final boolean secure) { ExecutorService service = Executors.newFixedThreadPool(threadCount); AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder() - .setMaximumConnectionsPerHost(60) - .setMaximumConnectionsTotal(60) + .setMaxConnectionsPerHost(60) + .setMaxConnectionsTotal(60) .build(); final AsyncHttpClient client = new DefaultAsyncHttpClient(new GrizzlyAsyncHttpProvider(config), config); @@ -238,8 +238,8 @@ private void doNonBlockingFeeder(final boolean secure) { final ExecutorService service = Executors.newCachedThreadPool(); AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder() - .setMaximumConnectionsPerHost(60) - .setMaximumConnectionsTotal(60) + .setMaxConnectionsPerHost(60) + .setMaxConnectionsTotal(60) .build(); final AsyncHttpClient client = new DefaultAsyncHttpClient(new GrizzlyAsyncHttpProvider(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 82e39012db..ef02fa7896 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 @@ -82,7 +82,7 @@ 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()); + .setAllowPoolingConnection(true).setMaxConnectionsTotal(1).build()); int samples = 10; 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 ab4fda18b4..de5fbc896e 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 @@ -89,7 +89,7 @@ public void testRetryNonBlocking() throws IOException, InterruptedException, Exe AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// .setAllowPoolingConnection(true)// - .setMaximumConnectionsTotal(100)// + .setMaxConnectionsTotal(100)// .setConnectionTimeoutInMs(60000)// .setRequestTimeoutInMs(30000)// .build(); @@ -125,10 +125,9 @@ public void testRetryNonBlockingAsyncConnect() throws IOException, InterruptedEx AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// .setAllowPoolingConnection(true)// - .setMaximumConnectionsTotal(100)// + .setMaxConnectionsTotal(100)// .setConnectionTimeoutInMs(60000)// .setRequestTimeoutInMs(30000)// - .setAsyncConnectMode(true) // .build(); AsyncHttpClient client = getAsyncHttpClient(config); From 094fcc01e531c5681fe4b9da6456fd6d11bd9beb Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 4 Jun 2014 08:57:36 +0200 Subject: [PATCH 0029/2020] Only trigger IdleChannelDetector when maxIdleTime is > 0, close #566 --- .../providers/netty/channel/DefaultChannelPool.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java index 3d9fa7fb7f..e4b2554762 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java @@ -72,7 +72,9 @@ public DefaultChannelPool(// this.maxIdleTime = maxIdleTime; this.maxConnectionLifeTimeInMs = maxConnectionLifeTimeInMs; this.nettyTimer = nettyTimer; - scheduleNewIdleChannelDetector(new IdleChannelDetector()); + if (maxIdleTime > 0L) { + scheduleNewIdleChannelDetector(new IdleChannelDetector()); + } } private void scheduleNewIdleChannelDetector(TimerTask task) { From 9024e4a19aba83cedb813cce82fbdaa5fb68e1ab Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 4 Jun 2014 09:46:21 +0200 Subject: [PATCH 0030/2020] Use Java8 ConcurrentHashMap backport --- .../providers/netty/channel/DefaultChannelPool.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java index e4b2554762..c7d8f94919 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java @@ -24,11 +24,11 @@ import io.netty.util.Timeout; import io.netty.util.Timer; import io.netty.util.TimerTask; +import io.netty.util.internal.chmv8.ConcurrentHashMapV8; import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -39,9 +39,9 @@ public class DefaultChannelPool implements ChannelPool { private final static Logger log = LoggerFactory.getLogger(DefaultChannelPool.class); - private final ConcurrentHashMap> connectionsPool = new ConcurrentHashMap>(); - private final ConcurrentHashMap channel2IdleChannel = new ConcurrentHashMap(); - private final ConcurrentHashMap channel2CreationDate = new ConcurrentHashMap(); + private final ConcurrentHashMapV8> connectionsPool = new ConcurrentHashMapV8>(); + private final ConcurrentHashMapV8 channel2IdleChannel = new ConcurrentHashMapV8(); + private final ConcurrentHashMapV8 channel2CreationDate = new ConcurrentHashMapV8(); private final AtomicBoolean closed = new AtomicBoolean(false); private final Timer nettyTimer; private final boolean sslConnectionPoolEnabled; From 8edfd54a93b61cf232d76efdbe2beffa3f58eb23 Mon Sep 17 00:00:00 2001 From: oleksiys Date: Wed, 4 Jun 2014 17:47:04 -0700 Subject: [PATCH 0031/2020] [master] fix issue #564 https://github.com/AsyncHttpClient/async-http-client/issues/564 "emtpy HTTP delete message is sent using chunked transfer-encoding for no reason" --- .../providers/grizzly/Utils.java | 13 +--- .../bodyhandler/BodyGeneratorBodyHandler.java | 4 +- .../grizzly/bodyhandler/BodyHandler.java | 24 +++++-- .../bodyhandler/BodyHandlerFactory.java | 7 +- .../bodyhandler/ByteArrayBodyHandler.java | 19 +++-- .../grizzly/bodyhandler/ExpectHandler.java | 11 ++- .../grizzly/bodyhandler/FileBodyHandler.java | 23 ++++++- .../grizzly/bodyhandler/NoBodyHandler.java | 4 +- .../bodyhandler/ParamsBodyHandler.java | 4 +- .../grizzly/bodyhandler/PartsBodyHandler.java | 4 +- .../bodyhandler/StreamDataBodyHandler.java | 4 +- .../bodyhandler/StringBodyHandler.java | 4 +- .../filters/AsyncHttpClientFilter.java | 69 ++++++++++++------- 13 files changed, 126 insertions(+), 64 deletions(-) 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 069652ef29..340b70527f 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -13,12 +13,10 @@ 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; @@ -86,13 +84,4 @@ 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/BodyGeneratorBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyGeneratorBodyHandler.java index f4c018337c..53770d3b41 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -26,7 +26,7 @@ import java.io.IOException; -public final class BodyGeneratorBodyHandler implements BodyHandler { +public final class BodyGeneratorBodyHandler extends BodyHandler { // -------------------------------------------- Methods from BodyHandler diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyHandler.java index 44c907b9ba..2b6e17a32b 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -19,11 +19,25 @@ import java.io.IOException; -public interface BodyHandler { +public abstract class BodyHandler { - static int MAX_CHUNK_SIZE = 8192; + public static int MAX_CHUNK_SIZE = 8192; - boolean handlesBodyType(final Request request); + public abstract boolean handlesBodyType(final Request request); - boolean doHandle(final FilterChainContext ctx, final Request request, final HttpRequestPacket requestPacket) throws IOException; + public abstract boolean doHandle(final FilterChainContext ctx, + final Request request, final HttpRequestPacket requestPacket) + throws IOException; + + /** + * Tries to predict request content-length based on the {@link Request}. + * Not all the BodyHandlers can predict the content-length in advance. + * + * @param request + * @return the content-length, or -1 if the content-length can't be + * predicted + */ + protected long getContentLength(final Request request) { + return request.getContentLength(); + } } diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyHandlerFactory.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyHandlerFactory.java index 44c7677e40..8b5cf4ff63 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyHandlerFactory.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/BodyHandlerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -27,7 +27,7 @@ public BodyHandlerFactory(GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider) { new ParamsBodyHandler(grizzlyAsyncHttpProvider),// new StreamDataBodyHandler(),// new PartsBodyHandler(),// - new FileBodyHandler(),// + new FileBodyHandler(grizzlyAsyncHttpProvider),// new BodyGeneratorBodyHandler() // }; } @@ -39,7 +39,8 @@ public BodyHandler getBodyHandler(final Request request) { return h; } } - return new NoBodyHandler(); + + return null; } } // END BodyHandlerFactory 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 7731b92f26..2a16c06b60 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -24,11 +24,12 @@ import java.io.IOException; -public final class ByteArrayBodyHandler implements BodyHandler { +public final class ByteArrayBodyHandler extends BodyHandler { private final boolean compressionEnabled; - public ByteArrayBodyHandler(GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider) { + public ByteArrayBodyHandler( + final GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider) { compressionEnabled = grizzlyAsyncHttpProvider.getClientConfig().isCompressionEnabled(); } @@ -39,7 +40,8 @@ public boolean handlesBodyType(final Request request) { } @SuppressWarnings({ "unchecked" }) - public boolean doHandle(final FilterChainContext ctx, final Request request, final HttpRequestPacket requestPacket) throws IOException { + public boolean doHandle(final FilterChainContext ctx, final Request request, + final HttpRequestPacket requestPacket) throws IOException { final byte[] data = request.getByteData(); final MemoryManager mm = ctx.getMemoryManager(); @@ -54,4 +56,13 @@ public boolean doHandle(final FilterChainContext ctx, final Request request, fin ctx.write(content, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null)); return true; } + + @Override + protected long getContentLength(final Request request) { + if (request.getContentLength() >= 0) { + return request.getContentLength(); + } + + return compressionEnabled ? -1 : request.getByteData().length; + } } diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ExpectHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ExpectHandler.java index cdb1553a03..f322aa80dc 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ExpectHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ExpectHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -19,7 +19,7 @@ import java.io.IOException; -public final class ExpectHandler implements BodyHandler { +public final class ExpectHandler extends BodyHandler { private final BodyHandler delegate; private Request request; @@ -41,6 +41,13 @@ public boolean handlesBodyType(Request request) { public boolean doHandle(FilterChainContext ctx, Request request, HttpRequestPacket requestPacket) throws IOException { this.request = request; this.requestPacket = requestPacket; + + // Set content-length if possible + final long contentLength = delegate.getContentLength(request); + if (contentLength != -1) { + requestPacket.setContentLengthLong(contentLength); + } + ctx.write(requestPacket, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null)); return true; } 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 d957bf0ce6..70bed73210 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -31,14 +31,22 @@ import java.io.FileInputStream; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; +import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; -public final class FileBodyHandler implements BodyHandler { +public final class FileBodyHandler extends BodyHandler { private static final boolean SEND_FILE_SUPPORT; static { SEND_FILE_SUPPORT = configSendFileSupport(); } + private final boolean compressionEnabled; + + public FileBodyHandler( + final GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider) { + compressionEnabled = grizzlyAsyncHttpProvider.getClientConfig().isCompressionEnabled(); + } + // ------------------------------------------------ Methods from BodyHandler public boolean handlesBodyType(final Request request) { @@ -51,7 +59,7 @@ public boolean doHandle(final FilterChainContext ctx, final Request request, fin final File f = request.getFile(); requestPacket.setContentLengthLong(f.length()); final HttpTxContext context = HttpTxContext.get(ctx); - if (!SEND_FILE_SUPPORT || requestPacket.isSecure()) { + if (compressionEnabled || !SEND_FILE_SUPPORT || requestPacket.isSecure()) { final FileInputStream fis = new FileInputStream(request.getFile()); final MemoryManager mm = ctx.getMemoryManager(); AtomicInteger written = new AtomicInteger(); @@ -98,6 +106,15 @@ public void completed(WriteResult result) { return true; } + @Override + protected long getContentLength(final Request request) { + if (request.getContentLength() >= 0) { + return request.getContentLength(); + } + + return compressionEnabled ? -1 : request.getFile().length(); + } + // --------------------------------------------------------- Private Methods private static void notifyHandlerIfNeeded(final HttpTxContext context, final HttpRequestPacket requestPacket, diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/NoBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/NoBodyHandler.java index 879e894ea1..c3865952bf 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/NoBodyHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/NoBodyHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -21,7 +21,7 @@ import java.io.IOException; -public final class NoBodyHandler implements BodyHandler { +public final class NoBodyHandler extends BodyHandler { // -------------------------------------------- Methods from BodyHandler diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ParamsBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ParamsBodyHandler.java index 8cf8992e5c..69c4a63236 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ParamsBodyHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ParamsBodyHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -31,7 +31,7 @@ import java.util.List; import java.util.Map; -public final class ParamsBodyHandler implements BodyHandler { +public final class ParamsBodyHandler extends BodyHandler { private final boolean compressionEnabled; 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 d7ffcd1683..11171d06a7 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -31,7 +31,7 @@ import java.io.IOException; import java.util.List; -public final class PartsBodyHandler implements BodyHandler { +public final class PartsBodyHandler extends BodyHandler { // -------------------------------------------- Methods from BodyHandler diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/StreamDataBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/StreamDataBodyHandler.java index 2647bd7c2a..79eb603242 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/StreamDataBodyHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/StreamDataBodyHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -25,7 +25,7 @@ import java.io.IOException; import java.io.InputStream; -public final class StreamDataBodyHandler implements BodyHandler { +public final class StreamDataBodyHandler extends BodyHandler { // -------------------------------------------- Methods from BodyHandler diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/StringBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/StringBodyHandler.java index bf1e54855e..7785c1fc87 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/StringBodyHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/StringBodyHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -25,7 +25,7 @@ import java.io.IOException; -public final class StringBodyHandler implements BodyHandler { +public final class StringBodyHandler extends BodyHandler { private final GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider; public StringBodyHandler(GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider) { 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 951a206951..90e57d95d1 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 @@ -73,7 +73,6 @@ import org.glassfish.grizzly.websockets.Version; import org.slf4j.Logger; -import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; @@ -198,7 +197,9 @@ private static void recycleRequestResponsePackets(final Connection c, final Http } } - private boolean sendAsGrizzlyRequest(final RequestInfoHolder requestInfoHolder, final FilterChainContext ctx) throws IOException { + private boolean sendAsGrizzlyRequest( + final RequestInfoHolder requestInfoHolder, + final FilterChainContext ctx) throws IOException { HttpTxContext httpTxContext = requestInfoHolder.getHttpTxContext(); if (httpTxContext == null) { @@ -230,18 +231,25 @@ private boolean sendAsGrizzlyRequest(final RequestInfoHolder requestInfoHolder, if (requestPacket == null) { requestPacket = new HttpRequestPacketImpl(); } - requestPacket.setMethod(request.getMethod()); + + final Method method = Method.valueOf(request.getMethod()); + + requestPacket.setMethod(method); requestPacket.setProtocol(Protocol.HTTP_1_1); // Special handling for CONNECT. - if (Method.CONNECT.matchesMethod(request.getMethod())) { + if (method == Method.CONNECT) { final int port = uri.getPort(); requestPacket.setRequestURI(uri.getHost() + ':' + (port == -1 ? 443 : port)); } else { requestPacket.setRequestURI(uri.getPath()); } - if (Utils.requestHasEntityBody(request)) { + final BodyHandler bodyHandler = isPayloadAllowed(method) ? + bodyHandlerFactory.getBodyHandler(request) : + null; + + if (bodyHandler != null) { final long contentLength = request.getContentLength(); if (contentLength >= 0) { requestPacket.setContentLengthLong(contentLength); @@ -301,35 +309,25 @@ private boolean sendAsGrizzlyRequest(final RequestInfoHolder requestInfoHolder, requestPacketLocal.getProcessingState().setHttpContext(httpCtx); requestPacketLocal.setConnection(c); - return sendRequest(sendingCtx, request, requestPacketLocal); + return sendRequest(sendingCtx, request, requestPacketLocal, + wrapWithExpectHandlerIfNeeded(bodyHandler, requestPacket)); } @SuppressWarnings("unchecked") - public boolean sendRequest(final FilterChainContext ctx, final Request request, final HttpRequestPacket requestPacket) + public boolean sendRequest(final FilterChainContext ctx, + final Request request, final HttpRequestPacket requestPacket, + final BodyHandler bodyHandler) throws IOException { boolean isWriteComplete = true; - if (Utils.requestHasEntityBody(request)) { + if (bodyHandler != null) { 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); + context.setBodyHandler(bodyHandler); if (logger.isDebugEnabled()) { logger.debug("REQUEST: {}", requestPacket); } - isWriteComplete = handler.doHandle(ctx, request, requestPacket); + isWriteComplete = bodyHandler.doHandle(ctx, request, requestPacket); } else { HttpContent content = HttpContent.builder(requestPacket).last(true).build(); if (logger.isDebugEnabled()) { @@ -359,6 +357,31 @@ private static FilterChainContext checkAndHandleFilterChainUpdate(final FilterCh return ctxLocal; } + /** + * check if we need to wrap the BodyHandler with ExpectHandler + */ + private static BodyHandler wrapWithExpectHandlerIfNeeded( + final BodyHandler bodyHandler, + final HttpRequestPacket requestPacket) { + + if (bodyHandler == null) { + return null; + } + + // check if we need to wrap the BodyHandler with ExpectHandler + final MimeHeaders headers = requestPacket.getHeaders(); + final int expectHeaderIdx = headers.indexOf(Header.Expect, 0); + + return expectHeaderIdx != -1 + && headers.getValue(expectHeaderIdx).equalsIgnoreCase("100-Continue") + ? new ExpectHandler(bodyHandler) + : bodyHandler; + } + + private static boolean isPayloadAllowed(final Method method) { + return method.getPayloadExpectation() != Method.PayloadExpectation.NOT_ALLOWED; + } + private static void initTransferCompletionHandler(final Request request, final AsyncHandler h) throws IOException { if (h instanceof TransferCompletionHandler) { final FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(request.getHeaders()); From 82cb11a779ae67f39c1f8359e192e56540bfc384 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 5 Jun 2014 17:38:32 +0200 Subject: [PATCH 0032/2020] Curse people setting public modifier on interface methods --- .../AsyncHttpClientRegistry.java | 1 + .../AsyncHttpProviderConfig.java | 8 +-- .../main/java/org/asynchttpclient/Body.java | 1 - .../org/asynchttpclient/BodyConsumer.java | 1 - .../asynchttpclient/ProgressAsyncHandler.java | 1 - .../java/org/asynchttpclient/Request.java | 57 ++++++++++--------- .../ResumableBodyConsumer.java | 1 - .../asynchttpclient/SignatureCalculator.java | 4 +- .../org/asynchttpclient/ThrowableHandler.java | 1 - .../org/asynchttpclient/UpgradeHandler.java | 1 - .../filter/IOExceptionFilter.java | 2 +- .../asynchttpclient/filter/RequestFilter.java | 2 +- .../filter/ResponseFilter.java | 2 +- .../listener/TransferListener.java | 13 +++-- .../resumable/ResumableAsyncHandler.java | 8 +-- .../resumable/ResumableListener.java | 9 ++- .../simple/SimpleAHCTransferListener.java | 2 +- .../spnego/SpnegoTokenGenerator.java | 1 - .../asynchttpclient/util/HostnameChecker.java | 4 +- .../asynchttpclient/websocket/WebSocket.java | 1 + .../websocket/WebSocketByteListener.java | 1 - .../websocket/WebSocketListener.java | 1 - .../websocket/WebSocketPingListener.java | 1 - .../websocket/WebSocketPongListener.java | 1 - .../websocket/WebSocketTextListener.java | 1 - .../asynchttpclient/extra/HttpProgress.java | 1 - .../request/body/FeedableBodyGenerator.java | 2 +- 27 files changed, 60 insertions(+), 68 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientRegistry.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientRegistry.java index 740313380a..5e05afa780 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientRegistry.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientRegistry.java @@ -79,3 +79,4 @@ public interface AsyncHttpClientRegistry { void clearAllInstances(); } + diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpProviderConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpProviderConfig.java index 8ba404b98b..f511580c9b 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpProviderConfig.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpProviderConfig.java @@ -29,7 +29,7 @@ public interface AsyncHttpProviderConfig { * @param value the value of the property * @return this instance of AsyncHttpProviderConfig */ - public AsyncHttpProviderConfig addProperty(U name, V value); + AsyncHttpProviderConfig addProperty(U name, V value); /** * Return the value associated with the property's name @@ -37,7 +37,7 @@ public interface AsyncHttpProviderConfig { * @param name * @return this instance of AsyncHttpProviderConfig */ - public V getProperty(U name); + V getProperty(U name); /** * Remove the value associated with the property's name @@ -45,12 +45,12 @@ public interface AsyncHttpProviderConfig { * @param name * @return true if removed */ - public V removeProperty(U name); + V removeProperty(U name); /** * Return the curent entry set. * * @return a the curent entry set. */ - public Set> propertiesSet(); + Set> propertiesSet(); } diff --git a/api/src/main/java/org/asynchttpclient/Body.java b/api/src/main/java/org/asynchttpclient/Body.java index dacf0642d4..5a39306b2d 100644 --- a/api/src/main/java/org/asynchttpclient/Body.java +++ b/api/src/main/java/org/asynchttpclient/Body.java @@ -44,5 +44,4 @@ public interface Body extends Closeable { * @throws IOException */ void close() throws IOException; - } diff --git a/api/src/main/java/org/asynchttpclient/BodyConsumer.java b/api/src/main/java/org/asynchttpclient/BodyConsumer.java index ccebd5e8b0..0e93652bb1 100644 --- a/api/src/main/java/org/asynchttpclient/BodyConsumer.java +++ b/api/src/main/java/org/asynchttpclient/BodyConsumer.java @@ -36,5 +36,4 @@ public interface BodyConsumer extends Closeable { * @throws IOException */ void close() throws IOException; - } diff --git a/api/src/main/java/org/asynchttpclient/ProgressAsyncHandler.java b/api/src/main/java/org/asynchttpclient/ProgressAsyncHandler.java index a562addc42..bde0d5dd52 100644 --- a/api/src/main/java/org/asynchttpclient/ProgressAsyncHandler.java +++ b/api/src/main/java/org/asynchttpclient/ProgressAsyncHandler.java @@ -44,5 +44,4 @@ public interface ProgressAsyncHandler extends AsyncHandler { * @return a {@link AsyncHandler.STATE} telling to CONTINUE or ABORT the current processing. */ STATE onContentWriteProgress(long amount, long current, long total); - } diff --git a/api/src/main/java/org/asynchttpclient/Request.java b/api/src/main/java/org/asynchttpclient/Request.java index 2dd3864b98..248f6e5e5e 100644 --- a/api/src/main/java/org/asynchttpclient/Request.java +++ b/api/src/main/java/org/asynchttpclient/Request.java @@ -44,43 +44,43 @@ public interface Request { * * @return the request's method name (GET, POST, etc.) */ - public String getMethod(); + String getMethod(); /** * Return the decoded url * * @return the decoded url */ - public String getUrl(); + String getUrl(); - public URI getOriginalURI(); + URI getOriginalURI(); - public URI getURI(); + URI getURI(); - public URI getRawURI(); + URI getRawURI(); /** * Return the InetAddress to override * * @return the InetAddress */ - public InetAddress getInetAddress(); + InetAddress getInetAddress(); - public InetAddress getLocalAddress(); + InetAddress getLocalAddress(); /** * Return the undecoded url * * @return the undecoded url */ - public String getRawUrl(); + String getRawUrl(); /** * Return the current set of Headers. * * @return a {@link FluentCaseInsensitiveStringsMap} contains headers. */ - public FluentCaseInsensitiveStringsMap getHeaders(); + FluentCaseInsensitiveStringsMap getHeaders(); /** * @return return true if request headers have been added, @@ -95,128 +95,129 @@ public interface Request { * * @return an unmodifiable Collection of Cookies */ - public Collection getCookies(); + Collection getCookies(); /** * Return the current request's body as a byte array * * @return a byte array of the current request's body. */ - public byte[] getByteData(); + byte[] getByteData(); /** * Return the current request's body as a string * * @return an String representation of the current request's body. */ - public String getStringData(); + String getStringData(); /** * Return the current request's body as an InputStream * * @return an InputStream representation of the current request's body. */ - public InputStream getStreamData(); + InputStream getStreamData(); /** * Return the current request's body generator. * * @return A generator for the request body. */ - public BodyGenerator getBodyGenerator(); + 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. */ - public long getContentLength(); + long getContentLength(); /** * Return the current parameters. * * @return a {@link FluentStringsMap} of parameters. */ - public FluentStringsMap getParams(); + FluentStringsMap getParams(); /** * Return the current {@link Part} * * @return the current {@link Part} */ - public List getParts(); + List getParts(); /** * Return the virtual host value. * * @return the virtual host value. */ - public String getVirtualHost(); + String getVirtualHost(); /** * Return the query params. * * @return {@link FluentStringsMap} of query string */ - public FluentStringsMap getQueryParams(); + FluentStringsMap getQueryParams(); /** * Return the {@link ProxyServer} * * @return the {@link ProxyServer} */ - public ProxyServer getProxyServer(); + ProxyServer getProxyServer(); /** * Return the {@link Realm} * * @return the {@link Realm} */ - public Realm getRealm(); + Realm getRealm(); /** * Return the {@link File} to upload. * * @return the {@link File} to upload. */ - public File getFile(); + File getFile(); /** * Return the true> to follow redirect * * @return the true> to follow redirect */ - public boolean isRedirectEnabled(); + boolean isRedirectEnabled(); /** * * @return true> if request's redirectEnabled setting * should be used in place of client's */ - public boolean isRedirectOverrideSet(); + boolean isRedirectOverrideSet(); /** * Return the request time out in milliseconds. * * @return requestTimeoutInMs. */ - public int getRequestTimeoutInMs(); + int getRequestTimeoutInMs(); /** * Return the HTTP Range header value, or * * @return the range header value, or 0 is not set. */ - public long getRangeOffset(); + long getRangeOffset(); /** * Return the encoding value used when encoding the request's body. * * @return the encoding value used when encoding the request's body. */ - public String getBodyEncoding(); + String getBodyEncoding(); - public boolean isUseRawUrl(); + boolean isUseRawUrl(); ConnectionPoolKeyStrategy getConnectionPoolKeyStrategy(); } + diff --git a/api/src/main/java/org/asynchttpclient/ResumableBodyConsumer.java b/api/src/main/java/org/asynchttpclient/ResumableBodyConsumer.java index 3f327446c9..42e71536ce 100644 --- a/api/src/main/java/org/asynchttpclient/ResumableBodyConsumer.java +++ b/api/src/main/java/org/asynchttpclient/ResumableBodyConsumer.java @@ -33,5 +33,4 @@ public interface ResumableBodyConsumer extends BodyConsumer { * @throws IOException */ long getTransferredBytes() throws IOException; - } diff --git a/api/src/main/java/org/asynchttpclient/SignatureCalculator.java b/api/src/main/java/org/asynchttpclient/SignatureCalculator.java index 6e0966437f..2f907b9390 100644 --- a/api/src/main/java/org/asynchttpclient/SignatureCalculator.java +++ b/api/src/main/java/org/asynchttpclient/SignatureCalculator.java @@ -35,5 +35,7 @@ public interface SignatureCalculator { * @param request Request that is being built; needed to access content to * be signed */ - public void calculateAndAddSignature(String url, Request request, RequestBuilderBase requestBuilder); + void calculateAndAddSignature(String url, + Request request, + RequestBuilderBase requestBuilder); } diff --git a/api/src/main/java/org/asynchttpclient/ThrowableHandler.java b/api/src/main/java/org/asynchttpclient/ThrowableHandler.java index cbafe59011..1bc28ea824 100644 --- a/api/src/main/java/org/asynchttpclient/ThrowableHandler.java +++ b/api/src/main/java/org/asynchttpclient/ThrowableHandler.java @@ -19,5 +19,4 @@ public interface ThrowableHandler { void onThrowable(Throwable t); - } diff --git a/api/src/main/java/org/asynchttpclient/UpgradeHandler.java b/api/src/main/java/org/asynchttpclient/UpgradeHandler.java index 9ed5e4a7b0..e541e67459 100644 --- a/api/src/main/java/org/asynchttpclient/UpgradeHandler.java +++ b/api/src/main/java/org/asynchttpclient/UpgradeHandler.java @@ -33,5 +33,4 @@ public interface UpgradeHandler { * @param t a {@link Throwable} */ void onFailure(Throwable t); - } diff --git a/api/src/main/java/org/asynchttpclient/filter/IOExceptionFilter.java b/api/src/main/java/org/asynchttpclient/filter/IOExceptionFilter.java index 9473146273..99b3ee2ee0 100644 --- a/api/src/main/java/org/asynchttpclient/filter/IOExceptionFilter.java +++ b/api/src/main/java/org/asynchttpclient/filter/IOExceptionFilter.java @@ -25,5 +25,5 @@ public interface IOExceptionFilter { * @return {@link FilterContext}. The {@link FilterContext} instance may not the same as the original one. * @throws FilterException to interrupt the filter processing. */ - public FilterContext filter(FilterContext ctx) throws FilterException; + FilterContext filter(FilterContext ctx) throws FilterException; } diff --git a/api/src/main/java/org/asynchttpclient/filter/RequestFilter.java b/api/src/main/java/org/asynchttpclient/filter/RequestFilter.java index 520db598c5..9116ed0c1f 100644 --- a/api/src/main/java/org/asynchttpclient/filter/RequestFilter.java +++ b/api/src/main/java/org/asynchttpclient/filter/RequestFilter.java @@ -26,5 +26,5 @@ public interface RequestFilter { * @return {@link FilterContext}. The {@link FilterContext} instance may not the same as the original one. * @throws FilterException to interrupt the filter processing. */ - public FilterContext filter(FilterContext ctx) throws FilterException; + FilterContext filter(FilterContext ctx) throws FilterException; } diff --git a/api/src/main/java/org/asynchttpclient/filter/ResponseFilter.java b/api/src/main/java/org/asynchttpclient/filter/ResponseFilter.java index cdad3ce23c..a49a71f948 100644 --- a/api/src/main/java/org/asynchttpclient/filter/ResponseFilter.java +++ b/api/src/main/java/org/asynchttpclient/filter/ResponseFilter.java @@ -29,5 +29,5 @@ public interface ResponseFilter { * @return {@link FilterContext}. The {@link FilterContext} instance may not the same as the original one. * @throws FilterException to interrupt the filter processing. */ - public FilterContext filter(FilterContext ctx) throws FilterException; + FilterContext filter(FilterContext ctx) throws FilterException; } diff --git a/api/src/main/java/org/asynchttpclient/listener/TransferListener.java b/api/src/main/java/org/asynchttpclient/listener/TransferListener.java index d61a675295..1043b3c561 100644 --- a/api/src/main/java/org/asynchttpclient/listener/TransferListener.java +++ b/api/src/main/java/org/asynchttpclient/listener/TransferListener.java @@ -24,19 +24,19 @@ public interface TransferListener { /** * Invoked when the request bytes are starting to get send. */ - public void onRequestHeadersSent(FluentCaseInsensitiveStringsMap headers); + void onRequestHeadersSent(FluentCaseInsensitiveStringsMap headers); /** * Invoked when the response bytes are starting to get received. */ - public void onResponseHeadersReceived(FluentCaseInsensitiveStringsMap headers); + void onResponseHeadersReceived(FluentCaseInsensitiveStringsMap headers); /** * Invoked every time response's chunk are received. * * @param bytes a {@link byte[]} */ - public void onBytesReceived(byte[] bytes) throws IOException; + void onBytesReceived(byte[] bytes) throws IOException; /** * Invoked every time request's chunk are sent. @@ -45,17 +45,18 @@ public interface TransferListener { * @param current The amount of bytes transferred * @param total The total number of bytes transferred */ - public void onBytesSent(long amount, long current, long total); + void onBytesSent(long amount, long current, long total); /** * Invoked when the response bytes are been fully received. */ - public void onRequestResponseCompleted(); + void onRequestResponseCompleted(); /** * Invoked when there is an unexpected issue. * * @param t a {@link Throwable} */ - public void onThrowable(Throwable t); + void onThrowable(Throwable t); } + diff --git a/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java b/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java index 6b301b7f93..2463186d4c 100644 --- a/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java +++ b/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java @@ -254,14 +254,14 @@ public static interface ResumableProcessor { * @param key a key. The recommended way is to use an url. * @param transferredBytes The number of bytes successfully transferred. */ - public void put(String key, long transferredBytes); + void put(String key, long transferredBytes); /** * Remove the key associate value. * * @param key key from which the value will be discarded */ - public void remove(String key); + void remove(String key); /** * Save the current {@link Map} instance which contains information about the current transfer state. @@ -269,14 +269,14 @@ public static interface ResumableProcessor { * * @param map */ - public void save(Map map); + void save(Map map); /** * Load the {@link Map} in memory, contains information about the transferred bytes. * * @return {@link Map} */ - public Map load(); + Map load(); } diff --git a/api/src/main/java/org/asynchttpclient/resumable/ResumableListener.java b/api/src/main/java/org/asynchttpclient/resumable/ResumableListener.java index 252236cbae..c8af9c6561 100644 --- a/api/src/main/java/org/asynchttpclient/resumable/ResumableListener.java +++ b/api/src/main/java/org/asynchttpclient/resumable/ResumableListener.java @@ -26,18 +26,17 @@ public interface ResumableListener { * @param byteBuffer the current bytes * @throws IOException */ - public void onBytesReceived(ByteBuffer byteBuffer) throws IOException; + void onBytesReceived(ByteBuffer byteBuffer) throws IOException; /** * Invoked when all the bytes has been sucessfully transferred. */ - public void onAllBytesReceived(); + void onAllBytesReceived(); /** * Return the length of previously downloaded bytes. * * @return the length of previously downloaded bytes */ - public long length(); - -} \ No newline at end of file + long length(); +} diff --git a/api/src/main/java/org/asynchttpclient/simple/SimpleAHCTransferListener.java b/api/src/main/java/org/asynchttpclient/simple/SimpleAHCTransferListener.java index b8e300f020..51448ee38e 100644 --- a/api/src/main/java/org/asynchttpclient/simple/SimpleAHCTransferListener.java +++ b/api/src/main/java/org/asynchttpclient/simple/SimpleAHCTransferListener.java @@ -75,5 +75,5 @@ public interface SimpleAHCTransferListener { * @param statusText the received status text. */ void onCompleted(String url, int statusCode, String statusText); - } + diff --git a/api/src/main/java/org/asynchttpclient/spnego/SpnegoTokenGenerator.java b/api/src/main/java/org/asynchttpclient/spnego/SpnegoTokenGenerator.java index ead1c19335..ee0b9876c4 100644 --- a/api/src/main/java/org/asynchttpclient/spnego/SpnegoTokenGenerator.java +++ b/api/src/main/java/org/asynchttpclient/spnego/SpnegoTokenGenerator.java @@ -51,5 +51,4 @@ public interface SpnegoTokenGenerator { byte[] generateSpnegoDERObject(byte[] kerberosTicket) throws IOException; - } diff --git a/api/src/main/java/org/asynchttpclient/util/HostnameChecker.java b/api/src/main/java/org/asynchttpclient/util/HostnameChecker.java index daf012deca..a25bb467e0 100644 --- a/api/src/main/java/org/asynchttpclient/util/HostnameChecker.java +++ b/api/src/main/java/org/asynchttpclient/util/HostnameChecker.java @@ -21,7 +21,7 @@ */ public interface HostnameChecker { - public void match(String hostname, X509Certificate peerCertificate) throws CertificateException; + void match(String hostname, X509Certificate peerCertificate) throws CertificateException; - public boolean match(String hostname, Principal principal); + boolean match(String hostname, Principal principal); } diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocket.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocket.java index 9e9c981c16..81f7392b99 100644 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocket.java +++ b/api/src/main/java/org/asynchttpclient/websocket/WebSocket.java @@ -108,3 +108,4 @@ public interface WebSocket extends Closeable { */ void close(); } + diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteListener.java index 55f35a3f2b..04dd4075ec 100644 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteListener.java +++ b/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteListener.java @@ -30,5 +30,4 @@ public interface WebSocketByteListener extends WebSocketListener { * @param last if this fragment is the last in the series. */ void onFragment(byte[] fragment, boolean last); - } diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketListener.java index bf187be07c..6c8b7fbc25 100644 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketListener.java +++ b/api/src/main/java/org/asynchttpclient/websocket/WebSocketListener.java @@ -37,5 +37,4 @@ public interface WebSocketListener { * @param t a {@link Throwable} */ void onError(Throwable t); - } diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketPingListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketPingListener.java index 7abc8ab4a2..0567c90c2d 100644 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketPingListener.java +++ b/api/src/main/java/org/asynchttpclient/websocket/WebSocketPingListener.java @@ -22,5 +22,4 @@ public interface WebSocketPingListener extends WebSocketListener { * @param message a byte array */ void onPing(byte[] message); - } diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketPongListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketPongListener.java index f140a2208d..a27e93bfd4 100644 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketPongListener.java +++ b/api/src/main/java/org/asynchttpclient/websocket/WebSocketPongListener.java @@ -22,5 +22,4 @@ public interface WebSocketPongListener extends WebSocketListener { * @param message a byte array */ void onPong(byte[] message); - } diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextListener.java index d52bfad0ea..2ea133b1ec 100644 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextListener.java +++ b/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextListener.java @@ -30,5 +30,4 @@ public interface WebSocketTextListener extends WebSocketListener { * @param last if this fragment is the last of the series. */ void onFragment(String fragment, boolean last); - } diff --git a/extras/jdeferred/src/main/java/org/asynchttpclient/extra/HttpProgress.java b/extras/jdeferred/src/main/java/org/asynchttpclient/extra/HttpProgress.java index 15af6debbc..19a3e22796 100644 --- a/extras/jdeferred/src/main/java/org/asynchttpclient/extra/HttpProgress.java +++ b/extras/jdeferred/src/main/java/org/asynchttpclient/extra/HttpProgress.java @@ -16,5 +16,4 @@ package org.asynchttpclient.extra; public interface HttpProgress { - } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/FeedableBodyGenerator.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/FeedableBodyGenerator.java index 2e8da2d845..97bdc34701 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/FeedableBodyGenerator.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/FeedableBodyGenerator.java @@ -47,7 +47,7 @@ public void feed(final ByteBuffer buffer, final boolean isLast) throws IOExcepti } public static interface FeedListener { - public void onContentAdded(); + void onContentAdded(); } public void setListener(FeedListener listener) { From d8a0355a5afef83dcc22a6afddfbc4b57e0d35ec Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 5 Jun 2014 17:45:37 +0200 Subject: [PATCH 0033/2020] minor clean up --- .../netty/channel/DefaultChannelPool.java | 89 ++++++++----------- 1 file changed, 39 insertions(+), 50 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java index c7d8f94919..245723ff6e 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java @@ -38,7 +38,7 @@ */ public class DefaultChannelPool implements ChannelPool { - private final static Logger log = LoggerFactory.getLogger(DefaultChannelPool.class); + private final static Logger LOGGER = LoggerFactory.getLogger(DefaultChannelPool.class); private final ConcurrentHashMapV8> connectionsPool = new ConcurrentHashMapV8>(); private final ConcurrentHashMapV8 channel2IdleChannel = new ConcurrentHashMapV8(); private final ConcurrentHashMapV8 channel2CreationDate = new ConcurrentHashMapV8(); @@ -81,30 +81,24 @@ private void scheduleNewIdleChannelDetector(TimerTask task) { nettyTimer.newTimeout(task, maxIdleTime, TimeUnit.MILLISECONDS); } - private static class IdleChannel { - final String uri; + private static final class IdleChannel { + final String key; final Channel channel; final long start; - IdleChannel(String uri, Channel channel) { - this.uri = uri; + IdleChannel(String key, Channel channel) { + if (key == null) + throw new NullPointerException("key"); + if (channel == null) + throw new NullPointerException("channel"); + this.key = key; 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; + return this == o || (o instanceof IdleChannel && channel.equals(IdleChannel.class.cast(o).channel)); } @Override @@ -121,11 +115,11 @@ public void run(Timeout timeout) throws Exception { if (closed.get()) return; - if (log.isDebugEnabled()) { + if (LOGGER.isDebugEnabled()) { Set keys = connectionsPool.keySet(); for (String s : keys) { - log.debug("Entry count for : {} : {}", s, connectionsPool.get(s).size()); + LOGGER.debug("Entry count for : {} : {}", s, connectionsPool.get(s).size()); } } @@ -136,7 +130,7 @@ public void run(Timeout timeout) throws Exception { long age = currentTime - idleChannel.start; if (age > maxIdleTime) { - log.debug("Adding Candidate Idle Channel {}", idleChannel.channel); + LOGGER.debug("Adding Candidate Idle Channel {}", idleChannel.channel); // store in an unsynchronized list to minimize the impact on the ConcurrentHashMap. channelsInTimeout.add(idleChannel); @@ -151,28 +145,28 @@ public void run(Timeout timeout) throws Exception { NettyResponseFuture future = (NettyResponseFuture) attachment; if (!future.isDone() && !future.isCancelled()) { - log.debug("Future not in appropriate state %s\n", future); + LOGGER.debug("Future not in appropriate state %s\n", future); continue; } } } if (remove(idleChannel)) { - log.debug("Closing Idle Channel {}", idleChannel.channel); + LOGGER.debug("Closing Idle Channel {}", idleChannel.channel); close(idleChannel.channel); } } - if (log.isTraceEnabled()) { + if (LOGGER.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, + LOGGER.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); + LOGGER.error("uncaught exception!", t); } scheduleNewIdleChannelDetector(timeout.task()); @@ -183,46 +177,42 @@ public void run(Timeout timeout) throws Exception { * {@inheritDoc} */ public boolean offer(String uri, Channel channel) { - if (closed.get()) + if (closed.get() || (!sslConnectionPoolEnabled && uri.startsWith("https"))) 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); + LOGGER.debug("Channel {} expired", channel); return false; } - log.debug("Adding uri: {} for channel {}", uri, channel); + LOGGER.debug("Adding uri: {} for channel {}", uri, channel); Channels.setDefaultAttribute(channel, DiscardEvent.INSTANCE); - ConcurrentLinkedQueue idleConnectionForHost = connectionsPool.get(uri); - if (idleConnectionForHost == null) { + ConcurrentLinkedQueue pooledConnectionForKey = connectionsPool.get(uri); + if (pooledConnectionForKey == null) { ConcurrentLinkedQueue newPool = new ConcurrentLinkedQueue(); - idleConnectionForHost = connectionsPool.putIfAbsent(uri, newPool); - if (idleConnectionForHost == null) - idleConnectionForHost = newPool; + pooledConnectionForKey = connectionsPool.putIfAbsent(uri, newPool); + if (pooledConnectionForKey == null) + pooledConnectionForKey = newPool; } boolean added; - int size = idleConnectionForHost.size(); + int size = pooledConnectionForKey.size(); if (maxConnectionPerHost == -1 || size < maxConnectionPerHost) { IdleChannel idleChannel = new IdleChannel(uri, channel); - synchronized (idleConnectionForHost) { - added = idleConnectionForHost.add(idleChannel); + synchronized (pooledConnectionForKey) { + added = pooledConnectionForKey.add(idleChannel); if (channel2IdleChannel.put(channel, idleChannel) != null) { - log.error("Channel {} already exists in the connections pool!", channel); + LOGGER.error("Channel {} already exists in the connections pool!", channel); } } } else { - log.debug("Maximum number of requests per host reached {} for {}", maxConnectionPerHost, uri); + LOGGER.debug("Maximum number of requests per host reached {} for {}", maxConnectionPerHost, uri); added = false; } return added; @@ -237,13 +227,13 @@ public Channel poll(String uri) { } IdleChannel idleChannel = null; - ConcurrentLinkedQueue idleConnectionForHost = connectionsPool.get(uri); - if (idleConnectionForHost != null) { + ConcurrentLinkedQueue pooledConnectionForKey = connectionsPool.get(uri); + if (pooledConnectionForKey != null) { boolean poolEmpty = false; while (!poolEmpty && idleChannel == null) { - if (idleConnectionForHost.size() > 0) { - synchronized (idleConnectionForHost) { - idleChannel = idleConnectionForHost.poll(); + if (pooledConnectionForKey.size() > 0) { + synchronized (pooledConnectionForKey) { + idleChannel = pooledConnectionForKey.poll(); if (idleChannel != null) { channel2IdleChannel.remove(idleChannel.channel); } @@ -254,7 +244,7 @@ public Channel poll(String uri) { poolEmpty = true; } else if (!idleChannel.channel.isActive() || !idleChannel.channel.isOpen()) { idleChannel = null; - log.trace("Channel not connected or not opened!"); + LOGGER.trace("Channel not connected or not opened!"); } } } @@ -266,12 +256,11 @@ private boolean remove(IdleChannel pooledChannel) { return false; boolean isRemoved = false; - ConcurrentLinkedQueue pooledConnectionForHost = connectionsPool.get(pooledChannel.uri); + ConcurrentLinkedQueue pooledConnectionForHost = connectionsPool.get(pooledChannel.key); if (pooledConnectionForHost != null) { isRemoved = pooledConnectionForHost.remove(pooledChannel); } - isRemoved |= channel2IdleChannel.remove(pooledChannel.channel) != null; - return isRemoved; + return isRemoved |= channel2IdleChannel.remove(pooledChannel.channel) != null; } /** From 77be69e78ee6d6110bd6160aa5633b89d43dae98 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 11 Jun 2014 15:07:58 +0200 Subject: [PATCH 0034/2020] Release 1.8.10 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a2c150d501..07afb045d3 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.8.9 + 1.8.10 ``` From 4e4953c89b7b1d1c6394a99ac1fbf10431d20e26 Mon Sep 17 00:00:00 2001 From: sasurendran Date: Wed, 11 Jun 2014 17:42:20 -0700 Subject: [PATCH 0035/2020] Making changes so that asynchttpclient config default values are picked up from the asynchttpclient.properties in case they are not specified via system property. The search order for the default value for each config property is as such: 1. System Property 2. Property specified in asynchttpclient.properties 3. Default Value specified in code Also introducing JMockit for unit testing. --- .../AsyncHttpClientConfigDefaults.java | 59 ++++---- .../asynchttpclient/util/AsyncImplHelper.java | 2 +- .../org/asynchttpclient/util/MiscUtil.java | 50 ++++++- .../AsyncHttpClientConfigBuilderTest.java | 30 ++++ .../asynchttpclient/AsyncImplHelperMock.java | 23 +++ .../asynchttpclient/util/MiscUtilTest.java | 140 ++++++++++++++++++ pom.xml | 7 + 7 files changed, 281 insertions(+), 30 deletions(-) create mode 100644 api/src/test/java/org/asynchttpclient/AsyncHttpClientConfigBuilderTest.java create mode 100644 api/src/test/java/org/asynchttpclient/AsyncImplHelperMock.java create mode 100644 api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java index edd51169a1..8b40d67c45 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java @@ -12,13 +12,17 @@ */ package org.asynchttpclient; -import static org.asynchttpclient.util.MiscUtil.getBoolean; -import org.asynchttpclient.util.DefaultHostnameVerifier; import javax.net.ssl.HostnameVerifier; +import org.asynchttpclient.util.DefaultHostnameVerifier; +import static org.asynchttpclient.util.MiscUtil.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public final class AsyncHttpClientConfigDefaults { + public final static Logger logger = LoggerFactory.getLogger(AsyncHttpClientConfigDefaults.class); private AsyncHttpClientConfigDefaults() { } @@ -26,47 +30,47 @@ private AsyncHttpClientConfigDefaults() { public static final String ASYNC_CLIENT = AsyncHttpClientConfig.class.getName() + "."; public static int defaultMaxTotalConnections() { - return Integer.getInteger(ASYNC_CLIENT + "maxTotalConnections", -1); + return getIntValue(ASYNC_CLIENT + "maxTotalConnections", -1); } public static int defaultMaxConnectionPerHost() { - return Integer.getInteger(ASYNC_CLIENT + "maxConnectionsPerHost", -1); + return getIntValue(ASYNC_CLIENT + "maxConnectionsPerHost", -1); } public static int defaultConnectionTimeOutInMs() { - return Integer.getInteger(ASYNC_CLIENT + "connectionTimeoutInMs", 60 * 1000); + return getIntValue(ASYNC_CLIENT + "connectionTimeoutInMs", 60 * 1000); } public static int defaultIdleConnectionInPoolTimeoutInMs() { - return Integer.getInteger(ASYNC_CLIENT + "idleConnectionInPoolTimeoutInMs", 60 * 1000); + return getIntValue(ASYNC_CLIENT + "idleConnectionInPoolTimeoutInMs", 60 * 1000); } public static int defaultIdleConnectionTimeoutInMs() { - return Integer.getInteger(ASYNC_CLIENT + "idleConnectionTimeoutInMs", 60 * 1000); + return getIntValue(ASYNC_CLIENT + "idleConnectionTimeoutInMs", 60 * 1000); } public static int defaultRequestTimeoutInMs() { - return Integer.getInteger(ASYNC_CLIENT + "requestTimeoutInMs", 60 * 1000); + return getIntValue(ASYNC_CLIENT + "requestTimeoutInMs", 60 * 1000); } public static int defaultWebSocketIdleTimeoutInMs() { - return Integer.getInteger(ASYNC_CLIENT + "webSocketTimoutInMS", 15 * 60 * 1000); + return getIntValue(ASYNC_CLIENT + "webSocketTimoutInMS", 15 * 60 * 1000); } public static int defaultMaxConnectionLifeTimeInMs() { - return Integer.getInteger(ASYNC_CLIENT + "maxConnectionLifeTimeInMs", -1); + return getIntValue(ASYNC_CLIENT + "maxConnectionLifeTimeInMs", -1); } public static boolean defaultRedirectEnabled() { - return Boolean.getBoolean(ASYNC_CLIENT + "redirectsEnabled"); + return getBooleanValue(ASYNC_CLIENT + "redirectsEnabled",false); } public static int defaultMaxRedirects() { - return Integer.getInteger(ASYNC_CLIENT + "maxRedirects", 5); + return getIntValue(ASYNC_CLIENT + "maxRedirects", 5); } public static boolean defaultCompressionEnabled() { - return Boolean.getBoolean(ASYNC_CLIENT + "compressionEnabled"); + return getBooleanValue(ASYNC_CLIENT + "compressionEnabled",false); } public static String defaultUserAgent() { @@ -74,48 +78,48 @@ public static String defaultUserAgent() { } public static int defaultIoThreadMultiplier() { - return Integer.getInteger(ASYNC_CLIENT + "ioThreadMultiplier", 2); + return getIntValue(ASYNC_CLIENT + "ioThreadMultiplier", 2); } public static boolean defaultUseProxySelector() { - return Boolean.getBoolean(ASYNC_CLIENT + "useProxySelector"); + return getBooleanValue(ASYNC_CLIENT + "useProxySelector",false); } public static boolean defaultUseProxyProperties() { - return Boolean.getBoolean(ASYNC_CLIENT + "useProxyProperties"); + return getBooleanValue(ASYNC_CLIENT + "useProxyProperties",false); } public static boolean defaultStrict302Handling() { - return Boolean.getBoolean(ASYNC_CLIENT + "strict302Handling"); + return getBooleanValue(ASYNC_CLIENT + "strict302Handling",false); } public static boolean defaultAllowPoolingConnection() { - return getBoolean(ASYNC_CLIENT + "allowPoolingConnection", true); + return getBooleanValue(ASYNC_CLIENT + "allowPoolingConnection", true); } public static boolean defaultUseRelativeURIsWithSSLProxies() { - return getBoolean(ASYNC_CLIENT + "useRelativeURIsWithSSLProxies", true); + return getBooleanValue(ASYNC_CLIENT + "useRelativeURIsWithSSLProxies", true); } // unused/broken, left there for compatibility, fixed in Netty 4 public static int defaultRequestCompressionLevel() { - return Integer.getInteger(ASYNC_CLIENT + "requestCompressionLevel", -1); + return getIntValue(ASYNC_CLIENT + "requestCompressionLevel", -1); } public static int defaultMaxRequestRetry() { - return Integer.getInteger(ASYNC_CLIENT + "maxRequestRetry", 5); + return getIntValue(ASYNC_CLIENT + "maxRequestRetry", 5); } public static boolean defaultAllowSslConnectionPool() { - return getBoolean(ASYNC_CLIENT + "allowSslConnectionPool", true); + return getBooleanValue(ASYNC_CLIENT + "allowSslConnectionPool", true); } public static boolean defaultUseRawUrl() { - return Boolean.getBoolean(ASYNC_CLIENT + "useRawUrl"); + return getBooleanValue(ASYNC_CLIENT + "useRawUrl",false); } public static boolean defaultRemoveQueryParamOnRedirect() { - return getBoolean(ASYNC_CLIENT + "removeQueryParamOnRedirect", true); + return getBooleanValue(ASYNC_CLIENT + "removeQueryParamOnRedirect", true); } public static HostnameVerifier defaultHostnameVerifier() { @@ -123,14 +127,15 @@ public static HostnameVerifier defaultHostnameVerifier() { } public static boolean defaultSpdyEnabled() { - return Boolean.getBoolean(ASYNC_CLIENT + "spdyEnabled"); + return getBooleanValue(ASYNC_CLIENT + "spdyEnabled",false); } public static int defaultSpdyInitialWindowSize() { - return Integer.getInteger(ASYNC_CLIENT + "spdyInitialWindowSize", 10 * 1024 * 1024); + return getIntValue(ASYNC_CLIENT + "spdyInitialWindowSize", 10 * 1024 * 1024); } public static int defaultSpdyMaxConcurrentStreams() { - return Integer.getInteger(ASYNC_CLIENT + "spdyMaxConcurrentStreams", 100); + return getIntValue(ASYNC_CLIENT + "spdyMaxConcurrentStreams", 100); } + } diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncImplHelper.java b/api/src/main/java/org/asynchttpclient/util/AsyncImplHelper.java index a61d2d74d6..47b23bf767 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncImplHelper.java +++ b/api/src/main/java/org/asynchttpclient/util/AsyncImplHelper.java @@ -59,7 +59,7 @@ public static Class getAsyncImplClass(String propertyName) { return asyncHttpClientImplClass; } - private static Properties getAsyncImplProperties() { + public static Properties getAsyncImplProperties() { try { return AccessController.doPrivileged(new PrivilegedExceptionAction() { public Properties run() throws IOException { diff --git a/api/src/main/java/org/asynchttpclient/util/MiscUtil.java b/api/src/main/java/org/asynchttpclient/util/MiscUtil.java index 7a80e00268..65c8db0b11 100644 --- a/api/src/main/java/org/asynchttpclient/util/MiscUtil.java +++ b/api/src/main/java/org/asynchttpclient/util/MiscUtil.java @@ -14,6 +14,9 @@ import java.util.Collection; import java.util.Map; +import java.util.Properties; + +import org.asynchttpclient.AsyncHttpClientConfigDefaults; public class MiscUtil { @@ -40,8 +43,51 @@ public static boolean isNonEmpty(Map map) { return map != null && !map.isEmpty(); } - public static boolean getBoolean(String systemPropName, boolean defaultValue) { + /* public static boolean getBoolean(String systemPropName, boolean defaultValue) { String systemPropValue = System.getProperty(systemPropName); return systemPropValue != null ? systemPropValue.equalsIgnoreCase("true") : defaultValue; - } + }*/ + + public static Integer getIntValue(String property,int defaultValue){ + //Read system property and if not null return that. + Integer value = Integer.getInteger(property); + if(value != null) + return value; + Properties asyncHttpClientConfigProperties = AsyncImplHelper.getAsyncImplProperties(); + if(asyncHttpClientConfigProperties!=null){ + String valueString=asyncHttpClientConfigProperties.getProperty(property); + try{ + //If property is present and is non null parse it. + if(valueString != null) + return Integer.parseInt(valueString); + }catch(NumberFormatException e){ + //If property couldn't be parsed log the error message and return default value. + AsyncHttpClientConfigDefaults.logger.error("Property : " + property + " has value = " + valueString + + " which couldn't be parsed to an int value. Returning default value: " + defaultValue,e); + } + } + return defaultValue; + } + + public static Boolean getBooleanValue(String property,boolean defaultValue){ + String value = System.getProperty(property); + Properties asyncHttpClientConfigProperties = AsyncImplHelper.getAsyncImplProperties(); + //If system property is invalid and property file is present then read value + //from property file + if(!MiscUtil.isValidBooleanValue(value) && asyncHttpClientConfigProperties!=null) + value=asyncHttpClientConfigProperties.getProperty(property); + //If valid value has been found return that value + if(MiscUtil.isValidBooleanValue(value)) + return Boolean.parseBoolean(value); + //If a value has been specified but can't be parsed into a boolean log a message + //stating that value is unparseable and default values are being used. + if(value != null) + AsyncHttpClientConfigDefaults.logger.error("Property : " + property + " has value = " + value + + " which couldn't be parsed to an boolean value. Returning default value: " + defaultValue); + return defaultValue; + } + + private static boolean isValidBooleanValue(String value){ + return value != null && ("true".equalsIgnoreCase(value)||"false".equalsIgnoreCase(value)); + } } diff --git a/api/src/test/java/org/asynchttpclient/AsyncHttpClientConfigBuilderTest.java b/api/src/test/java/org/asynchttpclient/AsyncHttpClientConfigBuilderTest.java new file mode 100644 index 0000000000..02d532f3d4 --- /dev/null +++ b/api/src/test/java/org/asynchttpclient/AsyncHttpClientConfigBuilderTest.java @@ -0,0 +1,30 @@ +package org.asynchttpclient; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class AsyncHttpClientConfigBuilderTest { + + @Test + public void testDefaultConfigValues(){ + AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().build(); + Assert.assertEquals(config.getConnectionTimeoutInMs(),60000); + Assert.assertEquals(config.getRequestTimeoutInMs(),60000); + Assert.assertEquals(config.getIdleConnectionTimeoutInMs(),60000); + Assert.assertFalse(config.isCompressionEnabled()); + Assert.assertFalse(config.isRedirectEnabled()); + System.setProperty(AsyncHttpClientConfig.ASYNC_CLIENT+"connectionTimeoutInMs","1000"); + System.setProperty(AsyncHttpClientConfig.ASYNC_CLIENT+"requestTimeoutInMs","500"); + System.setProperty(AsyncHttpClientConfig.ASYNC_CLIENT+"compressionEnabled","true"); + System.setProperty(AsyncHttpClientConfig.ASYNC_CLIENT+"redirectsEnabled","true"); + config = new AsyncHttpClientConfig.Builder().build(); + Assert.assertEquals(config.getConnectionTimeoutInMs(),1000); + Assert.assertEquals(config.getRequestTimeoutInMs(), 500); + Assert.assertTrue(config.isCompressionEnabled()); + Assert.assertTrue(config.isRedirectEnabled()); + System.clearProperty(AsyncHttpClientConfig.ASYNC_CLIENT+"connectionTimeoutInMs"); + System.clearProperty(AsyncHttpClientConfig.ASYNC_CLIENT+"requestTimeoutInMs"); + System.clearProperty(AsyncHttpClientConfig.ASYNC_CLIENT+"compressionEnabled"); + System.clearProperty(AsyncHttpClientConfig.ASYNC_CLIENT+"defaultRedirectsEnabled"); + } +} diff --git a/api/src/test/java/org/asynchttpclient/AsyncImplHelperMock.java b/api/src/test/java/org/asynchttpclient/AsyncImplHelperMock.java new file mode 100644 index 0000000000..562af7a3dd --- /dev/null +++ b/api/src/test/java/org/asynchttpclient/AsyncImplHelperMock.java @@ -0,0 +1,23 @@ +package org.asynchttpclient; + +import java.util.Properties; + +import mockit.Mock; +import mockit.MockUp; + +import org.asynchttpclient.util.AsyncImplHelper; + +public class AsyncImplHelperMock extends MockUp { + + private static Properties properties; + + public AsyncImplHelperMock(Properties properties){ + this.properties=properties; + } + + @Mock + public Properties getAsyncImplProperties() { + return properties; + } + +} diff --git a/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java b/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java new file mode 100644 index 0000000000..d9f4bbed19 --- /dev/null +++ b/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java @@ -0,0 +1,140 @@ +package org.asynchttpclient.util; + +import java.util.Properties; + +import mockit.Deencapsulation; + +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.AsyncImplHelperMock; +import org.asynchttpclient.AsyncHttpClientConfig.Builder; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class MiscUtilTest { + private final Integer MY_SPECIAL_INT_VALUE = 10; + private final Integer MY_SPECIAL_SYSTEM_INT_VALUE = 100; + private final String MY_SPECIAL_INT_PROPERTY = "my.special.int.property"; + private final String MY_SPECIAL_BOOLEAN_PROPERTY = "my.special.boolean.property"; + private final Integer MY_SPECIAL_INT_DEFAULT_VALUE = -100; + + + @Test + public void testGetIntegerValue() { + // Setup a AsyncImplHelperMock that returns a mock + // asynchttpclient.properties with a value + // set for 'my.special.int.property' property + Properties properties = new Properties(); + properties.setProperty(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_VALUE.toString()); + AsyncImplHelperMock asyncImplHelperMock = new AsyncImplHelperMock(properties); + + + // Assert that the getIntValue() method returns 10 + Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, -1), MY_SPECIAL_INT_VALUE); + // Set a system property that overrides the value in the + // asynchttpclient.properties + System.setProperty(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_SYSTEM_INT_VALUE.toString()); + // Assert 100 is returned, i.e. system property takes precedence over + // property in asynchttpclient.properties + Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, -1),MY_SPECIAL_SYSTEM_INT_VALUE); + // Clear the system property + System.clearProperty(MY_SPECIAL_INT_PROPERTY); + // Assert that the value set in asynchttpclient.properties is returned + Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, -1), MY_SPECIAL_INT_VALUE); + // Set a corrupt system property + System.setProperty(MY_SPECIAL_INT_PROPERTY, "corrupt property"); + // Assert that the value set in asynchttpclient.properties is returned + // even though corrupt system property is set. + Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, -1), MY_SPECIAL_INT_VALUE); + System.clearProperty(MY_SPECIAL_INT_PROPERTY); + } + + @Test + public void testGetBooleanValue() { + // Setup a AsyncImplHelperMock that returns a mock + // asynchttpclient.properties with a value + // set for 'my.special.int.property' property + Properties properties = new Properties(); + properties.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "true"); + AsyncImplHelperMock asyncImplHelperMock = new AsyncImplHelperMock(properties); + + + // Assert that the getBooleanValue() method returns TRUE + Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); + // Set a system property that overrides the value in the + // asynchttpclient.properties + System.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "false"); + // Assert false is returned, i.e. system property takes precedence over + // property in asynchttpclient.properties + Assert.assertFalse(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, true)); + // Clear the system property + System.clearProperty(MY_SPECIAL_BOOLEAN_PROPERTY); + // Assert that the value set in asynchttpclient.properties is returned + Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); + // Set a corrupt system property + System.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "corrupt property"); + // Assert that the value set in asynchttpclient.properties is returned + // even though corrupt system property is set. + Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); + System.clearProperty(MY_SPECIAL_BOOLEAN_PROPERTY); + } + + @Test + public void testGetDefaultIntegerValue() { + + // Assert that the getIntValue() method returns the default value if + // Properties is not present + Assert.assertEquals( + MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), + MY_SPECIAL_INT_DEFAULT_VALUE); + // Setup up a mock of a asynchttpclient.properties that initially is empty + Properties properties = new Properties(); + AsyncImplHelperMock asyncImplHelperMock = new AsyncImplHelperMock(properties); + + + // Assert that the getIntValue() method returns the default value if there is no + // property set in the asynchttpclient.properties + Assert.assertEquals( + MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), + MY_SPECIAL_INT_DEFAULT_VALUE); + //Now set a corrupt value in the asynchttpclient.properties + properties.setProperty(MY_SPECIAL_INT_PROPERTY, "corrupt property"); + // Assert that the getIntValue() method returns the default value if there is a corrupt + // property set in the asynchttpclient.properties + Assert.assertEquals( + MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), + MY_SPECIAL_INT_DEFAULT_VALUE); + // Set a corrupt system property + System.setProperty(MY_SPECIAL_INT_PROPERTY, "corrupt property"); + // Assert that even though values set in asynchttpclient.properties and system property is corrupt the default value is returned + Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), + MY_SPECIAL_INT_DEFAULT_VALUE); + System.clearProperty(MY_SPECIAL_INT_PROPERTY); + } + + + + @Test + public void testGetDefaultBooleanValue() { + // Assert that the getBooleanValue() method returns the default value if + // asynchttpclient.properties is not present + Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, true)); + // Setup up a mock of a asynchttpclient.properties that initially is empty + Properties properties = new Properties(); + AsyncImplHelperMock asyncImplHelperMock = new AsyncImplHelperMock(properties); + + + // Assert that the getBooleanValue() method returns the default value if there is no + // property set in the asynchttpclient.properties + Assert.assertTrue(!MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); + //Now set a corrupt value in the asynchttpclient.properties + properties.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "corrupt property"); + // Assert that the getBooleanValue() method returns the default value if there is a corrupt + // property set in the asynchttpclient.properties + Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, true)); + // Set a corrupt system property + System.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "corrupt property"); + // Assert that even though values set in asynchttpclient.properties and system property is corrupt the default value is returned + Assert.assertTrue(!MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); + System.clearProperty(MY_SPECIAL_INT_PROPERTY); + } +} diff --git a/pom.xml b/pom.xml index a4e54d8ded..47d662b815 100644 --- a/pom.xml +++ b/pom.xml @@ -519,6 +519,12 @@ ${privilegedaccessor.version} test + + com.googlecode.jmockit + jmockit + ${jmockit.version} + test + http://oss.sonatype.org/content/repositories/snapshots @@ -533,6 +539,7 @@ 6.0.29 2.4 1.3 + 1.5 1.2.2 From 8b736ced1acb84da364b89adeeb867584bebc722 Mon Sep 17 00:00:00 2001 From: sasurendran Date: Wed, 11 Jun 2014 17:42:20 -0700 Subject: [PATCH 0036/2020] Making changes so that asynchttpclient config default values are picked up from the asynchttpclient.properties in case they are not specified via system property. The search order for the default value for each config property is as such: 1. System Property 2. Property specified in asynchttpclient.properties 3. Default Value specified in code Also introducing JMockit for unit testing. --- .../asynchttpclient/AsyncHttpClientConfigDefaults.java | 5 ++++- .../main/java/org/asynchttpclient/util/MiscUtil.java | 10 +++++++--- .../java/org/asynchttpclient/util/MiscUtilTest.java | 10 ++++------ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java index 8b40d67c45..16818d1aa9 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java @@ -14,6 +14,9 @@ +import static org.asynchttpclient.util.MiscUtil.getBooleanValue; +import static org.asynchttpclient.util.MiscUtil.getIntValue; +import org.asynchttpclient.util.DefaultHostnameVerifier; import javax.net.ssl.HostnameVerifier; import org.asynchttpclient.util.DefaultHostnameVerifier; @@ -22,7 +25,7 @@ import org.slf4j.LoggerFactory; public final class AsyncHttpClientConfigDefaults { - public final static Logger logger = LoggerFactory.getLogger(AsyncHttpClientConfigDefaults.class); + private AsyncHttpClientConfigDefaults() { } diff --git a/api/src/main/java/org/asynchttpclient/util/MiscUtil.java b/api/src/main/java/org/asynchttpclient/util/MiscUtil.java index 65c8db0b11..486140035b 100644 --- a/api/src/main/java/org/asynchttpclient/util/MiscUtil.java +++ b/api/src/main/java/org/asynchttpclient/util/MiscUtil.java @@ -12,13 +12,17 @@ */ package org.asynchttpclient.util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.Map; import java.util.Properties; -import org.asynchttpclient.AsyncHttpClientConfigDefaults; + public class MiscUtil { + + public final static Logger logger = LoggerFactory.getLogger(MiscUtil.class); private MiscUtil() { } @@ -62,7 +66,7 @@ public static Integer getIntValue(String property,int defaultValue){ return Integer.parseInt(valueString); }catch(NumberFormatException e){ //If property couldn't be parsed log the error message and return default value. - AsyncHttpClientConfigDefaults.logger.error("Property : " + property + " has value = " + valueString + + logger.error("Property : " + property + " has value = " + valueString + " which couldn't be parsed to an int value. Returning default value: " + defaultValue,e); } } @@ -82,7 +86,7 @@ public static Boolean getBooleanValue(String property,boolean defaultValue){ //If a value has been specified but can't be parsed into a boolean log a message //stating that value is unparseable and default values are being used. if(value != null) - AsyncHttpClientConfigDefaults.logger.error("Property : " + property + " has value = " + value + + logger.error("Property : " + property + " has value = " + value + " which couldn't be parsed to an boolean value. Returning default value: " + defaultValue); return defaultValue; } diff --git a/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java b/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java index d9f4bbed19..52b698b40e 100644 --- a/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java +++ b/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java @@ -2,11 +2,7 @@ import java.util.Properties; -import mockit.Deencapsulation; - -import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.AsyncImplHelperMock; -import org.asynchttpclient.AsyncHttpClientConfig.Builder; import org.testng.Assert; import org.testng.annotations.Test; @@ -105,7 +101,8 @@ public void testGetDefaultIntegerValue() { MY_SPECIAL_INT_DEFAULT_VALUE); // Set a corrupt system property System.setProperty(MY_SPECIAL_INT_PROPERTY, "corrupt property"); - // Assert that even though values set in asynchttpclient.properties and system property is corrupt the default value is returned + // Assert that even though values set in asynchttpclient.properties and system property is corrupt the default value is + //returned Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), MY_SPECIAL_INT_DEFAULT_VALUE); System.clearProperty(MY_SPECIAL_INT_PROPERTY); @@ -133,7 +130,8 @@ public void testGetDefaultBooleanValue() { Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, true)); // Set a corrupt system property System.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "corrupt property"); - // Assert that even though values set in asynchttpclient.properties and system property is corrupt the default value is returned + // Assert that even though values set in asynchttpclient.properties and system property is corrupt the default value is + //returned Assert.assertTrue(!MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); System.clearProperty(MY_SPECIAL_INT_PROPERTY); } From df81d4420432c814c57a1d7acb4b503c5a3a6b1d Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 12 Jun 2014 10:23:58 +0200 Subject: [PATCH 0037/2020] Make NPE message more explicit when passing a null hostname to avoid proxy, close #560, close #561 --- .../main/java/org/asynchttpclient/util/ProxyUtils.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java b/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java index b48f7c7535..a2ce5413d8 100644 --- a/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java @@ -108,12 +108,15 @@ public static boolean avoidProxy(final ProxyServer proxyServer, final Request re * See http://download.oracle.com/javase/1.4.2/docs/guide/net/properties.html * * @param proxyServer - * @param target the hostname + * @param hostname the hostname * @return true if we have to avoid proxy use (obeying non-proxy hosts settings), false otherwise. */ - public static boolean avoidProxy(final ProxyServer proxyServer, final String target) { + public static boolean avoidProxy(final ProxyServer proxyServer, final String hostname) { if (proxyServer != null) { - final String targetHost = target.toLowerCase(Locale.ENGLISH); + if (hostname == null) + throw new NullPointerException("hostname"); + + final String targetHost = hostname.toLowerCase(Locale.ENGLISH); List nonProxyHosts = proxyServer.getNonProxyHosts(); From 0ef53c580c5e058c451176667a6ac52b18c1543d Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 12 Jun 2014 11:11:21 +0200 Subject: [PATCH 0038/2020] RequestBuilder.setUrl shouldn't accept an URI without a host, close #571 --- .../main/java/org/asynchttpclient/RequestBuilderBase.java | 5 ++++- .../org/asynchttpclient/async/AsyncProvidersBasicTest.java | 7 ++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java index 3b8fbfe04c..8fbf20fb29 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -388,8 +388,11 @@ public T setUrl(String url) { } public T setURI(URI uri) { + if (uri.getHost() == null) + throw new NullPointerException("uri.host"); if (uri.getPath() == null) - throw new IllegalArgumentException("Unsupported uri format: " + uri); + throw new NullPointerException("uri.path"); + 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 00a1653678..379027d662 100755 --- a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java @@ -386,15 +386,12 @@ public void onThrowable(Throwable t) { } } - @Test(groups = { "online", "default_provider", "async" }) + @Test(groups = { "online", "default_provider", "async" }, expectedExceptions = { NullPointerException.class }) public void asyncNullSchemeTest() throws Exception { AsyncHttpClient client = getAsyncHttpClient(null); try { client.prepareGet("www.sun.com").execute(); - fail(); - } catch (IllegalArgumentException ex) { - assertTrue(true); } finally { client.close(); } @@ -1555,7 +1552,7 @@ public void getShouldAllowBody() throws IllegalArgumentException, IOException { } } - @Test(groups = { "standalone", "default_provider" }, expectedExceptions = { IllegalArgumentException.class }) + @Test(groups = { "standalone", "default_provider" }, expectedExceptions = { NullPointerException.class }) public void invalidUri() throws Exception { AsyncHttpClient client = getAsyncHttpClient(null); try { From 02f069d5a3873d81ab9211247aeee7a4b4ffce7d Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 12 Jun 2014 12:13:05 +0200 Subject: [PATCH 0039/2020] minor clean up, properly use NPE instead of IAE --- .../java/org/asynchttpclient/RequestBuilder.java | 15 +++++++-------- .../org/asynchttpclient/RequestBuilderBase.java | 10 +++++----- .../asynchttpclient/SimpleAsyncHttpClient.java | 16 ++++++++-------- .../multipart/MultipartUtils.java | 7 +++---- .../asynchttpclient/multipart/StringPart.java | 2 +- .../java/org/asynchttpclient/util/DateUtil.java | 8 ++++---- .../async/AsyncProvidersBasicTest.java | 2 +- .../providers/grizzly/FeedableBodyGenerator.java | 2 +- .../netty/request/body/BodyChunkedInput.java | 5 ++--- .../netty/request/body/BodyFileRegion.java | 5 ++--- 10 files changed, 34 insertions(+), 38 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilder.java b/api/src/main/java/org/asynchttpclient/RequestBuilder.java index 5718b37530..57f888ff95 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilder.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilder.java @@ -50,7 +50,7 @@ public RequestBuilder(Request prototype) { // access these methods - see Clojure tickets 126 and 259 @Override - public RequestBuilder addBodyPart(Part part) throws IllegalArgumentException { + public RequestBuilder addBodyPart(Part part) { return super.addBodyPart(part); } @@ -65,7 +65,7 @@ public RequestBuilder addHeader(String name, String value) { } @Override - public RequestBuilder addParameter(String key, String value) throws IllegalArgumentException { + public RequestBuilder addParameter(String key, String value) { return super.addParameter(key, value); } @@ -85,7 +85,7 @@ public Request build() { } @Override - public RequestBuilder setBody(byte[] data) throws IllegalArgumentException { + public RequestBuilder setBody(byte[] data) { return super.setBody(data); } @@ -93,15 +93,14 @@ public RequestBuilder setBody(byte[] data) throws IllegalArgumentException { * Set a Stream for chunking * @param stream - An {@link InputStream} * @return a {@link RequestBuilder} - * @throws IllegalArgumentException */ @Override - public RequestBuilder setBody(InputStream stream) throws IllegalArgumentException { + public RequestBuilder setBody(InputStream stream) { return super.setBody(stream); } @Override - public RequestBuilder setBody(String data) throws IllegalArgumentException { + public RequestBuilder setBody(String data) { return super.setBody(data); } @@ -121,12 +120,12 @@ public RequestBuilder setHeaders(Map> headers) { } @Override - public RequestBuilder setParameters(Map> parameters) throws IllegalArgumentException { + public RequestBuilder setParameters(Map> parameters) { return super.setParameters(parameters); } @Override - public RequestBuilder setParameters(FluentStringsMap parameters) throws IllegalArgumentException { + public RequestBuilder setParameters(FluentStringsMap parameters) { return super.setParameters(parameters); } diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java index 8fbf20fb29..01f0eaea05 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -514,7 +514,7 @@ public T setBody(File file) { return derived.cast(this); } - public T setBody(byte[] data) throws IllegalArgumentException { + public T setBody(byte[] data) { resetParameters(); resetNonMultipartData(); resetMultipartData(); @@ -522,7 +522,7 @@ public T setBody(byte[] data) throws IllegalArgumentException { return derived.cast(this); } - public T setBody(String data) throws IllegalArgumentException { + public T setBody(String data) { resetParameters(); resetNonMultipartData(); resetMultipartData(); @@ -530,7 +530,7 @@ public T setBody(String data) throws IllegalArgumentException { return derived.cast(this); } - public T setBody(InputStream stream) throws IllegalArgumentException { + public T setBody(InputStream stream) { resetParameters(); resetNonMultipartData(); resetMultipartData(); @@ -560,7 +560,7 @@ public T setQueryParameters(FluentStringsMap parameters) { return derived.cast(this); } - public T addParameter(String key, String value) throws IllegalArgumentException { + public T addParameter(String key, String value) { resetNonMultipartData(); resetMultipartData(); if (request.params == null) { @@ -570,7 +570,7 @@ public T addParameter(String key, String value) throws IllegalArgumentException return derived.cast(this); } - public T setParameters(FluentStringsMap parameters) throws IllegalArgumentException { + public T setParameters(FluentStringsMap parameters) { resetNonMultipartData(); resetMultipartData(); request.params = new FluentStringsMap(parameters); diff --git a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java index 3bbf84d7fb..4147e3e8d0 100644 --- a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java +++ b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java @@ -373,9 +373,9 @@ public interface DerivedBuilder { DerivedBuilder setUrl(String url); - DerivedBuilder setParameters(FluentStringsMap parameters) throws IllegalArgumentException; + DerivedBuilder setParameters(FluentStringsMap parameters); - DerivedBuilder setParameters(Map> parameters) throws IllegalArgumentException; + DerivedBuilder setParameters(Map> parameters); DerivedBuilder setHeaders(Map> headers); @@ -385,13 +385,13 @@ public interface DerivedBuilder { DerivedBuilder addQueryParameter(String name, String value); - DerivedBuilder addParameter(String key, String value) throws IllegalArgumentException; + DerivedBuilder addParameter(String key, String value); DerivedBuilder addHeader(String name, String value); DerivedBuilder addCookie(Cookie cookie); - DerivedBuilder addBodyPart(Part part) throws IllegalArgumentException; + DerivedBuilder addBodyPart(Part part); DerivedBuilder setResumableDownload(boolean resume); @@ -428,7 +428,7 @@ private Builder(SimpleAsyncHttpClient client) { this.listener = client.listener; } - public Builder addBodyPart(Part part) throws IllegalArgumentException { + public Builder addBodyPart(Part part) { requestBuilder.addBodyPart(part); return this; } @@ -443,7 +443,7 @@ public Builder addHeader(String name, String value) { return this; } - public Builder addParameter(String key, String value) throws IllegalArgumentException { + public Builder addParameter(String key, String value) { requestBuilder.addParameter(key, value); return this; } @@ -468,12 +468,12 @@ public Builder setHeaders(Map> headers) { return this; } - public Builder setParameters(Map> parameters) throws IllegalArgumentException { + public Builder setParameters(Map> parameters) { requestBuilder.setParameters(parameters); return this; } - public Builder setParameters(FluentStringsMap parameters) throws IllegalArgumentException { + public Builder setParameters(FluentStringsMap parameters) { requestBuilder.setParameters(parameters); return this; } diff --git a/api/src/main/java/org/asynchttpclient/multipart/MultipartUtils.java b/api/src/main/java/org/asynchttpclient/multipart/MultipartUtils.java index e73cb802a6..9abc5b67d6 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/MultipartUtils.java +++ b/api/src/main/java/org/asynchttpclient/multipart/MultipartUtils.java @@ -61,7 +61,7 @@ private MultipartUtils() { */ public static MultipartBody newMultipartBody(List parts, FluentCaseInsensitiveStringsMap requestHeaders) { if (parts == null) { - throw new IllegalArgumentException("parts cannot be null"); + throw new NullPointerException("parts"); } byte[] multipartBoundary; @@ -162,9 +162,8 @@ public static long writeBytesToChannel(WritableByteChannel target, byte[] bytes) public static byte[] getMessageEnd(byte[] partBoundary) throws IOException { - if (partBoundary == null || partBoundary.length == 0) { + if (!isNonEmpty(partBoundary)) throw new IllegalArgumentException("partBoundary may not be empty"); - } ByteArrayOutputStream out = new ByteArrayOutputStream(); OutputStreamPartVisitor visitor = new OutputStreamPartVisitor(out); @@ -180,7 +179,7 @@ public static long getLengthOfParts(List parts, byte[] partBoundary) { try { if (parts == null) { - throw new IllegalArgumentException("Parts may not be null"); + throw new NullPointerException("parts"); } long total = 0; for (Part part : parts) { diff --git a/api/src/main/java/org/asynchttpclient/multipart/StringPart.java b/api/src/main/java/org/asynchttpclient/multipart/StringPart.java index b8061a3088..a6d60b7153 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/StringPart.java +++ b/api/src/main/java/org/asynchttpclient/multipart/StringPart.java @@ -60,7 +60,7 @@ public StringPart(String name, String value, String charset, String contentId) { super(name, DEFAULT_CONTENT_TYPE, charset == null ? DEFAULT_CHARSET : charset, DEFAULT_TRANSFER_ENCODING, contentId); if (value == null) { - throw new IllegalArgumentException("Value may not be null"); + throw new NullPointerException("value"); } if (value.indexOf(0) != -1) { // See RFC 2048, 2.8. "8bit Data" diff --git a/api/src/main/java/org/asynchttpclient/util/DateUtil.java b/api/src/main/java/org/asynchttpclient/util/DateUtil.java index 549174f755..a41ec9deba 100644 --- a/api/src/main/java/org/asynchttpclient/util/DateUtil.java +++ b/api/src/main/java/org/asynchttpclient/util/DateUtil.java @@ -131,7 +131,7 @@ public static Date parseDate(String dateValue, Collection dateFormats) t public static Date parseDate(String dateValue, Collection dateFormats, Date startDate) throws DateParseException { if (dateValue == null) { - throw new IllegalArgumentException("dateValue is null"); + throw new NullPointerException("dateValue"); } if (dateFormats == null) { dateFormats = DEFAULT_PATTERNS; @@ -187,14 +187,14 @@ public static String formatDate(Date date) { * @param date The date to format. * @param pattern The pattern to use for formatting the date. * @return A formatted date string. - * @throws IllegalArgumentException If the given date pattern is invalid. + * @throws NullPointerException If the given date pattern is invalid. * @see java.text.SimpleDateFormat */ public static String formatDate(Date date, String pattern) { if (date == null) - throw new IllegalArgumentException("date is null"); + throw new NullPointerException("date"); if (pattern == null) - throw new IllegalArgumentException("pattern is null"); + throw new NullPointerException("pattern"); SimpleDateFormat formatter = new SimpleDateFormat(pattern, Locale.US); formatter.setTimeZone(GMT); diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java index 379027d662..accbe651a7 100755 --- a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java @@ -1543,7 +1543,7 @@ public void onThrowable(Throwable t) { } @Test(groups = { "standalone", "default_provider" }) - public void getShouldAllowBody() throws IllegalArgumentException, IOException { + public void getShouldAllowBody() throws IOException { AsyncHttpClient client = getAsyncHttpClient(null); try { client.prepareGet(getTargetUrl()).setBody("Boo!").execute(); 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 1717dcba18..6514edb61a 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 @@ -309,7 +309,7 @@ protected BaseFeeder(FeedableBodyGenerator feedableBodyGenerator) { @SuppressWarnings("UnusedDeclaration") public final synchronized void feed(final Buffer buffer, final boolean last) throws IOException { if (buffer == null) { - throw new IllegalArgumentException("Buffer argument cannot be null."); + throw new NullPointerException("buffer"); } if (!feedableBodyGenerator.asyncTransferInitiated) { throw new IllegalStateException("Asynchronous transfer has not been initiated."); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyChunkedInput.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyChunkedInput.java index 5fbe753846..271046a3e7 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyChunkedInput.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyChunkedInput.java @@ -35,9 +35,8 @@ public class BodyChunkedInput implements ChunkedInput { private boolean endOfInput; public BodyChunkedInput(Body body) { - if (body == null) { - throw new IllegalArgumentException("no body specified"); - } + if (body == null) + throw new NullPointerException("body"); this.body = body; contentLength = (int) body.getContentLength(); if (contentLength <= 0) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java index 7d005c6c0d..857b610784 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java @@ -29,9 +29,8 @@ public class BodyFileRegion extends AbstractReferenceCounted implements FileRegi private long transfered; public BodyFileRegion(RandomAccessBody body) { - if (body == null) { - throw new IllegalArgumentException("no body specified"); - } + if (body == null) + throw new NullPointerException("body"); this.body = body; } From 01e61a10ed1bdd4839f4752c287192cfb20b59c6 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 12 Jun 2014 13:23:38 +0200 Subject: [PATCH 0040/2020] Minor ProxyUtils.matchNonProxyHost clean up, still very incomplete --- .../org/asynchttpclient/util/ProxyUtils.java | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java b/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java index a2ce5413d8..1b183dd3e8 100644 --- a/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java @@ -35,7 +35,7 @@ * * @author cstamas */ -public class ProxyUtils { +public final class ProxyUtils { private final static Logger log = LoggerFactory.getLogger(ProxyUtils.class); @@ -71,6 +71,9 @@ public class ProxyUtils { */ public static final String PROXY_PASSWORD = PROPERTY_PREFIX + "password"; + private ProxyUtils() { + } + /** * @param config the global config * @param request the request @@ -88,19 +91,25 @@ public static ProxyServer getProxyServer(AsyncHttpClientConfig config, Request r } /** - * Checks whether proxy should be used according to nonProxyHosts settings of it, or we want to go directly to - * target host. If null proxy is passed in, this method returns true -- since there is NO proxy, we - * should avoid to use it. Simple hostname pattern matching using "*" are supported, but only as prefixes. - * See http://download.oracle.com/javase/1.4.2/docs/guide/net/properties.html - * - * @param proxyServer - * @param request - * @return true if we have to avoid proxy use (obeying non-proxy hosts settings), false otherwise. + * @see #avoidProxy(ProxyServer, String) */ public static boolean avoidProxy(final ProxyServer proxyServer, final Request request) { return avoidProxy(proxyServer, AsyncHttpProviderUtils.getHost(request.getOriginalURI())); } + private static boolean matchNonProxyHost(String targetHost, String nonProxyHost) { + + if (nonProxyHost.length() > 1) { + if (nonProxyHost.charAt(0) == '*') + return targetHost.regionMatches(true, targetHost.length() - nonProxyHost.length() + 1, nonProxyHost, 1, + nonProxyHost.length() - 1); + else if (nonProxyHost.charAt(nonProxyHost.length() - 1) == '*') + return targetHost.regionMatches(true, 0, nonProxyHost, 0, nonProxyHost.length() - 1); + } + + return nonProxyHost.equalsIgnoreCase(targetHost); + } + /** * Checks whether proxy should be used according to nonProxyHosts settings of it, or we want to go directly to * target host. If null proxy is passed in, this method returns true -- since there is NO proxy, we @@ -116,22 +125,12 @@ public static boolean avoidProxy(final ProxyServer proxyServer, final String hos if (hostname == null) throw new NullPointerException("hostname"); - final String targetHost = hostname.toLowerCase(Locale.ENGLISH); - List nonProxyHosts = proxyServer.getNonProxyHosts(); if (isNonEmpty(nonProxyHosts)) { for (String nonProxyHost : nonProxyHosts) { - // FIXME use regionMatches instead - if (nonProxyHost.startsWith("*") && nonProxyHost.length() > 1 - && targetHost.endsWith(nonProxyHost.substring(1).toLowerCase(Locale.ENGLISH))) { - return true; - } else if (nonProxyHost.endsWith("*") && nonProxyHost.length() > 1 - && targetHost.startsWith(nonProxyHost.substring(0, nonProxyHost.length() - 1).toLowerCase(Locale.ENGLISH))) { + if (matchNonProxyHost(hostname, nonProxyHost)) return true; - } else if (nonProxyHost.equalsIgnoreCase(targetHost)) { - return true; - } } } From eb84fa587bdcf31ba79a15887813972abea5e674 Mon Sep 17 00:00:00 2001 From: Will Sargent Date: Wed, 2 Apr 2014 22:13:44 -0700 Subject: [PATCH 0041/2020] Ensure certificate verification by using a singleton. --- .../org/asynchttpclient/util/SslUtils.java | 134 ++---------------- .../grizzly/GrizzlyAsyncHttpProvider.java | 2 +- .../providers/netty/channel/Channels.java | 2 +- 3 files changed, 12 insertions(+), 126 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/util/SslUtils.java b/api/src/main/java/org/asynchttpclient/util/SslUtils.java index 29c7b70011..10920fdb47 100644 --- a/api/src/main/java/org/asynchttpclient/util/SslUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/SslUtils.java @@ -15,30 +15,25 @@ */ package org.asynchttpclient.util; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.SecureRandom; -import java.security.Security; /** * This class is a copy of http://github.com/sonatype/wagon-ning/raw/master/src/main/java/org/apache/maven/wagon/providers/http/SslUtils.java */ public class SslUtils { - private static SSLContext context = null; + private static class SingletonHolder { + public static final SslUtils instance = new SslUtils(); + } + + public static SslUtils getInstance() { + return SingletonHolder.instance; + } - public static SSLEngine getSSLEngine() throws GeneralSecurityException, IOException { + public SSLEngine getSSLEngine() throws GeneralSecurityException, IOException { SSLEngine engine = null; SSLContext context = getSSLContext(); @@ -50,117 +45,8 @@ public static SSLEngine getSSLEngine() throws GeneralSecurityException, IOExcept return engine; } - public static SSLContext getSSLContext() throws GeneralSecurityException, IOException { - if (context == null) { - SSLConfig config = new SSLConfig(); - if (config.keyStoreLocation == null || config.trustStoreLocation == null) { - context = getLooseSSLContext(); - } else { - context = getStrictSSLContext(config); - } - } - return context; - } - - static SSLContext getStrictSSLContext(SSLConfig config) throws GeneralSecurityException, IOException { - KeyStore keyStore = KeyStore.getInstance(config.keyStoreType); - InputStream keystoreInputStream = new FileInputStream(config.keyStoreLocation); - try { - keyStore.load(keystoreInputStream, (config.keyStorePassword == null) ? null : config.keyStorePassword.toCharArray()); - } finally { - keystoreInputStream.close(); - } - - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(config.keyManagerAlgorithm); - keyManagerFactory.init(keyStore, (config.keyManagerPassword == null) ? null : config.keyManagerPassword.toCharArray()); - KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); - - KeyStore trustStore = KeyStore.getInstance(config.trustStoreType); - InputStream truststoreInputStream = new FileInputStream(config.trustStoreLocation); - try { - trustStore.load(truststoreInputStream, (config.trustStorePassword == null) ? null : config.trustStorePassword.toCharArray()); - } finally { - truststoreInputStream.close(); - } - - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(config.trustManagerAlgorithm); - trustManagerFactory.init(trustStore); - TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); - - SSLContext context = SSLContext.getInstance("TLS"); - context.init(keyManagers, trustManagers, null); - - return context; - } - - static SSLContext getLooseSSLContext() throws GeneralSecurityException { - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, new TrustManager[] { LooseTrustManager.INSTANCE }, new SecureRandom()); - return sslContext; - } - - static class LooseTrustManager implements X509TrustManager { - - public static final LooseTrustManager INSTANCE = new LooseTrustManager(); - - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return new java.security.cert.X509Certificate[0]; - } - - public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) { - } - - public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) { - } - } - - private final static class SSLConfig { - - public String keyStoreLocation; - - public String keyStoreType = "JKS"; - - public String keyStorePassword = "changeit"; - - public String keyManagerAlgorithm = "SunX509"; - - public String keyManagerPassword = "changeit"; - - public String trustStoreLocation; - - public String trustStoreType = "JKS"; - - public String trustStorePassword = "changeit"; - - public String trustManagerAlgorithm = "SunX509"; - - public SSLConfig() { - keyStoreLocation = System.getProperty("javax.net.ssl.keyStore"); - keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword", "changeit"); - keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType()); - keyManagerAlgorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); - - if (keyManagerAlgorithm == null) { - keyManagerAlgorithm = "SunX509"; - } - - keyManagerPassword = System.getProperty("javax.net.ssl.keyStorePassword", "changeit"); - - trustStoreLocation = System.getProperty("javax.net.ssl.trustStore"); - if (trustStoreLocation == null) { - trustStoreLocation = keyStoreLocation; - trustStorePassword = keyStorePassword; - trustStoreType = keyStoreType; - } else { - trustStorePassword = System.getProperty("javax.net.ssl.trustStorePassword", "changeit"); - trustStoreType = System.getProperty("javax.net.ssl.trustStoreType", KeyStore.getDefaultType()); - } - trustManagerAlgorithm = Security.getProperty("ssl.TrustManagerFactory.algorithm"); - - if (trustManagerAlgorithm == null) { - trustManagerAlgorithm = "SunX509"; - } - } + public SSLContext getSSLContext() throws GeneralSecurityException, IOException { + return SSLContext.getDefault(); } } 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 6949ca9be3..0290789067 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 @@ -253,7 +253,7 @@ public void onTimeout(Connection connection) { SSLContext context = clientConfig.getSSLContext(); if (context == null) { try { - context = SslUtils.getSSLContext(); + context = SslUtils.getInstance().getSSLContext(); } catch (Exception e) { throw new IllegalStateException(e); } 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 64bdf7ea81..8900449865 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 @@ -204,7 +204,7 @@ private Timer newNettyTimer() { private SSLEngine createSSLEngine() throws IOException, GeneralSecurityException { SSLEngine sslEngine = config.getSSLEngineFactory().newSSLEngine(); if (sslEngine == null) { - sslEngine = SslUtils.getSSLEngine(); + sslEngine = SslUtils.getInstance().getSSLEngine(); } return sslEngine; } From e5e6489941e222d4b510ba09ce7b53e9eed02601 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 12 Jun 2014 18:42:56 +0200 Subject: [PATCH 0042/2020] Move setting a SSLEngineFactory into Netty specific conf as Grizzly doesn't use it --- .../AsyncHttpClientConfig.java | 26 ------------------- .../AsyncHttpClientConfigBean.java | 5 ---- .../org/asynchttpclient/util/SslUtils.java | 14 ---------- .../netty/NettyAsyncHttpProviderConfig.java | 11 ++++++++ .../providers/netty/channel/Channels.java | 18 ++++++++++--- 5 files changed, 25 insertions(+), 49 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java index babb8cbd9e..e450c60a6e 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -25,7 +25,6 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; import java.io.IOException; import java.io.InputStream; @@ -94,7 +93,6 @@ public class AsyncHttpClientConfig { protected ExecutorService applicationThreadPool; protected ProxyServerSelector proxyServerSelector; protected SSLContext sslContext; - protected SSLEngineFactory sslEngineFactory; protected AsyncHttpProviderConfig providerConfig; protected Realm realm; protected List requestFilters; @@ -169,7 +167,6 @@ private AsyncHttpClientConfig(int maxTotalConnections, // this.userAgent = userAgent; this.allowPoolingConnection = keepAlive; this.sslContext = sslContext; - this.sslEngineFactory = sslEngineFactory; this.providerConfig = providerConfig; this.realm = realm; this.requestFilters = requestFilters; @@ -332,28 +329,6 @@ public SSLContext getSSLContext() { return sslContext; } - /** - * Return an instance of {@link SSLEngineFactory} used for SSL connection. - * - * @return an instance of {@link SSLEngineFactory} used for SSL connection. - */ - public SSLEngineFactory getSSLEngineFactory() { - if (sslEngineFactory == null) { - return new SSLEngineFactory() { - public SSLEngine newSSLEngine() { - if (sslContext != null) { - SSLEngine sslEngine = sslContext.createSSLEngine(); - sslEngine.setUseClientMode(true); - return sslEngine; - } else { - return null; - } - } - }; - } - return sslEngineFactory; - } - /** * Return the {@link AsyncHttpProviderConfig} * @@ -1122,7 +1097,6 @@ public Builder(AsyncHttpClientConfig prototype) { realm = prototype.getRealm(); requestTimeoutInMs = prototype.getRequestTimeoutInMs(); sslContext = prototype.getSSLContext(); - sslEngineFactory = prototype.getSSLEngineFactory(); userAgent = prototype.getUserAgent(); redirectEnabled = prototype.isRedirectEnabled(); compressionEnabled = prototype.isCompressionEnabled(); diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java index 98ab988ebb..d48b477eb3 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java @@ -175,11 +175,6 @@ public AsyncHttpClientConfigBean setSslContext(SSLContext sslContext) { return this; } - public AsyncHttpClientConfigBean setSslEngineFactory(SSLEngineFactory sslEngineFactory) { - this.sslEngineFactory = sslEngineFactory; - return this; - } - public AsyncHttpClientConfigBean setProviderConfig(AsyncHttpProviderConfig providerConfig) { this.providerConfig = providerConfig; return this; diff --git a/api/src/main/java/org/asynchttpclient/util/SslUtils.java b/api/src/main/java/org/asynchttpclient/util/SslUtils.java index 10920fdb47..14c2629c61 100644 --- a/api/src/main/java/org/asynchttpclient/util/SslUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/SslUtils.java @@ -16,7 +16,6 @@ package org.asynchttpclient.util; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; import java.io.IOException; import java.security.GeneralSecurityException; @@ -33,20 +32,7 @@ public static SslUtils getInstance() { return SingletonHolder.instance; } - public SSLEngine getSSLEngine() throws GeneralSecurityException, IOException { - SSLEngine engine = null; - - SSLContext context = getSSLContext(); - if (context != null) { - engine = context.createSSLEngine(); - engine.setUseClientMode(true); - } - - return engine; - } - public SSLContext getSSLContext() throws GeneralSecurityException, IOException { return SSLContext.getDefault(); } - } 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 f4a0022bad..ac75499993 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 @@ -17,6 +17,7 @@ package org.asynchttpclient.providers.netty; import org.asynchttpclient.AsyncHttpProviderConfig; +import org.asynchttpclient.SSLEngineFactory; import org.asynchttpclient.providers.netty.channel.ChannelPool; import org.asynchttpclient.providers.netty.response.EagerResponseBodyPart; import org.asynchttpclient.providers.netty.response.LazyResponseBodyPart; @@ -93,6 +94,8 @@ public class NettyAsyncHttpProviderConfig implements AsyncHttpProviderConfig Date: Thu, 12 Jun 2014 21:47:02 +0200 Subject: [PATCH 0043/2020] Introduce acceptAnyCertificate config, defaulting to false, close #526, close #352 --- .../AsyncHttpClientConfig.java | 18 ++++++++- .../AsyncHttpClientConfigBean.java | 6 +++ .../AsyncHttpClientConfigDefaults.java | 4 ++ .../SimpleAsyncHttpClient.java | 5 +++ .../org/asynchttpclient/util/MiscUtil.java | 4 ++ .../org/asynchttpclient/util/SslUtils.java | 37 ++++++++++++++++++- .../async/HttpToHttpsRedirectTest.java | 18 +++++++-- .../async/ProxyTunnellingTest.java | 16 ++++++-- .../websocket/ProxyTunnellingTest.java | 2 +- .../grizzly/GrizzlyAsyncHttpProvider.java | 2 +- .../GrizzlyFeedableBodyGeneratorTest.java | 2 + .../providers/netty/channel/Channels.java | 7 ++-- 12 files changed, 104 insertions(+), 17 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java index e450c60a6e..444ceda349 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -112,6 +112,7 @@ public class AsyncHttpClientConfig { protected int spdyInitialWindowSize; protected int spdyMaxConcurrentStreams; protected TimeConverter timeConverter; + protected boolean acceptAnyCertificate; protected AsyncHttpClientConfig() { } @@ -151,7 +152,8 @@ private AsyncHttpClientConfig(int maxTotalConnections, // boolean spdyEnabled, // int spdyInitialWindowSize, // int spdyMaxConcurrentStreams, // - TimeConverter timeConverter) { + TimeConverter timeConverter, // + boolean acceptAnyCertificate) { this.maxTotalConnections = maxTotalConnections; this.maxConnectionPerHost = maxConnectionPerHost; @@ -187,6 +189,7 @@ private AsyncHttpClientConfig(int maxTotalConnections, // this.spdyInitialWindowSize = spdyInitialWindowSize; this.spdyMaxConcurrentStreams = spdyMaxConcurrentStreams; this.timeConverter = timeConverter; + this.acceptAnyCertificate = acceptAnyCertificate; } @@ -533,6 +536,10 @@ public TimeConverter getTimeConverter() { return timeConverter; } + public boolean isAcceptAnyCertificate() { + return acceptAnyCertificate; + } + /** * Builder for an {@link AsyncHttpClient} */ @@ -564,6 +571,7 @@ public static class Builder { private boolean spdyEnabled = defaultSpdyEnabled(); private int spdyInitialWindowSize = defaultSpdyInitialWindowSize(); private int spdyMaxConcurrentStreams = defaultSpdyMaxConcurrentStreams(); + private boolean acceptAnyCertificate = defaultAcceptAnyCertificate(); private ScheduledExecutorService reaper; private ExecutorService applicationThreadPool; @@ -1078,6 +1086,11 @@ public Builder setTimeConverter(TimeConverter timeConverter) { return this; } + public Builder setAcceptAnyCertificate(boolean acceptAnyCertificate) { + this.acceptAnyCertificate = acceptAnyCertificate; + return this; + } + /** * Create a config builder with values taken from the given prototype configuration. * @@ -1186,7 +1199,8 @@ public Thread newThread(Runnable r) { spdyEnabled, // spdyInitialWindowSize, // spdyMaxConcurrentStreams, // - timeConverter); + timeConverter, // + acceptAnyCertificate); } } } diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java index d48b477eb3..463d0c2c37 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java @@ -70,6 +70,7 @@ void configureDefaults() { spdyEnabled = defaultSpdyEnabled(); spdyInitialWindowSize = defaultSpdyInitialWindowSize(); spdyMaxConcurrentStreams = defaultSpdyMaxConcurrentStreams(); + acceptAnyCertificate = defaultAcceptAnyCertificate(); if (defaultUseProxySelector()) { proxyServerSelector = ProxyUtils.getJdkDefaultProxyServerSelector(); } else if (defaultUseProxyProperties()) { @@ -234,4 +235,9 @@ public AsyncHttpClientConfigBean setIoThreadMultiplier(int ioThreadMultiplier) { this.ioThreadMultiplier = ioThreadMultiplier; return this; } + + public AsyncHttpClientConfigBean setAcceptAnyCertificate(boolean acceptAnyCertificate) { + this.acceptAnyCertificate = acceptAnyCertificate; + return this; + } } diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java index edd51169a1..3cb9ebddc7 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java @@ -133,4 +133,8 @@ public static int defaultSpdyInitialWindowSize() { public static int defaultSpdyMaxConcurrentStreams() { return Integer.getInteger(ASYNC_CLIENT + "spdyMaxConcurrentStreams", 100); } + + public static boolean defaultAcceptAnyCertificate() { + return getBoolean(ASYNC_CLIENT + "acceptAnyCertificate", false); + } } diff --git a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java index 4147e3e8d0..b96d69a91f 100644 --- a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java +++ b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java @@ -678,6 +678,11 @@ public Builder setProviderClass(String providerClass) { return this; } + public Builder setAcceptAnyCertificate(boolean acceptAnyCertificate) { + configBuilder.setAcceptAnyCertificate(acceptAnyCertificate); + return this; + } + public SimpleAsyncHttpClient build() { if (realmBuilder != null) { diff --git a/api/src/main/java/org/asynchttpclient/util/MiscUtil.java b/api/src/main/java/org/asynchttpclient/util/MiscUtil.java index 7a80e00268..3cad6cfd37 100644 --- a/api/src/main/java/org/asynchttpclient/util/MiscUtil.java +++ b/api/src/main/java/org/asynchttpclient/util/MiscUtil.java @@ -44,4 +44,8 @@ public static boolean getBoolean(String systemPropName, boolean defaultValue) { String systemPropValue = System.getProperty(systemPropName); return systemPropValue != null ? systemPropValue.equalsIgnoreCase("true") : defaultValue; } + + public static T withDefault(T value, T defaults) { + return value != null? value : value; + } } diff --git a/api/src/main/java/org/asynchttpclient/util/SslUtils.java b/api/src/main/java/org/asynchttpclient/util/SslUtils.java index 14c2629c61..becb9ed943 100644 --- a/api/src/main/java/org/asynchttpclient/util/SslUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/SslUtils.java @@ -16,14 +16,47 @@ package org.asynchttpclient.util; import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + import java.io.IOException; import java.security.GeneralSecurityException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; /** * This class is a copy of http://github.com/sonatype/wagon-ning/raw/master/src/main/java/org/apache/maven/wagon/providers/http/SslUtils.java */ public class SslUtils { + + static class LooseTrustManager implements X509TrustManager { + + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[0]; + } + + public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) { + } + public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) { + } + } + + private SSLContext looseTrustManagerSSLContext = looseTrustManagerSSLContext(); + + private SSLContext looseTrustManagerSSLContext() { + try { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[] { new LooseTrustManager() }, new SecureRandom()); + return sslContext; + } catch (NoSuchAlgorithmException e) { + throw new ExceptionInInitializerError(e); + } catch (KeyManagementException e) { + throw new ExceptionInInitializerError(e); + } + } + private static class SingletonHolder { public static final SslUtils instance = new SslUtils(); } @@ -32,7 +65,7 @@ public static SslUtils getInstance() { return SingletonHolder.instance; } - public SSLContext getSSLContext() throws GeneralSecurityException, IOException { - return SSLContext.getDefault(); + public SSLContext getSSLContext(boolean acceptAnyCertificate) throws GeneralSecurityException, IOException { + return acceptAnyCertificate? looseTrustManagerSSLContext: SSLContext.getDefault(); } } diff --git a/api/src/test/java/org/asynchttpclient/async/HttpToHttpsRedirectTest.java b/api/src/test/java/org/asynchttpclient/async/HttpToHttpsRedirectTest.java index f754ac83a5..132c87f453 100644 --- a/api/src/test/java/org/asynchttpclient/async/HttpToHttpsRedirectTest.java +++ b/api/src/test/java/org/asynchttpclient/async/HttpToHttpsRedirectTest.java @@ -96,7 +96,11 @@ public void runAllSequentiallyBecauseNotThreadSafe() throws Exception { public void httpToHttpsRedirect() throws Exception { redirectDone.getAndSet(false); - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setMaxRedirects(5).setFollowRedirects(true).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder()// + .setMaxRedirects(5)// + .setFollowRedirects(true)// + .setAcceptAnyCertificate(true)// + .build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", getTargetUrl2()).execute().get(); @@ -112,7 +116,11 @@ public void httpToHttpsRedirect() throws Exception { public void httpToHttpsProperConfig() throws Exception { redirectDone.getAndSet(false); - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setMaxRedirects(5).setFollowRedirects(true).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder()// + .setMaxRedirects(5)// + .setFollowRedirects(true)// + .setAcceptAnyCertificate(true)// + .build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", getTargetUrl2() + "/test2").execute().get(); @@ -134,7 +142,11 @@ public void httpToHttpsProperConfig() throws Exception { public void relativeLocationUrl() throws Exception { redirectDone.getAndSet(false); - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setMaxRedirects(5).setFollowRedirects(true).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder()// + .setMaxRedirects(5)// + .setFollowRedirects(true)// + .setAcceptAnyCertificate(true)// + .build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { Response response = c.prepareGet(getTargetUrl()).setHeader("X-redirect", "/foo/test").execute().get(); diff --git a/api/src/test/java/org/asynchttpclient/async/ProxyTunnellingTest.java b/api/src/test/java/org/asynchttpclient/async/ProxyTunnellingTest.java index 4f223f40ce..01d85ac608 100644 --- a/api/src/test/java/org/asynchttpclient/async/ProxyTunnellingTest.java +++ b/api/src/test/java/org/asynchttpclient/async/ProxyTunnellingTest.java @@ -74,12 +74,14 @@ public void tearDownGlobal() throws Exception { @Test(groups = { "online", "default_provider" }) public void testRequestProxy() throws IOException, InterruptedException, ExecutionException, TimeoutException { - AsyncHttpClientConfig.Builder b = new AsyncHttpClientConfig.Builder(); - b.setFollowRedirects(true); ProxyServer ps = new ProxyServer(ProxyServer.Protocol.HTTPS, "127.0.0.1", port1); - AsyncHttpClientConfig config = b.build(); + AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// + .setFollowRedirects(true)// + .setAcceptAnyCertificate(true)// + .build(); + AsyncHttpClient asyncHttpClient = getAsyncHttpClient(config); try { RequestBuilder rb = new RequestBuilder("GET").setProxyServer(ps).setUrl(getTargetUrl2()); @@ -108,6 +110,7 @@ public void testConfigProxy() throws IOException, InterruptedException, Executio AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// .setFollowRedirects(true)// .setProxyServer(new ProxyServer(ProxyServer.Protocol.HTTPS, "127.0.0.1", port1))// + .setAcceptAnyCertificate(true)// .build(); AsyncHttpClient asyncHttpClient = getAsyncHttpClient(config); try { @@ -136,7 +139,12 @@ public void testSimpleAHCConfigProxy() throws IOException, InterruptedException, SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder()// .setProviderClass(getProviderClass())// - .setProxyProtocol(ProxyServer.Protocol.HTTPS).setProxyHost("127.0.0.1").setProxyPort(port1).setFollowRedirects(true).setUrl(getTargetUrl2())// + .setProxyProtocol(ProxyServer.Protocol.HTTPS)// + .setProxyHost("127.0.0.1")// + .setProxyPort(port1)// + .setFollowRedirects(true)// + .setUrl(getTargetUrl2())// + .setAcceptAnyCertificate(true)// .setHeader("Content-Type", "text/html")// .build(); try { diff --git a/api/src/test/java/org/asynchttpclient/websocket/ProxyTunnellingTest.java b/api/src/test/java/org/asynchttpclient/websocket/ProxyTunnellingTest.java index 4df55e2537..cdb6e38cad 100644 --- a/api/src/test/java/org/asynchttpclient/websocket/ProxyTunnellingTest.java +++ b/api/src/test/java/org/asynchttpclient/websocket/ProxyTunnellingTest.java @@ -78,7 +78,7 @@ protected String getTargetUrl() { public void echoText() throws Exception { ProxyServer ps = new ProxyServer(ProxyServer.Protocol.HTTPS, "127.0.0.1", port1); - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().setProxyServer(ps).build(); + AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().setProxyServer(ps).setAcceptAnyCertificate(true).build(); AsyncHttpClient asyncHttpClient = getAsyncHttpClient(config); try { final CountDownLatch latch = new CountDownLatch(1); 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 0290789067..2e1d77ee37 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 @@ -253,7 +253,7 @@ public void onTimeout(Connection connection) { SSLContext context = clientConfig.getSSLContext(); if (context == null) { try { - context = SslUtils.getInstance().getSSLContext(); + context = SslUtils.getInstance().getSSLContext(clientConfig.isAcceptAnyCertificate()); } catch (Exception e) { throw new IllegalStateException(e); } diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java index 620eb6ea66..9e6a500c63 100644 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java @@ -138,6 +138,7 @@ private void doSimpleFeeder(final boolean secure) { AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder() .setMaxConnectionsPerHost(60) .setMaxConnectionsTotal(60) + .setAcceptAnyCertificate(true) .build(); final AsyncHttpClient client = new DefaultAsyncHttpClient(new GrizzlyAsyncHttpProvider(config), config); @@ -240,6 +241,7 @@ private void doNonBlockingFeeder(final boolean secure) { AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder() .setMaxConnectionsPerHost(60) .setMaxConnectionsTotal(60) + .setAcceptAnyCertificate(true) .build(); final AsyncHttpClient client = new DefaultAsyncHttpClient(new GrizzlyAsyncHttpProvider(config), config); 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 83213da44f..b55ab4721e 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 @@ -209,10 +209,9 @@ private SSLEngine createSSLEngine() throws IOException, GeneralSecurityException } else { SSLContext sslContext = config.getSSLContext(); - if (sslContext == null) { - sslContext = SslUtils.getInstance().getSSLContext(); - } - + if (sslContext == null) + sslContext = SslUtils.getInstance().getSSLContext(config.isAcceptAnyCertificate()); + SSLEngine sslEngine = sslContext.createSSLEngine(); sslEngine.setUseClientMode(true); return sslEngine; From 4f34eee9ea12bf16fa1d97ae229b79546fe6f26c Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 13 Jun 2014 13:38:24 +0200 Subject: [PATCH 0044/2020] Upgrade Netty 4.0.20 and Javassist 3.18.2 --- 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 3ecc938d35..bc9e908b63 100644 --- a/providers/netty/pom.xml +++ b/providers/netty/pom.xml @@ -39,12 +39,12 @@ io.netty netty-all - 4.0.19.Final + 4.0.20.Final org.javassist javassist - 3.18.1-GA + 3.18.2-GA From 4beb9c896ec5fbd8e2ebb78f89338ffcfbc5220f Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 13 Jun 2014 15:28:15 +0200 Subject: [PATCH 0045/2020] Add host and port to SSLEngine, close #527, close #513 --- .../providers/netty/channel/Channels.java | 33 ++++++------ .../netty/channel/SslInitializer.java | 54 +++++++++++++++++++ .../providers/netty/handler/HttpProtocol.java | 8 ++- 3 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java 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 b55ab4721e..9e546f30da 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 @@ -15,11 +15,11 @@ */ package org.asynchttpclient.providers.netty.channel; +import static org.asynchttpclient.providers.netty.handler.Processor.newHttpProcessor; +import static org.asynchttpclient.providers.netty.handler.Processor.newWsProcessor; import static org.asynchttpclient.providers.netty.util.HttpUtil.WEBSOCKET; import static org.asynchttpclient.providers.netty.util.HttpUtil.isSecure; import static org.asynchttpclient.providers.netty.util.HttpUtil.isWebSocket; -import static org.asynchttpclient.providers.netty.handler.Processor.newHttpProcessor; -import static org.asynchttpclient.providers.netty.handler.Processor.newWsProcessor; import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpClientConfig; @@ -202,20 +202,26 @@ private Timer newNettyTimer() { return nettyTimer; } - private SSLEngine createSSLEngine() throws IOException, GeneralSecurityException { + public SslHandler createSslHandler(String peerHost, int peerPort) throws IOException, GeneralSecurityException { + SSLEngine sslEngine = null; if (nettyProviderConfig.getSslEngineFactory() != null) { - return nettyProviderConfig.getSslEngineFactory().newSSLEngine(); + sslEngine = nettyProviderConfig.getSslEngineFactory().newSSLEngine(); } else { SSLContext sslContext = config.getSSLContext(); if (sslContext == null) sslContext = SslUtils.getInstance().getSSLContext(config.isAcceptAnyCertificate()); - SSLEngine sslEngine = sslContext.createSSLEngine(); + sslEngine = sslContext.createSSLEngine(peerHost, peerPort); sslEngine.setUseClientMode(true); - return sslEngine; } + + SslHandler sslHandler = new SslHandler(sslEngine); + if (handshakeTimeoutInMillis > 0) + sslHandler.setHandshakeTimeoutMillis(handshakeTimeoutInMillis); + + return sslHandler; } public void configureProcessor(NettyRequestSender requestSender, AtomicBoolean closed) { @@ -258,13 +264,8 @@ protected void initChannel(Channel ch) throws Exception { @Override protected void initChannel(Channel ch) throws Exception { - SSLEngine sslEngine = createSSLEngine(); - SslHandler sslHandler = new SslHandler(sslEngine); - if (handshakeTimeoutInMillis > 0) - sslHandler.setHandshakeTimeoutMillis(handshakeTimeoutInMillis); - ChannelPipeline pipeline = ch.pipeline()// - .addLast(SSL_HANDLER, sslHandler)// + .addLast(SSL_HANDLER, new SslInitializer(Channels.this)) .addLast(HTTP_HANDLER, newHttpClientCodec()); if (config.isCompressionEnabled()) { @@ -284,7 +285,7 @@ protected void initChannel(Channel ch) throws Exception { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline()// - .addLast(SSL_HANDLER, new SslHandler(createSSLEngine()))// + .addLast(SSL_HANDLER, new SslInitializer(Channels.this))// .addLast(HTTP_HANDLER, newHttpClientCodec())// .addLast(WS_PROCESSOR, wsProcessor); @@ -330,7 +331,7 @@ public void verifyChannelPipeline(ChannelPipeline pipeline, String scheme) throw pipeline.remove(SSL_HANDLER); } else if (isSecure) - pipeline.addFirst(SSL_HANDLER, new SslHandler(createSSLEngine())); + pipeline.addFirst(SSL_HANDLER, new SslInitializer(Channels.this)); } protected HttpClientCodec newHttpClientCodec() { @@ -346,7 +347,7 @@ protected HttpClientCodec newHttpClientCodec() { } } - public void upgradeProtocol(ChannelPipeline p, String scheme) throws IOException, GeneralSecurityException { + public void upgradeProtocol(ChannelPipeline p, String scheme, String host, int port) throws IOException, GeneralSecurityException { if (p.get(HTTP_HANDLER) != null) { p.remove(HTTP_HANDLER); } @@ -354,7 +355,7 @@ public void upgradeProtocol(ChannelPipeline p, String scheme) throws IOException if (isSecure(scheme)) { if (p.get(SSL_HANDLER) == null) { p.addFirst(HTTP_HANDLER, newHttpClientCodec()); - p.addFirst(SSL_HANDLER, new SslHandler(createSSLEngine())); + p.addFirst(SSL_HANDLER, createSslHandler(host, port)); } else { p.addAfter(SSL_HANDLER, HTTP_HANDLER, newHttpClientCodec()); } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java new file mode 100644 index 0000000000..0250f350ac --- /dev/null +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java @@ -0,0 +1,54 @@ +/* + * Copyright 2014 AsyncHttpClient Project. + * + * 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 io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; +import io.netty.channel.ChannelPromise; +import io.netty.handler.ssl.SslHandler; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +/** + * On connect, replaces itself with a SslHandler that has a SSLEngine configured with the remote host and port. + * + * @author slandelle + */ +public class SslInitializer extends ChannelOutboundHandlerAdapter { + + private final Channels channels; + + public SslInitializer(Channels channels) { + this.channels = channels; + new Exception().printStackTrace(); + } + + @Override + public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) + throws Exception { + + InetSocketAddress remoteInetSocketAddress = (InetSocketAddress) remoteAddress; + String peerHost = remoteInetSocketAddress.getHostName(); + int peerPort = remoteInetSocketAddress.getPort(); + + SslHandler sslHandler = channels.createSslHandler(peerHost, peerPort); + + ctx.pipeline().replace(Channels.SSL_HANDLER, Channels.SSL_HANDLER, sslHandler); + + ctx.connect(remoteAddress, localAddress, promise); + } +} 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 57f81b6142..044b3b3316 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 @@ -347,7 +347,13 @@ private boolean handleConnectOKAndExit(int statusCode, Realm realm, final Reques try { LOGGER.debug("Connecting to proxy {} for scheme {}", proxyServer, request.getUrl()); - channels.upgradeProtocol(channel.pipeline(), request.getURI().getScheme()); + + URI requestURI = request.getURI(); + String scheme = requestURI.getScheme(); + String host = AsyncHttpProviderUtils.getHost(requestURI); + int port = AsyncHttpProviderUtils.getPort(requestURI); + + channels.upgradeProtocol(channel.pipeline(), scheme, host, port); } catch (Throwable ex) { channels.abort(future, ex); } From f486d7b3ef6fe5879006a458cd07ab3a2c52d303 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 13 Jun 2014 15:43:47 +0200 Subject: [PATCH 0046/2020] unused import --- .../providers/netty/request/NettyConnectListener.java | 1 - 1 file changed, 1 deletion(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java index c023406e44..45f5ef4bfc 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java @@ -33,7 +33,6 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLSession; import java.net.ConnectException; import java.nio.channels.ClosedChannelException; From 1a3c9a966bc732e908e11523abfcddbb72e8d5d5 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 13 Jun 2014 15:46:35 +0200 Subject: [PATCH 0047/2020] woups --- .../asynchttpclient/providers/netty/channel/SslInitializer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java index 0250f350ac..e0df603cd0 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java @@ -34,7 +34,6 @@ public class SslInitializer extends ChannelOutboundHandlerAdapter { public SslInitializer(Channels channels) { this.channels = channels; - new Exception().printStackTrace(); } @Override From 26b588383218777ac96f77379bc61c7ded570f31 Mon Sep 17 00:00:00 2001 From: Lukasz Kryger Date: Sat, 14 Jun 2014 22:48:08 +0100 Subject: [PATCH 0048/2020] Minor fix in the contributing section of README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 07afb045d3..3e59fffe30 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ Here a the few rules we'd like you to respect if you do so: * Use a 140 chars line max length * Don't use * imports * Stick to the org, com, javax, java imports order -* Your PR can contain multiple commits when submitting, but once it's been reviewed, we'll ask you to squash the them into a single once +* Your PR can contain multiple commits when submitting, but once it's been reviewed, we'll ask you to squash them into a single one * Regarding licensing: * You must be the original author of the code you suggest. * If not, you have to prove that the original code was published under Apache License 2 and properly mention original copyrights. From 99e139103bbf92faf48de77ac6c26657fa92aed6 Mon Sep 17 00:00:00 2001 From: oleksiys Date: Tue, 17 Jun 2014 01:32:25 -0700 Subject: [PATCH 0049/2020] [master] + fix issue #1691 https://java.net/jira/browse/GRIZZLY-1691 "Support RFC 7238, HTTP Status Code "308 Permanent Redirect"" --- providers/grizzly/pom.xml | 2 +- .../org/asynchttpclient/providers/grizzly/EventHandler.java | 5 ++++- .../providers/grizzly/statushandler/RedirectHandler.java | 5 +++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/providers/grizzly/pom.xml b/providers/grizzly/pom.xml index 9de0ff62a0..b55f00865e 100644 --- a/providers/grizzly/pom.xml +++ b/providers/grizzly/pom.xml @@ -14,7 +14,7 @@ - 2.3.13 + 2.3.14 1.1 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 ec251b8955..934bc52bc4 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 @@ -65,7 +65,9 @@ public final class EventHandler { HANDLER_MAP.put(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407.getStatusCode(), ProxyAuthorizationHandler.INSTANCE); HANDLER_MAP.put(HttpStatus.MOVED_PERMANENTLY_301.getStatusCode(), RedirectHandler.INSTANCE); HANDLER_MAP.put(HttpStatus.FOUND_302.getStatusCode(), RedirectHandler.INSTANCE); + HANDLER_MAP.put(HttpStatus.SEE_OTHER_303.getStatusCode(), RedirectHandler.INSTANCE); HANDLER_MAP.put(HttpStatus.TEMPORARY_REDIRECT_307.getStatusCode(), RedirectHandler.INSTANCE); + HANDLER_MAP.put(HttpStatus.PERMANENT_REDIRECT_308.getStatusCode(), RedirectHandler.INSTANCE); } private final AsyncHttpClientConfig config; @@ -405,7 +407,8 @@ public static boolean isRedirect(final int status) { return HttpStatus.MOVED_PERMANENTLY_301.statusMatches(status)// || HttpStatus.FOUND_302.statusMatches(status)// || HttpStatus.SEE_OTHER_303.statusMatches(status)// - || HttpStatus.TEMPORARY_REDIRECT_307.statusMatches(status); + || HttpStatus.TEMPORARY_REDIRECT_307.statusMatches(status) + || HttpStatus.PERMANENT_REDIRECT_308.statusMatches(status); } // ----------------------------------------------------- Private Methods 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 c7bd5d514b..5d914b0f75 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -94,7 +94,8 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpT private boolean sendAsGet(final HttpResponsePacket response, final HttpTxContext ctx) { final int statusCode = response.getStatus(); - return !(statusCode < 302 || statusCode > 303) && !(statusCode == 302 && ctx.getProvider().getClientConfig().isStrict302Handling()); + return !(statusCode < 302 || statusCode > 303) && + !(statusCode == 302 && ctx.getProvider().getClientConfig().isStrict302Handling()); } } // END RedirectHandler From c04abace94cd6f14c5174235355e77eaf6ff891b Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 17 Jun 2014 10:38:30 +0200 Subject: [PATCH 0050/2020] Bump 1.8.11 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e59fffe30..539576791c 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.8.10 + 1.8.11 ``` From 5c3ae9b8e85c41343dd2b93c2069a4f9c7b9b89d Mon Sep 17 00:00:00 2001 From: salilsurendran Date: Tue, 17 Jun 2014 20:51:06 -0700 Subject: [PATCH 0051/2020] Making minor changes --- .../AsyncHttpClientConfigDefaults.java | 8 +- .../org/asynchttpclient/util/MiscUtil.java | 96 ++++++++++--------- .../asynchttpclient/util/MiscUtilTest.java | 56 +++++------ 3 files changed, 83 insertions(+), 77 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java index 16818d1aa9..bfaae5cb56 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java @@ -17,15 +17,13 @@ import static org.asynchttpclient.util.MiscUtil.getBooleanValue; import static org.asynchttpclient.util.MiscUtil.getIntValue; import org.asynchttpclient.util.DefaultHostnameVerifier; + import javax.net.ssl.HostnameVerifier; -import org.asynchttpclient.util.DefaultHostnameVerifier; -import static org.asynchttpclient.util.MiscUtil.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + public final class AsyncHttpClientConfigDefaults { - + private AsyncHttpClientConfigDefaults() { } diff --git a/api/src/main/java/org/asynchttpclient/util/MiscUtil.java b/api/src/main/java/org/asynchttpclient/util/MiscUtil.java index 486140035b..060d603f84 100644 --- a/api/src/main/java/org/asynchttpclient/util/MiscUtil.java +++ b/api/src/main/java/org/asynchttpclient/util/MiscUtil.java @@ -21,8 +21,8 @@ public class MiscUtil { - - public final static Logger logger = LoggerFactory.getLogger(MiscUtil.class); + + public final static Logger logger = LoggerFactory.getLogger(MiscUtil.class); private MiscUtil() { } @@ -47,51 +47,59 @@ public static boolean isNonEmpty(Map map) { return map != null && !map.isEmpty(); } + //The getBooleanValue() method replaces this and reads the property from properties file + //too. Plus has a better check for invalid boolean values. /* public static boolean getBoolean(String systemPropName, boolean defaultValue) { String systemPropValue = System.getProperty(systemPropName); return systemPropValue != null ? systemPropValue.equalsIgnoreCase("true") : defaultValue; }*/ - public static Integer getIntValue(String property,int defaultValue){ - //Read system property and if not null return that. - Integer value = Integer.getInteger(property); - if(value != null) - return value; - Properties asyncHttpClientConfigProperties = AsyncImplHelper.getAsyncImplProperties(); - if(asyncHttpClientConfigProperties!=null){ - String valueString=asyncHttpClientConfigProperties.getProperty(property); - try{ - //If property is present and is non null parse it. - if(valueString != null) - return Integer.parseInt(valueString); - }catch(NumberFormatException e){ - //If property couldn't be parsed log the error message and return default value. - logger.error("Property : " + property + " has value = " + valueString + - " which couldn't be parsed to an int value. Returning default value: " + defaultValue,e); - } - } - return defaultValue; - } - - public static Boolean getBooleanValue(String property,boolean defaultValue){ - String value = System.getProperty(property); - Properties asyncHttpClientConfigProperties = AsyncImplHelper.getAsyncImplProperties(); - //If system property is invalid and property file is present then read value - //from property file - if(!MiscUtil.isValidBooleanValue(value) && asyncHttpClientConfigProperties!=null) - value=asyncHttpClientConfigProperties.getProperty(property); - //If valid value has been found return that value - if(MiscUtil.isValidBooleanValue(value)) - return Boolean.parseBoolean(value); - //If a value has been specified but can't be parsed into a boolean log a message - //stating that value is unparseable and default values are being used. - if(value != null) - logger.error("Property : " + property + " has value = " + value + - " which couldn't be parsed to an boolean value. Returning default value: " + defaultValue); - return defaultValue; - } - - private static boolean isValidBooleanValue(String value){ - return value != null && ("true".equalsIgnoreCase(value)||"false".equalsIgnoreCase(value)); - } + public static Integer getIntValue(String property,int defaultValue){ + //Read system property and if not null return that. + Integer value = Integer.getInteger(property); + if(value != null) + return value; + Properties asyncHttpClientConfigProperties = AsyncImplHelper.getAsyncImplProperties(); + if(asyncHttpClientConfigProperties!=null){ + String valueString=asyncHttpClientConfigProperties.getProperty(property); + try{ + //If property is present and is non null parse it. + if(valueString != null) + return Integer.parseInt(valueString); + }catch(NumberFormatException e){ + //If property couldn't be parsed log the error message and return default value. + logger.error("Property : " + property + " has value = " + valueString + + " which couldn't be parsed to an int value. Returning default value: " + defaultValue,e); + } + } + return defaultValue; + } + + private static boolean isValidBooleanValue(String value){ + return value != null && ("true".equalsIgnoreCase(value)||"false".equalsIgnoreCase(value)); + } + + public static Boolean getBooleanValue(String property,boolean defaultValue){ + + // get from System property first + String value = System.getProperty(property); + if(isValidBooleanValue(value)) + return Boolean.parseBoolean(value); + + // get from property file + Properties asyncHttpClientConfigProperties = AsyncImplHelper.getAsyncImplProperties(); + if(asyncHttpClientConfigProperties!=null){ + value=asyncHttpClientConfigProperties.getProperty(property); + if(isValidBooleanValue(value)) + return Boolean.parseBoolean(value); + } + + //have to use the default value now + if(value != null) + logger.error("Property : " + property + " has value = " + value + + " which couldn't be parsed to an boolean value. Returning default value: " + defaultValue); + + return defaultValue; + } + } diff --git a/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java b/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java index 52b698b40e..695ec1f829 100644 --- a/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java +++ b/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java @@ -44,35 +44,35 @@ public void testGetIntegerValue() { System.clearProperty(MY_SPECIAL_INT_PROPERTY); } - @Test - public void testGetBooleanValue() { - // Setup a AsyncImplHelperMock that returns a mock - // asynchttpclient.properties with a value - // set for 'my.special.int.property' property - Properties properties = new Properties(); - properties.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "true"); - AsyncImplHelperMock asyncImplHelperMock = new AsyncImplHelperMock(properties); + @Test + public void testGetBooleanValue() { + // Setup a AsyncImplHelperMock that returns a mock + // asynchttpclient.properties with a value + // set for 'my.special.int.property' property + Properties properties = new Properties(); + properties.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "true"); + AsyncImplHelperMock asyncImplHelperMock = new AsyncImplHelperMock(properties); - - // Assert that the getBooleanValue() method returns TRUE - Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); - // Set a system property that overrides the value in the - // asynchttpclient.properties - System.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "false"); - // Assert false is returned, i.e. system property takes precedence over - // property in asynchttpclient.properties - Assert.assertFalse(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, true)); - // Clear the system property - System.clearProperty(MY_SPECIAL_BOOLEAN_PROPERTY); - // Assert that the value set in asynchttpclient.properties is returned - Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); - // Set a corrupt system property - System.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "corrupt property"); - // Assert that the value set in asynchttpclient.properties is returned - // even though corrupt system property is set. - Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); - System.clearProperty(MY_SPECIAL_BOOLEAN_PROPERTY); - } + + // Assert that the getBooleanValue() method returns TRUE + Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); + // Set a system property that overrides the value in the + // asynchttpclient.properties + System.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "false"); + // Assert false is returned, i.e. system property takes precedence over + // property in asynchttpclient.properties + Assert.assertFalse(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, true)); + // Clear the system property + System.clearProperty(MY_SPECIAL_BOOLEAN_PROPERTY); + // Assert that the value set in asynchttpclient.properties is returned + Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); + // Set a corrupt system property + System.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "corrupt property"); + // Assert that the value set in asynchttpclient.properties is returned + // even though corrupt system property is set. + Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); + System.clearProperty(MY_SPECIAL_BOOLEAN_PROPERTY); + } @Test public void testGetDefaultIntegerValue() { From d677d01e51a89c28f511dda40aeee0e484c64230 Mon Sep 17 00:00:00 2001 From: salilsurendran Date: Thu, 19 Jun 2014 11:56:28 -0700 Subject: [PATCH 0052/2020] Committing to fix formatting and indentation issues. --- .../org/asynchttpclient/util/MiscUtil.java | 72 +++++----- .../AsyncHttpClientConfigBuilderTest.java | 30 ++-- .../asynchttpclient/AsyncImplHelperMock.java | 22 +-- .../asynchttpclient/util/MiscUtilTest.java | 132 +++++++++--------- pom.xml | 24 ++-- 5 files changed, 138 insertions(+), 142 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/util/MiscUtil.java b/api/src/main/java/org/asynchttpclient/util/MiscUtil.java index 060d603f84..d7092bdf42 100644 --- a/api/src/main/java/org/asynchttpclient/util/MiscUtil.java +++ b/api/src/main/java/org/asynchttpclient/util/MiscUtil.java @@ -18,8 +18,6 @@ import java.util.Map; import java.util.Properties; - - public class MiscUtil { public final static Logger logger = LoggerFactory.getLogger(MiscUtil.class); @@ -47,59 +45,63 @@ public static boolean isNonEmpty(Map map) { return map != null && !map.isEmpty(); } - //The getBooleanValue() method replaces this and reads the property from properties file - //too. Plus has a better check for invalid boolean values. - /* public static boolean getBoolean(String systemPropName, boolean defaultValue) { - String systemPropValue = System.getProperty(systemPropName); - return systemPropValue != null ? systemPropValue.equalsIgnoreCase("true") : defaultValue; - }*/ - - public static Integer getIntValue(String property,int defaultValue){ - //Read system property and if not null return that. + // The getBooleanValue() method replaces this and reads the property from + // properties file + // too. Plus has a better check for invalid boolean values. + /* + * public static boolean getBoolean(String systemPropName, boolean + * defaultValue) { String systemPropValue = + * System.getProperty(systemPropName); return systemPropValue != null ? + * systemPropValue.equalsIgnoreCase("true") : defaultValue; } + */ + + public static Integer getIntValue(String property, int defaultValue) { + // Read system property and if not null return that. Integer value = Integer.getInteger(property); - if(value != null) + if (value != null) return value; Properties asyncHttpClientConfigProperties = AsyncImplHelper.getAsyncImplProperties(); - if(asyncHttpClientConfigProperties!=null){ - String valueString=asyncHttpClientConfigProperties.getProperty(property); - try{ - //If property is present and is non null parse it. - if(valueString != null) + if (asyncHttpClientConfigProperties != null) { + String valueString = asyncHttpClientConfigProperties.getProperty(property); + try { + // If property is present and is non null parse it. + if (valueString != null) return Integer.parseInt(valueString); - }catch(NumberFormatException e){ - //If property couldn't be parsed log the error message and return default value. - logger.error("Property : " + property + " has value = " + valueString + - " which couldn't be parsed to an int value. Returning default value: " + defaultValue,e); + } catch (NumberFormatException e) { + // If property couldn't be parsed log the error message and + // return default value. + logger.error("Property : " + property + " has value = " + valueString + + " which couldn't be parsed to an int value. Returning default value: " + defaultValue, e); } } return defaultValue; } - private static boolean isValidBooleanValue(String value){ - return value != null && ("true".equalsIgnoreCase(value)||"false".equalsIgnoreCase(value)); + private static boolean isValidBooleanValue(String value) { + return value != null && ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)); } - public static Boolean getBooleanValue(String property,boolean defaultValue){ + public static Boolean getBooleanValue(String property, boolean defaultValue) { // get from System property first String value = System.getProperty(property); - if(isValidBooleanValue(value)) - return Boolean.parseBoolean(value); + if (isValidBooleanValue(value)) + return Boolean.parseBoolean(value); // get from property file Properties asyncHttpClientConfigProperties = AsyncImplHelper.getAsyncImplProperties(); - if(asyncHttpClientConfigProperties!=null){ - value=asyncHttpClientConfigProperties.getProperty(property); - if(isValidBooleanValue(value)) - return Boolean.parseBoolean(value); + if (asyncHttpClientConfigProperties != null) { + value = asyncHttpClientConfigProperties.getProperty(property); + if (isValidBooleanValue(value)) + return Boolean.parseBoolean(value); } - //have to use the default value now - if(value != null) - logger.error("Property : " + property + " has value = " + value + - " which couldn't be parsed to an boolean value. Returning default value: " + defaultValue); + // have to use the default value now + if (value != null) + logger.error("Property : " + property + " has value = " + value + + " which couldn't be parsed to an boolean value. Returning default value: " + defaultValue); return defaultValue; - } + } } diff --git a/api/src/test/java/org/asynchttpclient/AsyncHttpClientConfigBuilderTest.java b/api/src/test/java/org/asynchttpclient/AsyncHttpClientConfigBuilderTest.java index 02d532f3d4..44818fb541 100644 --- a/api/src/test/java/org/asynchttpclient/AsyncHttpClientConfigBuilderTest.java +++ b/api/src/test/java/org/asynchttpclient/AsyncHttpClientConfigBuilderTest.java @@ -3,28 +3,28 @@ import org.testng.Assert; import org.testng.annotations.Test; -public class AsyncHttpClientConfigBuilderTest { - +public class AsyncHttpClientConfigBuilderTest { + @Test - public void testDefaultConfigValues(){ + public void testDefaultConfigValues() { AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().build(); - Assert.assertEquals(config.getConnectionTimeoutInMs(),60000); - Assert.assertEquals(config.getRequestTimeoutInMs(),60000); - Assert.assertEquals(config.getIdleConnectionTimeoutInMs(),60000); + Assert.assertEquals(config.getConnectionTimeoutInMs(), 60000); + Assert.assertEquals(config.getRequestTimeoutInMs(), 60000); + Assert.assertEquals(config.getIdleConnectionTimeoutInMs(), 60000); Assert.assertFalse(config.isCompressionEnabled()); Assert.assertFalse(config.isRedirectEnabled()); - System.setProperty(AsyncHttpClientConfig.ASYNC_CLIENT+"connectionTimeoutInMs","1000"); - System.setProperty(AsyncHttpClientConfig.ASYNC_CLIENT+"requestTimeoutInMs","500"); - System.setProperty(AsyncHttpClientConfig.ASYNC_CLIENT+"compressionEnabled","true"); - System.setProperty(AsyncHttpClientConfig.ASYNC_CLIENT+"redirectsEnabled","true"); + System.setProperty(AsyncHttpClientConfig.ASYNC_CLIENT + "connectionTimeoutInMs", "1000"); + System.setProperty(AsyncHttpClientConfig.ASYNC_CLIENT + "requestTimeoutInMs", "500"); + System.setProperty(AsyncHttpClientConfig.ASYNC_CLIENT + "compressionEnabled", "true"); + System.setProperty(AsyncHttpClientConfig.ASYNC_CLIENT + "redirectsEnabled", "true"); config = new AsyncHttpClientConfig.Builder().build(); - Assert.assertEquals(config.getConnectionTimeoutInMs(),1000); + Assert.assertEquals(config.getConnectionTimeoutInMs(), 1000); Assert.assertEquals(config.getRequestTimeoutInMs(), 500); Assert.assertTrue(config.isCompressionEnabled()); Assert.assertTrue(config.isRedirectEnabled()); - System.clearProperty(AsyncHttpClientConfig.ASYNC_CLIENT+"connectionTimeoutInMs"); - System.clearProperty(AsyncHttpClientConfig.ASYNC_CLIENT+"requestTimeoutInMs"); - System.clearProperty(AsyncHttpClientConfig.ASYNC_CLIENT+"compressionEnabled"); - System.clearProperty(AsyncHttpClientConfig.ASYNC_CLIENT+"defaultRedirectsEnabled"); + System.clearProperty(AsyncHttpClientConfig.ASYNC_CLIENT + "connectionTimeoutInMs"); + System.clearProperty(AsyncHttpClientConfig.ASYNC_CLIENT + "requestTimeoutInMs"); + System.clearProperty(AsyncHttpClientConfig.ASYNC_CLIENT + "compressionEnabled"); + System.clearProperty(AsyncHttpClientConfig.ASYNC_CLIENT + "defaultRedirectsEnabled"); } } diff --git a/api/src/test/java/org/asynchttpclient/AsyncImplHelperMock.java b/api/src/test/java/org/asynchttpclient/AsyncImplHelperMock.java index 562af7a3dd..e9f18af883 100644 --- a/api/src/test/java/org/asynchttpclient/AsyncImplHelperMock.java +++ b/api/src/test/java/org/asynchttpclient/AsyncImplHelperMock.java @@ -8,16 +8,16 @@ import org.asynchttpclient.util.AsyncImplHelper; public class AsyncImplHelperMock extends MockUp { - - private static Properties properties; - - public AsyncImplHelperMock(Properties properties){ - this.properties=properties; - } - - @Mock - public Properties getAsyncImplProperties() { - return properties; - } + + private static Properties properties; + + public AsyncImplHelperMock(Properties properties) { + this.properties = properties; + } + + @Mock + public Properties getAsyncImplProperties() { + return properties; + } } diff --git a/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java b/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java index 695ec1f829..794090f43e 100644 --- a/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java +++ b/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java @@ -7,14 +7,13 @@ import org.testng.annotations.Test; public class MiscUtilTest { - private final Integer MY_SPECIAL_INT_VALUE = 10; - private final Integer MY_SPECIAL_SYSTEM_INT_VALUE = 100; - private final String MY_SPECIAL_INT_PROPERTY = "my.special.int.property"; + private final Integer MY_SPECIAL_INT_VALUE = 10; + private final Integer MY_SPECIAL_SYSTEM_INT_VALUE = 100; + private final String MY_SPECIAL_INT_PROPERTY = "my.special.int.property"; private final String MY_SPECIAL_BOOLEAN_PROPERTY = "my.special.boolean.property"; private final Integer MY_SPECIAL_INT_DEFAULT_VALUE = -100; - - @Test + @Test public void testGetIntegerValue() { // Setup a AsyncImplHelperMock that returns a mock // asynchttpclient.properties with a value @@ -23,7 +22,6 @@ public void testGetIntegerValue() { properties.setProperty(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_VALUE.toString()); AsyncImplHelperMock asyncImplHelperMock = new AsyncImplHelperMock(properties); - // Assert that the getIntValue() method returns 10 Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, -1), MY_SPECIAL_INT_VALUE); // Set a system property that overrides the value in the @@ -31,7 +29,7 @@ public void testGetIntegerValue() { System.setProperty(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_SYSTEM_INT_VALUE.toString()); // Assert 100 is returned, i.e. system property takes precedence over // property in asynchttpclient.properties - Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, -1),MY_SPECIAL_SYSTEM_INT_VALUE); + Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, -1), MY_SPECIAL_SYSTEM_INT_VALUE); // Clear the system property System.clearProperty(MY_SPECIAL_INT_PROPERTY); // Assert that the value set in asynchttpclient.properties is returned @@ -43,8 +41,8 @@ public void testGetIntegerValue() { Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, -1), MY_SPECIAL_INT_VALUE); System.clearProperty(MY_SPECIAL_INT_PROPERTY); } - - @Test + + @Test public void testGetBooleanValue() { // Setup a AsyncImplHelperMock that returns a mock // asynchttpclient.properties with a value @@ -53,7 +51,6 @@ public void testGetBooleanValue() { properties.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "true"); AsyncImplHelperMock asyncImplHelperMock = new AsyncImplHelperMock(properties); - // Assert that the getBooleanValue() method returns TRUE Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); // Set a system property that overrides the value in the @@ -73,66 +70,63 @@ public void testGetBooleanValue() { Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); System.clearProperty(MY_SPECIAL_BOOLEAN_PROPERTY); } - - @Test - public void testGetDefaultIntegerValue() { - - // Assert that the getIntValue() method returns the default value if - // Properties is not present - Assert.assertEquals( - MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), - MY_SPECIAL_INT_DEFAULT_VALUE); - // Setup up a mock of a asynchttpclient.properties that initially is empty - Properties properties = new Properties(); - AsyncImplHelperMock asyncImplHelperMock = new AsyncImplHelperMock(properties); - - // Assert that the getIntValue() method returns the default value if there is no - // property set in the asynchttpclient.properties - Assert.assertEquals( - MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), - MY_SPECIAL_INT_DEFAULT_VALUE); - //Now set a corrupt value in the asynchttpclient.properties - properties.setProperty(MY_SPECIAL_INT_PROPERTY, "corrupt property"); - // Assert that the getIntValue() method returns the default value if there is a corrupt - // property set in the asynchttpclient.properties - Assert.assertEquals( - MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), - MY_SPECIAL_INT_DEFAULT_VALUE); - // Set a corrupt system property - System.setProperty(MY_SPECIAL_INT_PROPERTY, "corrupt property"); - // Assert that even though values set in asynchttpclient.properties and system property is corrupt the default value is - //returned - Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), - MY_SPECIAL_INT_DEFAULT_VALUE); - System.clearProperty(MY_SPECIAL_INT_PROPERTY); - } + @Test + public void testGetDefaultIntegerValue() { + + // Assert that the getIntValue() method returns the default value if + // Properties is not present + Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), MY_SPECIAL_INT_DEFAULT_VALUE); + // Setup up a mock of a asynchttpclient.properties that initially is + // empty + Properties properties = new Properties(); + AsyncImplHelperMock asyncImplHelperMock = new AsyncImplHelperMock(properties); - - - @Test - public void testGetDefaultBooleanValue() { - // Assert that the getBooleanValue() method returns the default value if - // asynchttpclient.properties is not present - Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, true)); - // Setup up a mock of a asynchttpclient.properties that initially is empty - Properties properties = new Properties(); - AsyncImplHelperMock asyncImplHelperMock = new AsyncImplHelperMock(properties); + // Assert that the getIntValue() method returns the default value if + // there is no + // property set in the asynchttpclient.properties + Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), MY_SPECIAL_INT_DEFAULT_VALUE); + // Now set a corrupt value in the asynchttpclient.properties + properties.setProperty(MY_SPECIAL_INT_PROPERTY, "corrupt property"); + // Assert that the getIntValue() method returns the default value if + // there is a corrupt + // property set in the asynchttpclient.properties + Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), MY_SPECIAL_INT_DEFAULT_VALUE); + // Set a corrupt system property + System.setProperty(MY_SPECIAL_INT_PROPERTY, "corrupt property"); + // Assert that even though values set in asynchttpclient.properties and + // system property is corrupt the default value is + // returned + Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), MY_SPECIAL_INT_DEFAULT_VALUE); + System.clearProperty(MY_SPECIAL_INT_PROPERTY); + } - - // Assert that the getBooleanValue() method returns the default value if there is no - // property set in the asynchttpclient.properties - Assert.assertTrue(!MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); - //Now set a corrupt value in the asynchttpclient.properties - properties.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "corrupt property"); - // Assert that the getBooleanValue() method returns the default value if there is a corrupt - // property set in the asynchttpclient.properties - Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, true)); - // Set a corrupt system property - System.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "corrupt property"); - // Assert that even though values set in asynchttpclient.properties and system property is corrupt the default value is - //returned - Assert.assertTrue(!MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); - System.clearProperty(MY_SPECIAL_INT_PROPERTY); - } + @Test + public void testGetDefaultBooleanValue() { + // Assert that the getBooleanValue() method returns the default value if + // asynchttpclient.properties is not present + Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, true)); + // Setup up a mock of a asynchttpclient.properties that initially is + // empty + Properties properties = new Properties(); + AsyncImplHelperMock asyncImplHelperMock = new AsyncImplHelperMock(properties); + + // Assert that the getBooleanValue() method returns the default value if + // there is no + // property set in the asynchttpclient.properties + Assert.assertTrue(!MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); + // Now set a corrupt value in the asynchttpclient.properties + properties.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "corrupt property"); + // Assert that the getBooleanValue() method returns the default value if + // there is a corrupt + // property set in the asynchttpclient.properties + Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, true)); + // Set a corrupt system property + System.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "corrupt property"); + // Assert that even though values set in asynchttpclient.properties and + // system property is corrupt the default value is + // returned + Assert.assertTrue(!MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); + System.clearProperty(MY_SPECIAL_INT_PROPERTY); + } } diff --git a/pom.xml b/pom.xml index 47d662b815..4df2c85397 100644 --- a/pom.xml +++ b/pom.xml @@ -514,17 +514,17 @@ test - com.e-movimento.tinytools - privilegedaccessor - ${privilegedaccessor.version} - test - - - com.googlecode.jmockit - jmockit - ${jmockit.version} - test - + com.e-movimento.tinytools + privilegedaccessor + ${privilegedaccessor.version} + test + + + com.googlecode.jmockit + jmockit + ${jmockit.version} + test + http://oss.sonatype.org/content/repositories/snapshots @@ -539,7 +539,7 @@ 6.0.29 2.4 1.3 - 1.5 + 1.5 1.2.2 From c5e9d8f7bf99f56d0e76f75a8a934cd126939664 Mon Sep 17 00:00:00 2001 From: Eugene Ivakhno Date: Fri, 20 Jun 2014 14:55:54 +0300 Subject: [PATCH 0053/2020] fixed realm URI computation for the case of non absolute uri, null query, and not omit query --- .../asynchttpclient/providers/netty/handler/HttpProtocol.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 044b3b3316..d0de464588 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 @@ -220,7 +220,7 @@ private final String computeRealmURI(Realm realm, URI requestURI) throws URISynt return requestURI.toString(); } } else { - if (realm.isOmitQuery() && isNonEmpty(requestURI.getQuery())) { + if (realm.isOmitQuery() || !isNonEmpty(requestURI.getQuery())) { return requestURI.getPath(); } else { return requestURI.getPath() + "?" + requestURI.getQuery(); From 9fdf6e79bff1b863c7a8bfba2f57daed8da82ddb Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 1 Jul 2014 09:47:16 +0200 Subject: [PATCH 0054/2020] typo --- api/src/main/java/org/asynchttpclient/Request.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/Request.java b/api/src/main/java/org/asynchttpclient/Request.java index 248f6e5e5e..a54b96f32f 100644 --- a/api/src/main/java/org/asynchttpclient/Request.java +++ b/api/src/main/java/org/asynchttpclient/Request.java @@ -126,9 +126,9 @@ public interface Request { BodyGenerator getBodyGenerator(); /** - * Return the current size of the content-lenght header based on the body's size. + * Return the current size of the content-length header based on the body's size. * - * @return the current size of the content-lenght header based on the body's size. + * @return the current size of the content-length header based on the body's size. */ long getContentLength(); From e5f188aaaef43f2eca45f5ccdc6548d7d32f6f4f Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 1 Jul 2014 10:43:06 +0200 Subject: [PATCH 0055/2020] Fix Fluent(CaseInsensitiveStrings)Map.add javadoc, close #581 --- .../asynchttpclient/FluentCaseInsensitiveStringsMap.java | 3 +-- api/src/main/java/org/asynchttpclient/FluentStringsMap.java | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java b/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java index 77a9e4e20f..c5417797e9 100644 --- a/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java +++ b/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java @@ -64,8 +64,7 @@ public FluentCaseInsensitiveStringsMap(Map> src) { * Adds the specified values and returns this object. * * @param key The key - * @param values The value(s); if null then this method has no effect. Use the empty string to - * generate an empty value + * @param values The value(s); if the array is null then this method has no effect. Individual null values are turned into empty strings * @return This object */ public FluentCaseInsensitiveStringsMap add(String key, String... values) { diff --git a/api/src/main/java/org/asynchttpclient/FluentStringsMap.java b/api/src/main/java/org/asynchttpclient/FluentStringsMap.java index 336ffbbc97..a8fa9e1a77 100644 --- a/api/src/main/java/org/asynchttpclient/FluentStringsMap.java +++ b/api/src/main/java/org/asynchttpclient/FluentStringsMap.java @@ -58,8 +58,7 @@ public FluentStringsMap(Map> src) { * Adds the specified values and returns this object. * * @param key The key - * @param values The value(s); if null then this method has no effect. Use the empty string to - * generate an empty value + * @param values The value(s); if the array is null then this method has no effect * @return This object */ public FluentStringsMap add(String key, String... values) { @@ -73,8 +72,7 @@ public FluentStringsMap add(String key, String... values) { * Adds the specified values and returns this object. * * @param key The key - * @param values The value(s); if null then this method has no effect. Use an empty collection - * to generate an empty value + * @param values The value(s); if the array is null then this method has no effect * @return This object */ public FluentStringsMap add(String key, Collection values) { From f7492801957848e38d6f304a0c3d18a05a278cd3 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 1 Jul 2014 10:46:59 +0200 Subject: [PATCH 0056/2020] Optimize Fluent(CaseInsensitive)StringsMap for single value, close #580 --- .../FluentCaseInsensitiveStringsMap.java | 20 +++++++++++++++++++ .../org/asynchttpclient/FluentStringsMap.java | 13 ++++++++++++ 2 files changed, 33 insertions(+) diff --git a/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java b/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java index c5417797e9..0b9b1ad192 100644 --- a/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java +++ b/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java @@ -60,6 +60,26 @@ public FluentCaseInsensitiveStringsMap(Map> src) { } } + public FluentCaseInsensitiveStringsMap add(String key, String value) { + if (key != null) { + String lcKey = key.toLowerCase(Locale.ENGLISH); + String realKey = keyLookup.get(lcKey); + + List curValues = null; + if (realKey == null) { + keyLookup.put(lcKey, key); + curValues = new ArrayList(); + values.put(key, curValues); + } else { + curValues = values.get(realKey); + } + + String nonNullValue = value != null? value : ""; + curValues.add(nonNullValue); + } + return this; + } + /** * Adds the specified values and returns this object. * diff --git a/api/src/main/java/org/asynchttpclient/FluentStringsMap.java b/api/src/main/java/org/asynchttpclient/FluentStringsMap.java index a8fa9e1a77..34cc5ff237 100644 --- a/api/src/main/java/org/asynchttpclient/FluentStringsMap.java +++ b/api/src/main/java/org/asynchttpclient/FluentStringsMap.java @@ -54,6 +54,19 @@ public FluentStringsMap(Map> src) { } } + public FluentStringsMap add(String key, String value) { + if (key != null) { + List curValues = values.get(key); + + if (curValues == null) { + curValues = new ArrayList(1); + values.put(key, curValues); + } + curValues.add(value); + } + return this; + } + /** * Adds the specified values and returns this object. * From 2694692806e26ea5e73b5e010f0c28dfa759b9ff Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 1 Jul 2014 12:54:00 +0200 Subject: [PATCH 0057/2020] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 539576791c..fab0cd307c 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.8.11 + 1.8.12 ``` From a2bcccf88dbd22be5a52913c4016f7107ed1b49a Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 1 Jul 2014 17:20:11 +0200 Subject: [PATCH 0058/2020] minor clean up --- .../main/java/org/asynchttpclient/ProxyServer.java | 2 +- .../util/AsyncHttpProviderUtils.java | 14 ++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/ProxyServer.java b/api/src/main/java/org/asynchttpclient/ProxyServer.java index f5c2f6e458..171c6f6238 100644 --- a/api/src/main/java/org/asynchttpclient/ProxyServer.java +++ b/api/src/main/java/org/asynchttpclient/ProxyServer.java @@ -66,7 +66,7 @@ public ProxyServer(final Protocol protocol, final String host, final int port, S this.port = port; this.principal = principal; this.password = password; - this.uri = AsyncHttpProviderUtils.createUri(toString()); + this.uri = AsyncHttpProviderUtils.createNonEmptyPathURI(toString()); } public ProxyServer(final String host, final int port, String principal, String password) { diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java index 386319b2a9..6a7bb09cc4 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java @@ -48,7 +48,7 @@ public static final void validateSupportedScheme(URI uri) { } } - public final static URI createUri(String u) { + public final static URI createNonEmptyPathURI(String u) { URI uri = URI.create(u); validateSupportedScheme(uri); @@ -64,10 +64,6 @@ public final static URI createUri(String u) { return uri; } - public static String getBaseUrl(String url) { - return getBaseUrl(createUri(url)); - } - public final static String getBaseUrl(URI uri) { String url = uri.getScheme() + "://" + uri.getAuthority(); int port = uri.getPort(); @@ -202,8 +198,7 @@ public static String parseCharset(String contentType) { // Quite a lot of sites have charset="CHARSET", // e.g. charset="utf-8". Note the quotes. This is // not correct, but client should be able to handle - // it (all browsers do, Apache HTTP Client and Grizzly - // strip it by default) + // it (all browsers do, Grizzly strips it by default) // This is a poor man's trim("\"").trim("'") return charset.replaceAll("\"", "").replaceAll("'", ""); } @@ -212,11 +207,6 @@ public static String parseCharset(String contentType) { return null; } - public static int secondsFromNow(long timeMillis) { - long maxAgeMillis = timeMillis - System.currentTimeMillis(); - return (int) (maxAgeMillis / 1000) + (maxAgeMillis % 1000 != 0 ? 1 : 0); - } - public static String keepAliveHeaderValue(AsyncHttpClientConfig config) { return config.getAllowPoolingConnection() ? "keep-alive" : "close"; } From a2d323e106544853387714be6e6ee165d81162db Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 1 Jul 2014 17:23:15 +0200 Subject: [PATCH 0059/2020] Upgrade Netty 4.0.21 --- 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 bc9e908b63..d6abb3a8ef 100644 --- a/providers/netty/pom.xml +++ b/providers/netty/pom.xml @@ -39,7 +39,7 @@ io.netty netty-all - 4.0.20.Final + 4.0.21.Final org.javassist From a89885558fa03bfc9d7e2a39ddbac149edc42a0b Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 1 Jul 2014 18:49:23 +0200 Subject: [PATCH 0060/2020] Port missing #540 on master --- .../asynchttpclient/RequestBuilderBase.java | 33 +++++++++++++++++-- .../async/RequestBuilderTest.java | 8 +++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java index 01f0eaea05..0d7dc1df29 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -634,8 +634,7 @@ public T setConnectionPoolKeyStrategy(ConnectionPoolKeyStrategy connectionPoolKe return derived.cast(this); } - public Request build() { - + private void executeSignatureCalculator() { /* Let's first calculate and inject signature, before finalizing actual build * (order does not matter with current implementation but may in future) */ @@ -647,8 +646,28 @@ public Request build() { url = url.substring(0, i); } signatureCalculator.calculateAndAddSignature(url, request, this); + } + } + + private void computeRequestCharset() { + if (request.charset != null) { + try { + final String contentType = request.headers.getFirstValue("Content-Type"); + if (contentType != null) { + final String charset = AsyncHttpProviderUtils.parseCharset(contentType); + if (charset != null) { + // ensure that if charset is provided with the Content-Type header, + // we propagate that down to the charset of the Request object + request.charset = charset; + } + } + } catch (Throwable e) { + // NoOp -- we can't fix the Content-Type or charset from here + } } - + } + + private void computeRequestContentLength() { if (request.length < 0 && request.streamData == null) { // can't concatenate content-length String contentLength = null; @@ -664,6 +683,14 @@ public Request build() { } } } + } + + public Request build() { + + executeSignatureCalculator(); + computeRequestCharset(); + computeRequestContentLength(); + if (request.cookies != null) { request.cookies = Collections.unmodifiableCollection(request.cookies); } diff --git a/api/src/test/java/org/asynchttpclient/async/RequestBuilderTest.java b/api/src/test/java/org/asynchttpclient/async/RequestBuilderTest.java index d4d3d8cd0b..a6e9214ab4 100644 --- a/api/src/test/java/org/asynchttpclient/async/RequestBuilderTest.java +++ b/api/src/test/java/org/asynchttpclient/async/RequestBuilderTest.java @@ -112,4 +112,12 @@ public void testPercentageEncodedUserInfo() { assertEquals(req.getMethod(), "GET"); assertEquals(req.getUrl(), "http://hello:wor%20ld@foo.com/"); } + + @Test(groups = {"standalone", "default_provider"}) + public void testContentTypeCharsetToBodyEncoding() { + final Request req = new RequestBuilder("GET").setHeader("Content-Type", "application/json; charset=utf-8").build(); + assertEquals(req.getBodyEncoding(), "utf-8"); + final Request req2 = new RequestBuilder("GET").setHeader("Content-Type", "application/json; charset=\"utf-8\"").build(); + assertEquals(req2.getBodyEncoding(), "utf-8"); + } } From 1d5392e9c11f54a8b06cd0f62a063fe0649c95d8 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 1 Jul 2014 19:32:18 +0200 Subject: [PATCH 0061/2020] Remove useless calls to Request.getUrl, close #582 --- .../asynchttpclient/resumable/ResumableAsyncHandler.java | 5 +++-- .../asynchttpclient/providers/netty/channel/Channels.java | 4 ++-- .../providers/netty/handler/HttpProtocol.java | 7 +++---- .../asynchttpclient/providers/netty/handler/Protocol.java | 2 +- .../providers/netty/request/NettyRequestSender.java | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java b/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java index 2463186d4c..53101d590a 100644 --- a/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java +++ b/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java @@ -197,8 +197,9 @@ public AsyncHandler.STATE onHeadersReceived(HttpResponseHeaders headers) throws */ public Request adjustRequestRange(Request request) { - if (resumableIndex.get(request.getUrl()) != null) { - byteTransferred.set(resumableIndex.get(request.getUrl())); + Long ri = resumableIndex.get(request.getUrl()); + if (ri != null) { + byteTransferred.set(ri); } // The Resumbale 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 9e546f30da..0045be2233 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 @@ -296,8 +296,8 @@ protected void initChannel(Channel ch) throws Exception { }); } - public Bootstrap getBootstrap(String url, boolean useSSl, boolean useProxy) { - return (url.startsWith(WEBSOCKET) && !useProxy) ? (useSSl ? secureWebSocketBootstrap : webSocketBootstrap) + public Bootstrap getBootstrap(URI uri, boolean useSSl, boolean useProxy) { + return (uri.getScheme().startsWith(WEBSOCKET) && !useProxy) ? (useSSl ? secureWebSocketBootstrap : webSocketBootstrap) : (useSSl ? secureBootstrap : plainBootstrap); } 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 d0de464588..436d16ea3f 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 @@ -257,7 +257,7 @@ private boolean handleUnauthorizedAndExit(int statusCode, Realm realm, final Req Realm nr = new Realm.RealmBuilder().clone(newRealm).setUri(realmURI).build(); final Request nextRequest = new RequestBuilder(future.getRequest()).setHeaders(request.getHeaders()).setRealm(nr).build(); - LOGGER.debug("Sending authentication to {}", request.getUrl()); + LOGGER.debug("Sending authentication to {}", request.getURI()); Callback callback = new Callback(future) { public void call() throws Exception { channels.drainChannel(channel, future); @@ -302,7 +302,7 @@ private boolean handleProxyAuthenticationRequiredAndExit(int statusCode,// if (statusCode == PROXY_AUTHENTICATION_REQUIRED.code() && realm != null) { List proxyAuthenticateHeaders = response.headers().getAll(HttpHeaders.Names.PROXY_AUTHENTICATE); if (!proxyAuthenticateHeaders.isEmpty() && !future.getAndSetAuth(true)) { - LOGGER.debug("Sending proxy authentication to {}", request.getUrl()); + LOGGER.debug("Sending proxy authentication to {}", request.getURI()); future.setState(NettyResponseFuture.STATE.NEW); Realm newRealm = null; @@ -346,10 +346,9 @@ private boolean handleConnectOKAndExit(int statusCode, Realm realm, final Reques } try { - LOGGER.debug("Connecting to proxy {} for scheme {}", proxyServer, request.getUrl()); - URI requestURI = request.getURI(); String scheme = requestURI.getScheme(); + LOGGER.debug("Connecting to proxy {} for scheme {}", proxyServer, scheme); String host = AsyncHttpProviderUtils.getHost(requestURI); int port = AsyncHttpProviderUtils.getPort(requestURI); 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 2ac8553440..7f9032cb9a 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 @@ -117,7 +117,7 @@ protected boolean handleRedirectAndExit(Request request, NettyResponseFuture future.setURI(uri); String newUrl = uri.toString(); - if (request.getUrl().startsWith(WEBSOCKET)) { + if (request.getURI().getScheme().startsWith(WEBSOCKET)) { newUrl = newUrl.replaceFirst(HTTP, WEBSOCKET); } 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 57e1210f65..48d6cfb3de 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 @@ -219,7 +219,7 @@ private ListenableFuture sendRequestWithNewChannel(// // 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, useProxy); + Bootstrap bootstrap = channels.getBootstrap(request.getURI(), useSSl, useProxy); NettyConnectListener connectListener = new NettyConnectListener(config, this, future); @@ -349,7 +349,7 @@ public ListenableFuture sendRequest(final Request request,// } // FIXME really useful? Why not do this check when building the request? - if (request.getUrl().startsWith(WEBSOCKET) && !validateWebSocketRequest(request, asyncHandler)) { + if (request.getURI().getScheme().startsWith(WEBSOCKET) && !validateWebSocketRequest(request, asyncHandler)) { throw new IOException("WebSocket method must be a GET"); } From 186a9d090de21e19ff4e4f7d1eae1aeaa207c0ea Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 1 Jul 2014 20:20:53 +0200 Subject: [PATCH 0062/2020] Fix build --- api/src/main/java/org/asynchttpclient/RequestBuilderBase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java index 0d7dc1df29..fa213378e4 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -650,7 +650,7 @@ private void executeSignatureCalculator() { } private void computeRequestCharset() { - if (request.charset != null) { + if (request.charset == null) { try { final String contentType = request.headers.getFirstValue("Content-Type"); if (contentType != null) { From 62336576cd7954812feedba834a63488a7a0ecd3 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 1 Jul 2014 21:29:08 +0200 Subject: [PATCH 0063/2020] Lazy init Request cookies list, add set(Collection) method, close #584 --- .../asynchttpclient/RequestBuilderBase.java | 97 ++++++++----------- 1 file changed, 40 insertions(+), 57 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java index fa213378e4..4a881f0fa5 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -57,7 +57,7 @@ private static final class RequestImpl implements Request { private InetAddress address; private InetAddress localAddress; private FluentCaseInsensitiveStringsMap headers; - private Collection cookies; + private ArrayList cookies; private byte[] byteData; private String stringData; private InputStream streamData; @@ -227,10 +227,7 @@ public boolean hasHeaders() { @Override public Collection getCookies() { - if (cookies == null) { - cookies = Collections.unmodifiableCollection(Collections. emptyList()); - } - return cookies; + return cookies != null ? Collections.unmodifiableCollection(cookies) : Collections. emptyList(); } @Override @@ -460,16 +457,14 @@ public T addHeader(String name, String value) { } public T setHeaders(FluentCaseInsensitiveStringsMap headers) { - if (headers != null) { + if (headers != null) request.headers = new FluentCaseInsensitiveStringsMap(headers); - } return derived.cast(this); } public T setHeaders(Map> headers) { - if (headers != null) { + if (headers != null) request.headers = new FluentCaseInsensitiveStringsMap(headers); - } return derived.cast(this); } @@ -478,20 +473,45 @@ public T setContentLength(int length) { return derived.cast(this); } + private void lazyInitCookies() { + if (request.cookies == null) + request.cookies = new ArrayList(3); + } + public T addCookie(Cookie cookie) { - if (request.cookies == null) { - request.cookies = new ArrayList(); - } + lazyInitCookies(); request.cookies.add(cookie); return derived.cast(this); } - public void resetQueryParameters() { - request.queryParams = null; + public void resetCookies() { + if (request.cookies != null) + request.cookies.clear(); } - public void resetCookies() { - request.cookies.clear(); + public T addOrReplaceCookie(Cookie cookie) { + String cookieKey = cookie.getName(); + boolean replace = false; + int index = 0; + lazyInitCookies(); + for (Cookie c : request.cookies) { + if (c.getName().equals(cookieKey)) { + replace = true; + break; + } + + index++; + } + if (replace) + request.cookies.set(index, cookie); + else + request.cookies.add(cookie); + return derived.cast(this); + } + + + public void resetQueryParameters() { + request.queryParams = null; } public void resetParameters() { @@ -544,28 +564,22 @@ public T setBody(BodyGenerator bodyGenerator) { } public T addQueryParameter(String name, String value) { - if (request.queryParams == null) { + if (request.queryParams == null) request.queryParams = new FluentStringsMap(); - } request.queryParams.add(name, value); return derived.cast(this); } public T setQueryParameters(FluentStringsMap parameters) { - if (parameters == null) { - request.queryParams = null; - } else { - request.queryParams = new FluentStringsMap(parameters); - } + request.queryParams = parameters != null? new FluentStringsMap(parameters) : null; return derived.cast(this); } public T addParameter(String key, String value) { resetNonMultipartData(); resetMultipartData(); - if (request.params == null) { + if (request.params == null) request.params = new FluentStringsMap(); - } request.params.add(key, value); return derived.cast(this); } @@ -587,9 +601,8 @@ public T setParameters(Map> parameters) { public T addBodyPart(Part part) { resetParameters(); resetNonMultipartData(); - if (request.parts == null) { + if (request.parts == null) request.parts = new ArrayList(); - } request.parts.add(part); return derived.cast(this); } @@ -686,39 +699,9 @@ private void computeRequestContentLength() { } public Request build() { - executeSignatureCalculator(); computeRequestCharset(); computeRequestContentLength(); - - if (request.cookies != null) { - request.cookies = Collections.unmodifiableCollection(request.cookies); - } return request; } - - public T addOrReplaceCookie(Cookie cookie) { - String cookieKey = cookie.getName(); - boolean replace = false; - int index = 0; - if (request.cookies == null) { - request.cookies = new ArrayList(); - request.cookies.add(cookie); - return derived.cast(this); - } - for (Cookie c : request.cookies) { - if (c.getName().equals(cookieKey)) { - replace = true; - break; - } - - index++; - } - if (replace) { - ((ArrayList) request.cookies).set(index, cookie); - } else { - request.cookies.add(cookie); - } - return derived.cast(this); - } } From b11c7e3934b86dccd7222878b409d6df3bbd501a Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Jul 2014 11:07:26 +0200 Subject: [PATCH 0064/2020] Revert "Pick up default config values from asynchttpclient.properties file" --- .../AsyncHttpClientConfigDefaults.java | 57 ++++---- .../asynchttpclient/util/AsyncImplHelper.java | 2 +- .../org/asynchttpclient/util/MiscUtil.java | 68 +-------- .../AsyncHttpClientConfigBuilderTest.java | 30 ---- .../asynchttpclient/AsyncImplHelperMock.java | 23 --- .../asynchttpclient/util/MiscUtilTest.java | 132 ------------------ pom.xml | 17 +-- 7 files changed, 35 insertions(+), 294 deletions(-) delete mode 100644 api/src/test/java/org/asynchttpclient/AsyncHttpClientConfigBuilderTest.java delete mode 100644 api/src/test/java/org/asynchttpclient/AsyncImplHelperMock.java delete mode 100644 api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java index edbc0c37eb..3cb9ebddc7 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java @@ -12,66 +12,61 @@ */ package org.asynchttpclient; +import static org.asynchttpclient.util.MiscUtil.getBoolean; - -import static org.asynchttpclient.util.MiscUtil.getBooleanValue; -import static org.asynchttpclient.util.MiscUtil.getIntValue; import org.asynchttpclient.util.DefaultHostnameVerifier; import javax.net.ssl.HostnameVerifier; - - public final class AsyncHttpClientConfigDefaults { - private AsyncHttpClientConfigDefaults() { } public static final String ASYNC_CLIENT = AsyncHttpClientConfig.class.getName() + "."; public static int defaultMaxTotalConnections() { - return getIntValue(ASYNC_CLIENT + "maxTotalConnections", -1); + return Integer.getInteger(ASYNC_CLIENT + "maxTotalConnections", -1); } public static int defaultMaxConnectionPerHost() { - return getIntValue(ASYNC_CLIENT + "maxConnectionsPerHost", -1); + return Integer.getInteger(ASYNC_CLIENT + "maxConnectionsPerHost", -1); } public static int defaultConnectionTimeOutInMs() { - return getIntValue(ASYNC_CLIENT + "connectionTimeoutInMs", 60 * 1000); + return Integer.getInteger(ASYNC_CLIENT + "connectionTimeoutInMs", 60 * 1000); } public static int defaultIdleConnectionInPoolTimeoutInMs() { - return getIntValue(ASYNC_CLIENT + "idleConnectionInPoolTimeoutInMs", 60 * 1000); + return Integer.getInteger(ASYNC_CLIENT + "idleConnectionInPoolTimeoutInMs", 60 * 1000); } public static int defaultIdleConnectionTimeoutInMs() { - return getIntValue(ASYNC_CLIENT + "idleConnectionTimeoutInMs", 60 * 1000); + return Integer.getInteger(ASYNC_CLIENT + "idleConnectionTimeoutInMs", 60 * 1000); } public static int defaultRequestTimeoutInMs() { - return getIntValue(ASYNC_CLIENT + "requestTimeoutInMs", 60 * 1000); + return Integer.getInteger(ASYNC_CLIENT + "requestTimeoutInMs", 60 * 1000); } public static int defaultWebSocketIdleTimeoutInMs() { - return getIntValue(ASYNC_CLIENT + "webSocketTimoutInMS", 15 * 60 * 1000); + return Integer.getInteger(ASYNC_CLIENT + "webSocketTimoutInMS", 15 * 60 * 1000); } public static int defaultMaxConnectionLifeTimeInMs() { - return getIntValue(ASYNC_CLIENT + "maxConnectionLifeTimeInMs", -1); + return Integer.getInteger(ASYNC_CLIENT + "maxConnectionLifeTimeInMs", -1); } public static boolean defaultRedirectEnabled() { - return getBooleanValue(ASYNC_CLIENT + "redirectsEnabled",false); + return Boolean.getBoolean(ASYNC_CLIENT + "redirectsEnabled"); } public static int defaultMaxRedirects() { - return getIntValue(ASYNC_CLIENT + "maxRedirects", 5); + return Integer.getInteger(ASYNC_CLIENT + "maxRedirects", 5); } public static boolean defaultCompressionEnabled() { - return getBooleanValue(ASYNC_CLIENT + "compressionEnabled",false); + return Boolean.getBoolean(ASYNC_CLIENT + "compressionEnabled"); } public static String defaultUserAgent() { @@ -79,48 +74,48 @@ public static String defaultUserAgent() { } public static int defaultIoThreadMultiplier() { - return getIntValue(ASYNC_CLIENT + "ioThreadMultiplier", 2); + return Integer.getInteger(ASYNC_CLIENT + "ioThreadMultiplier", 2); } public static boolean defaultUseProxySelector() { - return getBooleanValue(ASYNC_CLIENT + "useProxySelector",false); + return Boolean.getBoolean(ASYNC_CLIENT + "useProxySelector"); } public static boolean defaultUseProxyProperties() { - return getBooleanValue(ASYNC_CLIENT + "useProxyProperties",false); + return Boolean.getBoolean(ASYNC_CLIENT + "useProxyProperties"); } public static boolean defaultStrict302Handling() { - return getBooleanValue(ASYNC_CLIENT + "strict302Handling",false); + return Boolean.getBoolean(ASYNC_CLIENT + "strict302Handling"); } public static boolean defaultAllowPoolingConnection() { - return getBooleanValue(ASYNC_CLIENT + "allowPoolingConnection", true); + return getBoolean(ASYNC_CLIENT + "allowPoolingConnection", true); } public static boolean defaultUseRelativeURIsWithSSLProxies() { - return getBooleanValue(ASYNC_CLIENT + "useRelativeURIsWithSSLProxies", true); + return getBoolean(ASYNC_CLIENT + "useRelativeURIsWithSSLProxies", true); } // unused/broken, left there for compatibility, fixed in Netty 4 public static int defaultRequestCompressionLevel() { - return getIntValue(ASYNC_CLIENT + "requestCompressionLevel", -1); + return Integer.getInteger(ASYNC_CLIENT + "requestCompressionLevel", -1); } public static int defaultMaxRequestRetry() { - return getIntValue(ASYNC_CLIENT + "maxRequestRetry", 5); + return Integer.getInteger(ASYNC_CLIENT + "maxRequestRetry", 5); } public static boolean defaultAllowSslConnectionPool() { - return getBooleanValue(ASYNC_CLIENT + "allowSslConnectionPool", true); + return getBoolean(ASYNC_CLIENT + "allowSslConnectionPool", true); } public static boolean defaultUseRawUrl() { - return getBooleanValue(ASYNC_CLIENT + "useRawUrl",false); + return Boolean.getBoolean(ASYNC_CLIENT + "useRawUrl"); } public static boolean defaultRemoveQueryParamOnRedirect() { - return getBooleanValue(ASYNC_CLIENT + "removeQueryParamOnRedirect", true); + return getBoolean(ASYNC_CLIENT + "removeQueryParamOnRedirect", true); } public static HostnameVerifier defaultHostnameVerifier() { @@ -128,15 +123,15 @@ public static HostnameVerifier defaultHostnameVerifier() { } public static boolean defaultSpdyEnabled() { - return getBooleanValue(ASYNC_CLIENT + "spdyEnabled",false); + return Boolean.getBoolean(ASYNC_CLIENT + "spdyEnabled"); } public static int defaultSpdyInitialWindowSize() { - return getIntValue(ASYNC_CLIENT + "spdyInitialWindowSize", 10 * 1024 * 1024); + return Integer.getInteger(ASYNC_CLIENT + "spdyInitialWindowSize", 10 * 1024 * 1024); } public static int defaultSpdyMaxConcurrentStreams() { - return getIntValue(ASYNC_CLIENT + "spdyMaxConcurrentStreams", 100); + return Integer.getInteger(ASYNC_CLIENT + "spdyMaxConcurrentStreams", 100); } public static boolean defaultAcceptAnyCertificate() { diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncImplHelper.java b/api/src/main/java/org/asynchttpclient/util/AsyncImplHelper.java index 47b23bf767..a61d2d74d6 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncImplHelper.java +++ b/api/src/main/java/org/asynchttpclient/util/AsyncImplHelper.java @@ -59,7 +59,7 @@ public static Class getAsyncImplClass(String propertyName) { return asyncHttpClientImplClass; } - public static Properties getAsyncImplProperties() { + private static Properties getAsyncImplProperties() { try { return AccessController.doPrivileged(new PrivilegedExceptionAction() { public Properties run() throws IOException { diff --git a/api/src/main/java/org/asynchttpclient/util/MiscUtil.java b/api/src/main/java/org/asynchttpclient/util/MiscUtil.java index 054bb41fb9..3cad6cfd37 100644 --- a/api/src/main/java/org/asynchttpclient/util/MiscUtil.java +++ b/api/src/main/java/org/asynchttpclient/util/MiscUtil.java @@ -12,16 +12,11 @@ */ package org.asynchttpclient.util; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.Map; -import java.util.Properties; public class MiscUtil { - public final static Logger logger = LoggerFactory.getLogger(MiscUtil.class); - private MiscUtil() { } @@ -45,69 +40,12 @@ public static boolean isNonEmpty(Map map) { return map != null && !map.isEmpty(); } - // The getBooleanValue() method replaces this and reads the property from - // properties file - // too. Plus has a better check for invalid boolean values. - /* - * public static boolean getBoolean(String systemPropName, boolean - * defaultValue) { String systemPropValue = - * System.getProperty(systemPropName); return systemPropValue != null ? - * systemPropValue.equalsIgnoreCase("true") : defaultValue; } - */ - - public static Integer getIntValue(String property, int defaultValue) { - // Read system property and if not null return that. - Integer value = Integer.getInteger(property); - if (value != null) - return value; - Properties asyncHttpClientConfigProperties = AsyncImplHelper.getAsyncImplProperties(); - if (asyncHttpClientConfigProperties != null) { - String valueString = asyncHttpClientConfigProperties.getProperty(property); - try { - // If property is present and is non null parse it. - if (valueString != null) - return Integer.parseInt(valueString); - } catch (NumberFormatException e) { - // If property couldn't be parsed log the error message and - // return default value. - logger.error("Property : " + property + " has value = " + valueString - + " which couldn't be parsed to an int value. Returning default value: " + defaultValue, e); - } - } - return defaultValue; - } - - private static boolean isValidBooleanValue(String value) { - return value != null && ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)); + public static boolean getBoolean(String systemPropName, boolean defaultValue) { + String systemPropValue = System.getProperty(systemPropName); + return systemPropValue != null ? systemPropValue.equalsIgnoreCase("true") : defaultValue; } - - public static Boolean getBooleanValue(String property, boolean defaultValue) { - - // get from System property first - String value = System.getProperty(property); - if (isValidBooleanValue(value)) - return Boolean.parseBoolean(value); - - // get from property file - Properties asyncHttpClientConfigProperties = AsyncImplHelper.getAsyncImplProperties(); - if (asyncHttpClientConfigProperties != null) { - value = asyncHttpClientConfigProperties.getProperty(property); - if (isValidBooleanValue(value)) - return Boolean.parseBoolean(value); - } - - // have to use the default value now - if (value != null) - logger.error("Property : " + property + " has value = " + value - + " which couldn't be parsed to an boolean value. Returning default value: " + defaultValue); - - return defaultValue; - } - - public static T withDefault(T value, T defaults) { return value != null? value : value; } - } diff --git a/api/src/test/java/org/asynchttpclient/AsyncHttpClientConfigBuilderTest.java b/api/src/test/java/org/asynchttpclient/AsyncHttpClientConfigBuilderTest.java deleted file mode 100644 index 44818fb541..0000000000 --- a/api/src/test/java/org/asynchttpclient/AsyncHttpClientConfigBuilderTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.asynchttpclient; - -import org.testng.Assert; -import org.testng.annotations.Test; - -public class AsyncHttpClientConfigBuilderTest { - - @Test - public void testDefaultConfigValues() { - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().build(); - Assert.assertEquals(config.getConnectionTimeoutInMs(), 60000); - Assert.assertEquals(config.getRequestTimeoutInMs(), 60000); - Assert.assertEquals(config.getIdleConnectionTimeoutInMs(), 60000); - Assert.assertFalse(config.isCompressionEnabled()); - Assert.assertFalse(config.isRedirectEnabled()); - System.setProperty(AsyncHttpClientConfig.ASYNC_CLIENT + "connectionTimeoutInMs", "1000"); - System.setProperty(AsyncHttpClientConfig.ASYNC_CLIENT + "requestTimeoutInMs", "500"); - System.setProperty(AsyncHttpClientConfig.ASYNC_CLIENT + "compressionEnabled", "true"); - System.setProperty(AsyncHttpClientConfig.ASYNC_CLIENT + "redirectsEnabled", "true"); - config = new AsyncHttpClientConfig.Builder().build(); - Assert.assertEquals(config.getConnectionTimeoutInMs(), 1000); - Assert.assertEquals(config.getRequestTimeoutInMs(), 500); - Assert.assertTrue(config.isCompressionEnabled()); - Assert.assertTrue(config.isRedirectEnabled()); - System.clearProperty(AsyncHttpClientConfig.ASYNC_CLIENT + "connectionTimeoutInMs"); - System.clearProperty(AsyncHttpClientConfig.ASYNC_CLIENT + "requestTimeoutInMs"); - System.clearProperty(AsyncHttpClientConfig.ASYNC_CLIENT + "compressionEnabled"); - System.clearProperty(AsyncHttpClientConfig.ASYNC_CLIENT + "defaultRedirectsEnabled"); - } -} diff --git a/api/src/test/java/org/asynchttpclient/AsyncImplHelperMock.java b/api/src/test/java/org/asynchttpclient/AsyncImplHelperMock.java deleted file mode 100644 index e9f18af883..0000000000 --- a/api/src/test/java/org/asynchttpclient/AsyncImplHelperMock.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.asynchttpclient; - -import java.util.Properties; - -import mockit.Mock; -import mockit.MockUp; - -import org.asynchttpclient.util.AsyncImplHelper; - -public class AsyncImplHelperMock extends MockUp { - - private static Properties properties; - - public AsyncImplHelperMock(Properties properties) { - this.properties = properties; - } - - @Mock - public Properties getAsyncImplProperties() { - return properties; - } - -} diff --git a/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java b/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java deleted file mode 100644 index 794090f43e..0000000000 --- a/api/src/test/java/org/asynchttpclient/util/MiscUtilTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package org.asynchttpclient.util; - -import java.util.Properties; - -import org.asynchttpclient.AsyncImplHelperMock; -import org.testng.Assert; -import org.testng.annotations.Test; - -public class MiscUtilTest { - private final Integer MY_SPECIAL_INT_VALUE = 10; - private final Integer MY_SPECIAL_SYSTEM_INT_VALUE = 100; - private final String MY_SPECIAL_INT_PROPERTY = "my.special.int.property"; - private final String MY_SPECIAL_BOOLEAN_PROPERTY = "my.special.boolean.property"; - private final Integer MY_SPECIAL_INT_DEFAULT_VALUE = -100; - - @Test - public void testGetIntegerValue() { - // Setup a AsyncImplHelperMock that returns a mock - // asynchttpclient.properties with a value - // set for 'my.special.int.property' property - Properties properties = new Properties(); - properties.setProperty(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_VALUE.toString()); - AsyncImplHelperMock asyncImplHelperMock = new AsyncImplHelperMock(properties); - - // Assert that the getIntValue() method returns 10 - Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, -1), MY_SPECIAL_INT_VALUE); - // Set a system property that overrides the value in the - // asynchttpclient.properties - System.setProperty(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_SYSTEM_INT_VALUE.toString()); - // Assert 100 is returned, i.e. system property takes precedence over - // property in asynchttpclient.properties - Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, -1), MY_SPECIAL_SYSTEM_INT_VALUE); - // Clear the system property - System.clearProperty(MY_SPECIAL_INT_PROPERTY); - // Assert that the value set in asynchttpclient.properties is returned - Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, -1), MY_SPECIAL_INT_VALUE); - // Set a corrupt system property - System.setProperty(MY_SPECIAL_INT_PROPERTY, "corrupt property"); - // Assert that the value set in asynchttpclient.properties is returned - // even though corrupt system property is set. - Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, -1), MY_SPECIAL_INT_VALUE); - System.clearProperty(MY_SPECIAL_INT_PROPERTY); - } - - @Test - public void testGetBooleanValue() { - // Setup a AsyncImplHelperMock that returns a mock - // asynchttpclient.properties with a value - // set for 'my.special.int.property' property - Properties properties = new Properties(); - properties.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "true"); - AsyncImplHelperMock asyncImplHelperMock = new AsyncImplHelperMock(properties); - - // Assert that the getBooleanValue() method returns TRUE - Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); - // Set a system property that overrides the value in the - // asynchttpclient.properties - System.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "false"); - // Assert false is returned, i.e. system property takes precedence over - // property in asynchttpclient.properties - Assert.assertFalse(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, true)); - // Clear the system property - System.clearProperty(MY_SPECIAL_BOOLEAN_PROPERTY); - // Assert that the value set in asynchttpclient.properties is returned - Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); - // Set a corrupt system property - System.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "corrupt property"); - // Assert that the value set in asynchttpclient.properties is returned - // even though corrupt system property is set. - Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); - System.clearProperty(MY_SPECIAL_BOOLEAN_PROPERTY); - } - - @Test - public void testGetDefaultIntegerValue() { - - // Assert that the getIntValue() method returns the default value if - // Properties is not present - Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), MY_SPECIAL_INT_DEFAULT_VALUE); - // Setup up a mock of a asynchttpclient.properties that initially is - // empty - Properties properties = new Properties(); - AsyncImplHelperMock asyncImplHelperMock = new AsyncImplHelperMock(properties); - - // Assert that the getIntValue() method returns the default value if - // there is no - // property set in the asynchttpclient.properties - Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), MY_SPECIAL_INT_DEFAULT_VALUE); - // Now set a corrupt value in the asynchttpclient.properties - properties.setProperty(MY_SPECIAL_INT_PROPERTY, "corrupt property"); - // Assert that the getIntValue() method returns the default value if - // there is a corrupt - // property set in the asynchttpclient.properties - Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), MY_SPECIAL_INT_DEFAULT_VALUE); - // Set a corrupt system property - System.setProperty(MY_SPECIAL_INT_PROPERTY, "corrupt property"); - // Assert that even though values set in asynchttpclient.properties and - // system property is corrupt the default value is - // returned - Assert.assertEquals(MiscUtil.getIntValue(MY_SPECIAL_INT_PROPERTY, MY_SPECIAL_INT_DEFAULT_VALUE), MY_SPECIAL_INT_DEFAULT_VALUE); - System.clearProperty(MY_SPECIAL_INT_PROPERTY); - } - - @Test - public void testGetDefaultBooleanValue() { - // Assert that the getBooleanValue() method returns the default value if - // asynchttpclient.properties is not present - Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, true)); - // Setup up a mock of a asynchttpclient.properties that initially is - // empty - Properties properties = new Properties(); - AsyncImplHelperMock asyncImplHelperMock = new AsyncImplHelperMock(properties); - - // Assert that the getBooleanValue() method returns the default value if - // there is no - // property set in the asynchttpclient.properties - Assert.assertTrue(!MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); - // Now set a corrupt value in the asynchttpclient.properties - properties.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "corrupt property"); - // Assert that the getBooleanValue() method returns the default value if - // there is a corrupt - // property set in the asynchttpclient.properties - Assert.assertTrue(MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, true)); - // Set a corrupt system property - System.setProperty(MY_SPECIAL_BOOLEAN_PROPERTY, "corrupt property"); - // Assert that even though values set in asynchttpclient.properties and - // system property is corrupt the default value is - // returned - Assert.assertTrue(!MiscUtil.getBooleanValue(MY_SPECIAL_BOOLEAN_PROPERTY, false)); - System.clearProperty(MY_SPECIAL_INT_PROPERTY); - } -} diff --git a/pom.xml b/pom.xml index 4df2c85397..a4e54d8ded 100644 --- a/pom.xml +++ b/pom.xml @@ -514,17 +514,11 @@ test - com.e-movimento.tinytools - privilegedaccessor - ${privilegedaccessor.version} - test - - - com.googlecode.jmockit - jmockit - ${jmockit.version} - test - + com.e-movimento.tinytools + privilegedaccessor + ${privilegedaccessor.version} + test + http://oss.sonatype.org/content/repositories/snapshots @@ -539,7 +533,6 @@ 6.0.29 2.4 1.3 - 1.5 1.2.2 From ecb374a98cfa6299e5ae3020c0bba006463bf26d Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Jul 2014 11:13:10 +0200 Subject: [PATCH 0065/2020] PR didn't compile... --- .../java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java index edbc0c37eb..42bc30a4d8 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java @@ -140,6 +140,6 @@ public static int defaultSpdyMaxConcurrentStreams() { } public static boolean defaultAcceptAnyCertificate() { - return getBoolean(ASYNC_CLIENT + "acceptAnyCertificate", false); + return getBooleanValue(ASYNC_CLIENT + "acceptAnyCertificate", false); } } From 713054c1359a0cb346fe966fc2ebd0999680f4f4 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Jul 2014 11:36:39 +0200 Subject: [PATCH 0066/2020] Fix build after revert --- .../java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java index 88593ba310..3cb9ebddc7 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java @@ -135,6 +135,6 @@ public static int defaultSpdyMaxConcurrentStreams() { } public static boolean defaultAcceptAnyCertificate() { - return getBooleanValue(ASYNC_CLIENT + "acceptAnyCertificate", false); + return getBoolean(ASYNC_CLIENT + "acceptAnyCertificate", false); } } From 7faf5eb2682789cae79d73fc8ba4d2f2e3c41cc4 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Jul 2014 11:48:09 +0200 Subject: [PATCH 0067/2020] Move registry stuff to extra module --- extras/pom.xml | 1 + extras/registry/pom.xml | 30 +++++++++++++++++++ .../extra}/AsyncHttpClientFactory.java | 7 +++-- .../extra}/AsyncHttpClientImplException.java | 2 +- .../extra}/AsyncHttpClientRegistry.java | 5 ++-- .../extra}/AsyncHttpClientRegistryImpl.java | 4 +-- .../extra}/AsyncImplHelper.java | 4 +-- .../GrizzlyAsyncHttpClientFactoryTest.java | 5 ++-- .../NettyAsyncHttpClientFactoryTest.java | 5 ++-- .../AbstractAsyncHttpClientFactoryTest.java | 16 ++++++++-- .../extra}/AsyncHttpClientRegistryTest.java | 8 +++-- .../extra}/BadAsyncHttpClient.java | 12 +++++++- .../extra}/BadAsyncHttpClientException.java | 4 ++- .../extra}/BadAsyncHttpClientRegistry.java | 4 ++- .../extra}/TestAsyncHttpClientRegistry.java | 4 ++- 15 files changed, 88 insertions(+), 23 deletions(-) create mode 100644 extras/registry/pom.xml rename {api/src/main/java/org/asynchttpclient => extras/registry/src/main/java/org/asynchttpclient/extra}/AsyncHttpClientFactory.java (96%) rename {api/src/main/java/org/asynchttpclient => extras/registry/src/main/java/org/asynchttpclient/extra}/AsyncHttpClientImplException.java (96%) rename {api/src/main/java/org/asynchttpclient => extras/registry/src/main/java/org/asynchttpclient/extra}/AsyncHttpClientRegistry.java (96%) rename {api/src/main/java/org/asynchttpclient => extras/registry/src/main/java/org/asynchttpclient/extra}/AsyncHttpClientRegistryImpl.java (98%) rename {api/src/main/java/org/asynchttpclient/util => extras/registry/src/main/java/org/asynchttpclient/extra}/AsyncImplHelper.java (97%) rename {providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly => extras/registry/src/main/java/org/asynchttpclient/extra}/GrizzlyAsyncHttpClientFactoryTest.java (87%) rename {providers/netty/src/test/java/org/asynchttpclient/providers/netty => extras/registry/src/main/java/org/asynchttpclient/extra}/NettyAsyncHttpClientFactoryTest.java (87%) rename {api/src/test/java/org/asynchttpclient => extras/registry/src/test/java/org/asynchttpclient/extra}/AbstractAsyncHttpClientFactoryTest.java (95%) rename {api/src/test/java/org/asynchttpclient => extras/registry/src/test/java/org/asynchttpclient/extra}/AsyncHttpClientRegistryTest.java (94%) rename {api/src/test/java/org/asynchttpclient => extras/registry/src/test/java/org/asynchttpclient/extra}/BadAsyncHttpClient.java (88%) rename {api/src/test/java/org/asynchttpclient => extras/registry/src/test/java/org/asynchttpclient/extra}/BadAsyncHttpClientException.java (89%) rename {api/src/test/java/org/asynchttpclient => extras/registry/src/test/java/org/asynchttpclient/extra}/BadAsyncHttpClientRegistry.java (89%) rename {api/src/test/java/org/asynchttpclient => extras/registry/src/test/java/org/asynchttpclient/extra}/TestAsyncHttpClientRegistry.java (88%) diff --git a/extras/pom.xml b/extras/pom.xml index 281689b29b..c220594e75 100644 --- a/extras/pom.xml +++ b/extras/pom.xml @@ -46,6 +46,7 @@ guava jdeferred + registry diff --git a/extras/registry/pom.xml b/extras/registry/pom.xml new file mode 100644 index 0000000000..66119acdb0 --- /dev/null +++ b/extras/registry/pom.xml @@ -0,0 +1,30 @@ + + + org.asynchttpclient + async-http-client-extras-parent + 2.0.0-SNAPSHOT + + 4.0.0 + async-http-client-extras-registry + Asynchronous Http Client Registry Extras + + The Async Http Client Registry Extras. + + + + + + org.asynchttpclient + async-http-client-netty-provider + ${project.version} + test + + + org.asynchttpclient + async-http-client-grizzly-provider + ${project.version} + test + + + \ No newline at end of file diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientFactory.java b/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientFactory.java similarity index 96% rename from api/src/main/java/org/asynchttpclient/AsyncHttpClientFactory.java rename to extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientFactory.java index 8135f60163..780a0af585 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientFactory.java +++ b/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientFactory.java @@ -10,9 +10,12 @@ * "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; +package org.asynchttpclient.extra; -import org.asynchttpclient.util.AsyncImplHelper; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.AsyncHttpProvider; +import org.asynchttpclient.DefaultAsyncHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientImplException.java b/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientImplException.java similarity index 96% rename from api/src/main/java/org/asynchttpclient/AsyncHttpClientImplException.java rename to extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientImplException.java index 08e75e90fd..1862ea8c70 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientImplException.java +++ b/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientImplException.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; +package org.asynchttpclient.extra; @SuppressWarnings("serial") public class AsyncHttpClientImplException extends RuntimeException { diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientRegistry.java b/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientRegistry.java similarity index 96% rename from api/src/main/java/org/asynchttpclient/AsyncHttpClientRegistry.java rename to extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientRegistry.java index 5e05afa780..13ce83081e 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientRegistry.java +++ b/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientRegistry.java @@ -10,7 +10,9 @@ * "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; +package org.asynchttpclient.extra; + +import org.asynchttpclient.AsyncHttpClient; import java.util.Set; @@ -79,4 +81,3 @@ public interface AsyncHttpClientRegistry { void clearAllInstances(); } - diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientRegistryImpl.java b/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientRegistryImpl.java similarity index 98% rename from api/src/main/java/org/asynchttpclient/AsyncHttpClientRegistryImpl.java rename to extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientRegistryImpl.java index 61d8f3d38a..378f19290e 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientRegistryImpl.java +++ b/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientRegistryImpl.java @@ -10,9 +10,9 @@ * "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; +package org.asynchttpclient.extra; -import org.asynchttpclient.util.AsyncImplHelper; +import org.asynchttpclient.AsyncHttpClient; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncImplHelper.java b/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncImplHelper.java similarity index 97% rename from api/src/main/java/org/asynchttpclient/util/AsyncImplHelper.java rename to extras/registry/src/main/java/org/asynchttpclient/extra/AsyncImplHelper.java index a61d2d74d6..5f341d3ae7 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncImplHelper.java +++ b/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncImplHelper.java @@ -10,11 +10,9 @@ * "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.util; +package org.asynchttpclient.extra; import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientFactory; -import org.asynchttpclient.AsyncHttpClientImplException; import java.io.IOException; import java.io.InputStream; diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpClientFactoryTest.java b/extras/registry/src/main/java/org/asynchttpclient/extra/GrizzlyAsyncHttpClientFactoryTest.java similarity index 87% rename from providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpClientFactoryTest.java rename to extras/registry/src/main/java/org/asynchttpclient/extra/GrizzlyAsyncHttpClientFactoryTest.java index e201b05a9d..ed27eb1257 100644 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyAsyncHttpClientFactoryTest.java +++ b/extras/registry/src/main/java/org/asynchttpclient/extra/GrizzlyAsyncHttpClientFactoryTest.java @@ -10,11 +10,12 @@ * "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; +package org.asynchttpclient.extra; -import org.asynchttpclient.AbstractAsyncHttpClientFactoryTest; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.AsyncHttpProvider; +import org.asynchttpclient.extra.AbstractAsyncHttpClientFactoryTest; +import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; import org.testng.annotations.Test; @Test diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncHttpClientFactoryTest.java b/extras/registry/src/main/java/org/asynchttpclient/extra/NettyAsyncHttpClientFactoryTest.java similarity index 87% rename from providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncHttpClientFactoryTest.java rename to extras/registry/src/main/java/org/asynchttpclient/extra/NettyAsyncHttpClientFactoryTest.java index dc2d152b0f..340b3befe2 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyAsyncHttpClientFactoryTest.java +++ b/extras/registry/src/main/java/org/asynchttpclient/extra/NettyAsyncHttpClientFactoryTest.java @@ -10,11 +10,12 @@ * "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; +package org.asynchttpclient.extra; -import org.asynchttpclient.AbstractAsyncHttpClientFactoryTest; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.AsyncHttpProvider; +import org.asynchttpclient.extra.AbstractAsyncHttpClientFactoryTest; +import org.asynchttpclient.providers.netty.NettyAsyncHttpProvider; import org.testng.annotations.Test; @Test diff --git a/api/src/test/java/org/asynchttpclient/AbstractAsyncHttpClientFactoryTest.java b/extras/registry/src/test/java/org/asynchttpclient/extra/AbstractAsyncHttpClientFactoryTest.java similarity index 95% rename from api/src/test/java/org/asynchttpclient/AbstractAsyncHttpClientFactoryTest.java rename to extras/registry/src/test/java/org/asynchttpclient/extra/AbstractAsyncHttpClientFactoryTest.java index 757c1c26d9..7ee6f94d36 100644 --- a/api/src/test/java/org/asynchttpclient/AbstractAsyncHttpClientFactoryTest.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extra/AbstractAsyncHttpClientFactoryTest.java @@ -10,11 +10,20 @@ * "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; - +package org.asynchttpclient.extra; + +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.AsyncHttpProvider; +import org.asynchttpclient.DefaultAsyncHttpClient; +import org.asynchttpclient.Response; +import org.asynchttpclient.TestAsyncHttpClient; +import org.asynchttpclient.AsyncHttpClientConfig.Builder; import org.asynchttpclient.async.util.EchoHandler; import org.asynchttpclient.async.util.TestUtils; -import org.asynchttpclient.util.AsyncImplHelper; +import org.asynchttpclient.extra.AsyncHttpClientFactory; +import org.asynchttpclient.extra.AsyncHttpClientImplException; +import org.asynchttpclient.extra.AsyncImplHelper; import org.eclipse.jetty.server.Server; import org.testng.Assert; import org.testng.annotations.AfterClass; @@ -23,6 +32,7 @@ import org.testng.annotations.Test; import java.lang.reflect.InvocationTargetException; + import junit.extensions.PA; public abstract class AbstractAsyncHttpClientFactoryTest { diff --git a/api/src/test/java/org/asynchttpclient/AsyncHttpClientRegistryTest.java b/extras/registry/src/test/java/org/asynchttpclient/extra/AsyncHttpClientRegistryTest.java similarity index 94% rename from api/src/test/java/org/asynchttpclient/AsyncHttpClientRegistryTest.java rename to extras/registry/src/test/java/org/asynchttpclient/extra/AsyncHttpClientRegistryTest.java index 569c539308..c973b6d59e 100644 --- a/api/src/test/java/org/asynchttpclient/AsyncHttpClientRegistryTest.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extra/AsyncHttpClientRegistryTest.java @@ -10,9 +10,13 @@ * "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; +package org.asynchttpclient.extra; -import org.asynchttpclient.util.AsyncImplHelper; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.extra.AsyncHttpClientFactory; +import org.asynchttpclient.extra.AsyncHttpClientImplException; +import org.asynchttpclient.extra.AsyncHttpClientRegistryImpl; +import org.asynchttpclient.extra.AsyncImplHelper; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; diff --git a/api/src/test/java/org/asynchttpclient/BadAsyncHttpClient.java b/extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClient.java similarity index 88% rename from api/src/test/java/org/asynchttpclient/BadAsyncHttpClient.java rename to extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClient.java index f8e72c9538..d9b0a5c3c1 100644 --- a/api/src/test/java/org/asynchttpclient/BadAsyncHttpClient.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClient.java @@ -10,7 +10,17 @@ * "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; +package org.asynchttpclient.extra; + +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.AsyncHttpProvider; +import org.asynchttpclient.BoundRequestBuilder; +import org.asynchttpclient.ListenableFuture; +import org.asynchttpclient.Request; +import org.asynchttpclient.Response; +import org.asynchttpclient.SignatureCalculator; import java.io.IOException; diff --git a/api/src/test/java/org/asynchttpclient/BadAsyncHttpClientException.java b/extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClientException.java similarity index 89% rename from api/src/test/java/org/asynchttpclient/BadAsyncHttpClientException.java rename to extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClientException.java index af384e8623..318d59ddbf 100644 --- a/api/src/test/java/org/asynchttpclient/BadAsyncHttpClientException.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClientException.java @@ -10,7 +10,9 @@ * "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; +package org.asynchttpclient.extra; + +import org.asynchttpclient.extra.AsyncHttpClientImplException; @SuppressWarnings("serial") public class BadAsyncHttpClientException extends AsyncHttpClientImplException { diff --git a/api/src/test/java/org/asynchttpclient/BadAsyncHttpClientRegistry.java b/extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClientRegistry.java similarity index 89% rename from api/src/test/java/org/asynchttpclient/BadAsyncHttpClientRegistry.java rename to extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClientRegistry.java index 9ed9b5de88..7aef111bd0 100644 --- a/api/src/test/java/org/asynchttpclient/BadAsyncHttpClientRegistry.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClientRegistry.java @@ -10,7 +10,9 @@ * "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; +package org.asynchttpclient.extra; + +import org.asynchttpclient.extra.AsyncHttpClientRegistryImpl; public class BadAsyncHttpClientRegistry extends AsyncHttpClientRegistryImpl { diff --git a/api/src/test/java/org/asynchttpclient/TestAsyncHttpClientRegistry.java b/extras/registry/src/test/java/org/asynchttpclient/extra/TestAsyncHttpClientRegistry.java similarity index 88% rename from api/src/test/java/org/asynchttpclient/TestAsyncHttpClientRegistry.java rename to extras/registry/src/test/java/org/asynchttpclient/extra/TestAsyncHttpClientRegistry.java index 2f4ec9247e..84f052ddcb 100644 --- a/api/src/test/java/org/asynchttpclient/TestAsyncHttpClientRegistry.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extra/TestAsyncHttpClientRegistry.java @@ -10,7 +10,9 @@ * "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; +package org.asynchttpclient.extra; + +import org.asynchttpclient.extra.AsyncHttpClientRegistryImpl; public class TestAsyncHttpClientRegistry extends AsyncHttpClientRegistryImpl { From e966f74cd5b0451e96767dcbad082a8d8d78ec57 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Jul 2014 12:02:16 +0200 Subject: [PATCH 0068/2020] Move to proper per extras packages --- .../guava}/ListenableFutureAdapter.java | 2 +- .../guava}/RateLimitedThrottleRequestFilter.java | 4 +++- .../jdeferred}/AsyncHttpDeferredObject.java | 2 +- .../jdeferred}/ContentWriteProgress.java | 2 +- .../{extra => extras/jdeferred}/HttpProgress.java | 2 +- .../jdeferred}/HttpResponseBodyPartProgress.java | 2 +- .../java/org/asynchttpclient/extra/AsyncHttpTest.java | 2 ++ .../registry}/AsyncHttpClientFactory.java | 2 +- .../registry}/AsyncHttpClientImplException.java | 2 +- .../registry}/AsyncHttpClientRegistry.java | 2 +- .../registry}/AsyncHttpClientRegistryImpl.java | 2 +- .../{extra => extras/registry}/AsyncImplHelper.java | 2 +- .../registry}/AbstractAsyncHttpClientFactoryTest.java | 9 ++++----- .../registry}/AsyncHttpClientRegistryTest.java | 10 +++++----- .../{extra => extras/registry}/BadAsyncHttpClient.java | 2 +- .../registry}/BadAsyncHttpClientException.java | 4 ++-- .../registry}/BadAsyncHttpClientRegistry.java | 4 ++-- .../registry}/GrizzlyAsyncHttpClientFactoryTest.java | 4 ++-- .../registry}/NettyAsyncHttpClientFactoryTest.java | 4 ++-- .../registry}/TestAsyncHttpClientRegistry.java | 4 ++-- 20 files changed, 35 insertions(+), 32 deletions(-) rename extras/guava/src/main/java/org/asynchttpclient/{extra => extras/guava}/ListenableFutureAdapter.java (98%) rename extras/guava/src/main/java/org/asynchttpclient/{extra => extras/guava}/RateLimitedThrottleRequestFilter.java (96%) rename extras/jdeferred/src/main/java/org/asynchttpclient/{extra => extras/jdeferred}/AsyncHttpDeferredObject.java (98%) rename extras/jdeferred/src/main/java/org/asynchttpclient/{extra => extras/jdeferred}/ContentWriteProgress.java (96%) rename extras/jdeferred/src/main/java/org/asynchttpclient/{extra => extras/jdeferred}/HttpProgress.java (93%) rename extras/jdeferred/src/main/java/org/asynchttpclient/{extra => extras/jdeferred}/HttpResponseBodyPartProgress.java (95%) rename extras/registry/src/main/java/org/asynchttpclient/{extra => extras/registry}/AsyncHttpClientFactory.java (99%) rename extras/registry/src/main/java/org/asynchttpclient/{extra => extras/registry}/AsyncHttpClientImplException.java (95%) rename extras/registry/src/main/java/org/asynchttpclient/{extra => extras/registry}/AsyncHttpClientRegistry.java (98%) rename extras/registry/src/main/java/org/asynchttpclient/{extra => extras/registry}/AsyncHttpClientRegistryImpl.java (98%) rename extras/registry/src/main/java/org/asynchttpclient/{extra => extras/registry}/AsyncImplHelper.java (99%) rename extras/registry/src/test/java/org/asynchttpclient/{extra => extras/registry}/AbstractAsyncHttpClientFactoryTest.java (97%) rename extras/registry/src/test/java/org/asynchttpclient/{extra => extras/registry}/AsyncHttpClientRegistryTest.java (94%) rename extras/registry/src/test/java/org/asynchttpclient/{extra => extras/registry}/BadAsyncHttpClient.java (98%) rename extras/registry/src/test/java/org/asynchttpclient/{extra => extras/registry}/BadAsyncHttpClientException.java (88%) rename extras/registry/src/test/java/org/asynchttpclient/{extra => extras/registry}/BadAsyncHttpClientRegistry.java (88%) rename extras/registry/src/{main/java/org/asynchttpclient/extra => test/java/org/asynchttpclient/extras/registry}/GrizzlyAsyncHttpClientFactoryTest.java (91%) rename extras/registry/src/{main/java/org/asynchttpclient/extra => test/java/org/asynchttpclient/extras/registry}/NettyAsyncHttpClientFactoryTest.java (91%) rename extras/registry/src/test/java/org/asynchttpclient/{extra => extras/registry}/TestAsyncHttpClientRegistry.java (86%) diff --git a/extras/guava/src/main/java/org/asynchttpclient/extra/ListenableFutureAdapter.java b/extras/guava/src/main/java/org/asynchttpclient/extras/guava/ListenableFutureAdapter.java similarity index 98% rename from extras/guava/src/main/java/org/asynchttpclient/extra/ListenableFutureAdapter.java rename to extras/guava/src/main/java/org/asynchttpclient/extras/guava/ListenableFutureAdapter.java index 6ac40b92c6..9c51d29b68 100644 --- a/extras/guava/src/main/java/org/asynchttpclient/extra/ListenableFutureAdapter.java +++ b/extras/guava/src/main/java/org/asynchttpclient/extras/guava/ListenableFutureAdapter.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.extra; +package org.asynchttpclient.extras.guava; import org.asynchttpclient.ListenableFuture; diff --git a/extras/guava/src/main/java/org/asynchttpclient/extra/RateLimitedThrottleRequestFilter.java b/extras/guava/src/main/java/org/asynchttpclient/extras/guava/RateLimitedThrottleRequestFilter.java similarity index 96% rename from extras/guava/src/main/java/org/asynchttpclient/extra/RateLimitedThrottleRequestFilter.java rename to extras/guava/src/main/java/org/asynchttpclient/extras/guava/RateLimitedThrottleRequestFilter.java index d6ddb69a68..dc07928d43 100644 --- a/extras/guava/src/main/java/org/asynchttpclient/extra/RateLimitedThrottleRequestFilter.java +++ b/extras/guava/src/main/java/org/asynchttpclient/extras/guava/RateLimitedThrottleRequestFilter.java @@ -1,5 +1,7 @@ -package org.asynchttpclient.extra; +package org.asynchttpclient.extras.guava; +import org.asynchttpclient.extra.AsyncHandlerWrapper; +import org.asynchttpclient.extra.ThrottleRequestFilter; import org.asynchttpclient.filter.FilterContext; import org.asynchttpclient.filter.FilterException; import org.asynchttpclient.filter.RequestFilter; diff --git a/extras/jdeferred/src/main/java/org/asynchttpclient/extra/AsyncHttpDeferredObject.java b/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/AsyncHttpDeferredObject.java similarity index 98% rename from extras/jdeferred/src/main/java/org/asynchttpclient/extra/AsyncHttpDeferredObject.java rename to extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/AsyncHttpDeferredObject.java index 7be6007aa0..9f50dc71cc 100644 --- a/extras/jdeferred/src/main/java/org/asynchttpclient/extra/AsyncHttpDeferredObject.java +++ b/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/AsyncHttpDeferredObject.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.asynchttpclient.extra; +package org.asynchttpclient.extras.jdeferred; import org.asynchttpclient.AsyncCompletionHandler; import org.asynchttpclient.AsyncHandler; diff --git a/extras/jdeferred/src/main/java/org/asynchttpclient/extra/ContentWriteProgress.java b/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/ContentWriteProgress.java similarity index 96% rename from extras/jdeferred/src/main/java/org/asynchttpclient/extra/ContentWriteProgress.java rename to extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/ContentWriteProgress.java index 0f3b5b5ddc..b07a76d3f7 100644 --- a/extras/jdeferred/src/main/java/org/asynchttpclient/extra/ContentWriteProgress.java +++ b/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/ContentWriteProgress.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.asynchttpclient.extra; +package org.asynchttpclient.extras.jdeferred; public class ContentWriteProgress implements HttpProgress { private final long amount; diff --git a/extras/jdeferred/src/main/java/org/asynchttpclient/extra/HttpProgress.java b/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/HttpProgress.java similarity index 93% rename from extras/jdeferred/src/main/java/org/asynchttpclient/extra/HttpProgress.java rename to extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/HttpProgress.java index 19a3e22796..8ff4788564 100644 --- a/extras/jdeferred/src/main/java/org/asynchttpclient/extra/HttpProgress.java +++ b/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/HttpProgress.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.asynchttpclient.extra; +package org.asynchttpclient.extras.jdeferred; public interface HttpProgress { } diff --git a/extras/jdeferred/src/main/java/org/asynchttpclient/extra/HttpResponseBodyPartProgress.java b/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/HttpResponseBodyPartProgress.java similarity index 95% rename from extras/jdeferred/src/main/java/org/asynchttpclient/extra/HttpResponseBodyPartProgress.java rename to extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/HttpResponseBodyPartProgress.java index 362d1fb1a8..7137c5469b 100644 --- a/extras/jdeferred/src/main/java/org/asynchttpclient/extra/HttpResponseBodyPartProgress.java +++ b/extras/jdeferred/src/main/java/org/asynchttpclient/extras/jdeferred/HttpResponseBodyPartProgress.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.asynchttpclient.extra; +package org.asynchttpclient.extras.jdeferred; import org.asynchttpclient.HttpResponseBodyPart; diff --git a/extras/jdeferred/src/test/java/org/asynchttpclient/extra/AsyncHttpTest.java b/extras/jdeferred/src/test/java/org/asynchttpclient/extra/AsyncHttpTest.java index 0d5eeb837b..cb58470173 100644 --- a/extras/jdeferred/src/test/java/org/asynchttpclient/extra/AsyncHttpTest.java +++ b/extras/jdeferred/src/test/java/org/asynchttpclient/extra/AsyncHttpTest.java @@ -21,6 +21,8 @@ import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.DefaultAsyncHttpClient; import org.asynchttpclient.Response; +import org.asynchttpclient.extras.jdeferred.AsyncHttpDeferredObject; +import org.asynchttpclient.extras.jdeferred.HttpProgress; import org.jdeferred.DoneCallback; import org.jdeferred.ProgressCallback; import org.jdeferred.Promise; diff --git a/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientFactory.java b/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientFactory.java similarity index 99% rename from extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientFactory.java rename to extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientFactory.java index 780a0af585..90457a924f 100644 --- a/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientFactory.java +++ b/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientFactory.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.extra; +package org.asynchttpclient.extras.registry; import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; diff --git a/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientImplException.java b/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientImplException.java similarity index 95% rename from extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientImplException.java rename to extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientImplException.java index 1862ea8c70..f59bf0698c 100644 --- a/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientImplException.java +++ b/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientImplException.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.extra; +package org.asynchttpclient.extras.registry; @SuppressWarnings("serial") public class AsyncHttpClientImplException extends RuntimeException { diff --git a/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientRegistry.java b/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistry.java similarity index 98% rename from extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientRegistry.java rename to extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistry.java index 13ce83081e..ca63009ce7 100644 --- a/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientRegistry.java +++ b/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistry.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.extra; +package org.asynchttpclient.extras.registry; import org.asynchttpclient.AsyncHttpClient; diff --git a/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientRegistryImpl.java b/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryImpl.java similarity index 98% rename from extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientRegistryImpl.java rename to extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryImpl.java index 378f19290e..0c13c2e585 100644 --- a/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncHttpClientRegistryImpl.java +++ b/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryImpl.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.extra; +package org.asynchttpclient.extras.registry; import org.asynchttpclient.AsyncHttpClient; diff --git a/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncImplHelper.java b/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncImplHelper.java similarity index 99% rename from extras/registry/src/main/java/org/asynchttpclient/extra/AsyncImplHelper.java rename to extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncImplHelper.java index 5f341d3ae7..0e827c0114 100644 --- a/extras/registry/src/main/java/org/asynchttpclient/extra/AsyncImplHelper.java +++ b/extras/registry/src/main/java/org/asynchttpclient/extras/registry/AsyncImplHelper.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.extra; +package org.asynchttpclient.extras.registry; import org.asynchttpclient.AsyncHttpClient; diff --git a/extras/registry/src/test/java/org/asynchttpclient/extra/AbstractAsyncHttpClientFactoryTest.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AbstractAsyncHttpClientFactoryTest.java similarity index 97% rename from extras/registry/src/test/java/org/asynchttpclient/extra/AbstractAsyncHttpClientFactoryTest.java rename to extras/registry/src/test/java/org/asynchttpclient/extras/registry/AbstractAsyncHttpClientFactoryTest.java index 7ee6f94d36..4561a751ee 100644 --- a/extras/registry/src/test/java/org/asynchttpclient/extra/AbstractAsyncHttpClientFactoryTest.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AbstractAsyncHttpClientFactoryTest.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.extra; +package org.asynchttpclient.extras.registry; import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; @@ -18,12 +18,11 @@ import org.asynchttpclient.DefaultAsyncHttpClient; import org.asynchttpclient.Response; import org.asynchttpclient.TestAsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig.Builder; import org.asynchttpclient.async.util.EchoHandler; import org.asynchttpclient.async.util.TestUtils; -import org.asynchttpclient.extra.AsyncHttpClientFactory; -import org.asynchttpclient.extra.AsyncHttpClientImplException; -import org.asynchttpclient.extra.AsyncImplHelper; +import org.asynchttpclient.extras.registry.AsyncHttpClientFactory; +import org.asynchttpclient.extras.registry.AsyncHttpClientImplException; +import org.asynchttpclient.extras.registry.AsyncImplHelper; import org.eclipse.jetty.server.Server; import org.testng.Assert; import org.testng.annotations.AfterClass; diff --git a/extras/registry/src/test/java/org/asynchttpclient/extra/AsyncHttpClientRegistryTest.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryTest.java similarity index 94% rename from extras/registry/src/test/java/org/asynchttpclient/extra/AsyncHttpClientRegistryTest.java rename to extras/registry/src/test/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryTest.java index c973b6d59e..ffd8afad0f 100644 --- a/extras/registry/src/test/java/org/asynchttpclient/extra/AsyncHttpClientRegistryTest.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryTest.java @@ -10,13 +10,13 @@ * "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.extra; +package org.asynchttpclient.extras.registry; import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.extra.AsyncHttpClientFactory; -import org.asynchttpclient.extra.AsyncHttpClientImplException; -import org.asynchttpclient.extra.AsyncHttpClientRegistryImpl; -import org.asynchttpclient.extra.AsyncImplHelper; +import org.asynchttpclient.extras.registry.AsyncHttpClientFactory; +import org.asynchttpclient.extras.registry.AsyncHttpClientImplException; +import org.asynchttpclient.extras.registry.AsyncHttpClientRegistryImpl; +import org.asynchttpclient.extras.registry.AsyncImplHelper; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; diff --git a/extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClient.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClient.java similarity index 98% rename from extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClient.java rename to extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClient.java index d9b0a5c3c1..84343ec0d4 100644 --- a/extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClient.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClient.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.extra; +package org.asynchttpclient.extras.registry; import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpClient; diff --git a/extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClientException.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClientException.java similarity index 88% rename from extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClientException.java rename to extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClientException.java index 318d59ddbf..1aca098e89 100644 --- a/extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClientException.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClientException.java @@ -10,9 +10,9 @@ * "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.extra; +package org.asynchttpclient.extras.registry; -import org.asynchttpclient.extra.AsyncHttpClientImplException; +import org.asynchttpclient.extras.registry.AsyncHttpClientImplException; @SuppressWarnings("serial") public class BadAsyncHttpClientException extends AsyncHttpClientImplException { diff --git a/extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClientRegistry.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClientRegistry.java similarity index 88% rename from extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClientRegistry.java rename to extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClientRegistry.java index 7aef111bd0..b3d853de3f 100644 --- a/extras/registry/src/test/java/org/asynchttpclient/extra/BadAsyncHttpClientRegistry.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/BadAsyncHttpClientRegistry.java @@ -10,9 +10,9 @@ * "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.extra; +package org.asynchttpclient.extras.registry; -import org.asynchttpclient.extra.AsyncHttpClientRegistryImpl; +import org.asynchttpclient.extras.registry.AsyncHttpClientRegistryImpl; public class BadAsyncHttpClientRegistry extends AsyncHttpClientRegistryImpl { diff --git a/extras/registry/src/main/java/org/asynchttpclient/extra/GrizzlyAsyncHttpClientFactoryTest.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/GrizzlyAsyncHttpClientFactoryTest.java similarity index 91% rename from extras/registry/src/main/java/org/asynchttpclient/extra/GrizzlyAsyncHttpClientFactoryTest.java rename to extras/registry/src/test/java/org/asynchttpclient/extras/registry/GrizzlyAsyncHttpClientFactoryTest.java index ed27eb1257..cfa9c9d4bd 100644 --- a/extras/registry/src/main/java/org/asynchttpclient/extra/GrizzlyAsyncHttpClientFactoryTest.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/GrizzlyAsyncHttpClientFactoryTest.java @@ -10,11 +10,11 @@ * "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.extra; +package org.asynchttpclient.extras.registry; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.extra.AbstractAsyncHttpClientFactoryTest; +import org.asynchttpclient.extras.registry.AbstractAsyncHttpClientFactoryTest; import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; import org.testng.annotations.Test; diff --git a/extras/registry/src/main/java/org/asynchttpclient/extra/NettyAsyncHttpClientFactoryTest.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/NettyAsyncHttpClientFactoryTest.java similarity index 91% rename from extras/registry/src/main/java/org/asynchttpclient/extra/NettyAsyncHttpClientFactoryTest.java rename to extras/registry/src/test/java/org/asynchttpclient/extras/registry/NettyAsyncHttpClientFactoryTest.java index 340b3befe2..a5cc1fa028 100644 --- a/extras/registry/src/main/java/org/asynchttpclient/extra/NettyAsyncHttpClientFactoryTest.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/NettyAsyncHttpClientFactoryTest.java @@ -10,11 +10,11 @@ * "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.extra; +package org.asynchttpclient.extras.registry; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.extra.AbstractAsyncHttpClientFactoryTest; +import org.asynchttpclient.extras.registry.AbstractAsyncHttpClientFactoryTest; import org.asynchttpclient.providers.netty.NettyAsyncHttpProvider; import org.testng.annotations.Test; diff --git a/extras/registry/src/test/java/org/asynchttpclient/extra/TestAsyncHttpClientRegistry.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/TestAsyncHttpClientRegistry.java similarity index 86% rename from extras/registry/src/test/java/org/asynchttpclient/extra/TestAsyncHttpClientRegistry.java rename to extras/registry/src/test/java/org/asynchttpclient/extras/registry/TestAsyncHttpClientRegistry.java index 84f052ddcb..b9410737d5 100644 --- a/extras/registry/src/test/java/org/asynchttpclient/extra/TestAsyncHttpClientRegistry.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/TestAsyncHttpClientRegistry.java @@ -10,9 +10,9 @@ * "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.extra; +package org.asynchttpclient.extras.registry; -import org.asynchttpclient.extra.AsyncHttpClientRegistryImpl; +import org.asynchttpclient.extras.registry.AsyncHttpClientRegistryImpl; public class TestAsyncHttpClientRegistry extends AsyncHttpClientRegistryImpl { From de6030eeaa939dfb99a4715312a3eba8b71c6c3e Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Jul 2014 12:54:52 +0200 Subject: [PATCH 0069/2020] Minor clean up --- .../org/asynchttpclient/util/AsyncHttpProviderUtils.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java index 6a7bb09cc4..1f614a507d 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java @@ -65,13 +65,7 @@ public final static URI createNonEmptyPathURI(String u) { } public final static String getBaseUrl(URI uri) { - String url = uri.getScheme() + "://" + uri.getAuthority(); - int port = uri.getPort(); - if (port == -1) { - port = getPort(uri); - url += ":" + port; - } - return url; + return uri.getScheme() + "://" + getAuthority(uri); } public final static String getAuthority(URI uri) { From 85bb8150db972e3b3d9f965e672df0afd5bc8fda Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 2 Jul 2014 13:47:07 +0200 Subject: [PATCH 0070/2020] Drop Proxy.getURI, close #586 --- .../DefaultConnectionPoolStrategy.java | 2 +- .../main/java/org/asynchttpclient/ProxyServer.java | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/DefaultConnectionPoolStrategy.java b/api/src/main/java/org/asynchttpclient/DefaultConnectionPoolStrategy.java index 2ae901ea80..55d1a1eff8 100644 --- a/api/src/main/java/org/asynchttpclient/DefaultConnectionPoolStrategy.java +++ b/api/src/main/java/org/asynchttpclient/DefaultConnectionPoolStrategy.java @@ -26,6 +26,6 @@ public enum DefaultConnectionPoolStrategy implements ConnectionPoolKeyStrategy { @Override public String getKey(URI uri, ProxyServer proxyServer) { String serverPart = AsyncHttpProviderUtils.getBaseUrl(uri); - return proxyServer != null ? AsyncHttpProviderUtils.getBaseUrl(proxyServer.getURI()) + serverPart : serverPart; + return proxyServer != null ? proxyServer.getUrl() + serverPart : serverPart; } } diff --git a/api/src/main/java/org/asynchttpclient/ProxyServer.java b/api/src/main/java/org/asynchttpclient/ProxyServer.java index 171c6f6238..3c6a5ff429 100644 --- a/api/src/main/java/org/asynchttpclient/ProxyServer.java +++ b/api/src/main/java/org/asynchttpclient/ProxyServer.java @@ -16,10 +16,8 @@ */ package org.asynchttpclient; -import org.asynchttpclient.util.AsyncHttpProviderUtils; import org.asynchttpclient.util.StandardCharsets; -import java.net.URI; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; @@ -55,7 +53,7 @@ public String toString() { private final String principal; private final String password; private final int port; - private final URI uri; + private final String url; private String encoding = StandardCharsets.UTF_8.name(); private Charset charset = StandardCharsets.UTF_8; private String ntlmDomain = System.getProperty("http.auth.ntlm.domain", ""); @@ -66,7 +64,7 @@ public ProxyServer(final Protocol protocol, final String host, final int port, S this.port = port; this.principal = principal; this.password = password; - this.uri = AsyncHttpProviderUtils.createNonEmptyPathURI(toString()); + url = protocol + "://" + host + ":" + port; } public ProxyServer(final String host, final int port, String principal, String password) { @@ -119,8 +117,8 @@ public Charset getCharset() { return charset; } - public URI getURI() { - return uri; + public String getUrl() { + return url; } public ProxyServer addNonProxyHost(String uri) { @@ -148,6 +146,6 @@ public String getNtlmDomain() { @Override public String toString() { - return protocol + "://" + host + ":" + port; + return url; } } From 32c74a4407d45dfac4bdd9b13769c99abc8bc550 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 9 Jul 2014 00:24:12 +0200 Subject: [PATCH 0071/2020] Port on master new Request API, close #588 --- .../AsyncHttpClientConfig.java | 44 +- .../AsyncHttpClientConfigBean.java | 12 +- .../AsyncHttpClientConfigDefaults.java | 8 +- .../asynchttpclient/BoundRequestBuilder.java | 29 +- .../ConnectionPoolKeyStrategy.java | 4 +- .../DefaultAsyncHttpClient.java | 4 +- .../DefaultConnectionPoolStrategy.java | 5 +- .../org/asynchttpclient/FluentStringsMap.java | 40 +- .../asynchttpclient/HttpResponseStatus.java | 13 +- .../main/java/org/asynchttpclient/Param.java | 62 +++ .../asynchttpclient/ProxyServerSelector.java | 6 +- .../java/org/asynchttpclient/Request.java | 63 +-- .../org/asynchttpclient/RequestBuilder.java | 51 ++- .../asynchttpclient/RequestBuilderBase.java | 410 ++++++++---------- .../java/org/asynchttpclient/Response.java | 10 +- .../asynchttpclient/SignatureCalculator.java | 3 +- .../SimpleAsyncHttpClient.java | 30 +- .../oauth/OAuthSignatureCalculator.java | 72 ++- .../providers/ResponseBase.java | 4 +- .../resumable/ResumableAsyncHandler.java | 2 +- .../asynchttpclient/uri/UriComponents.java | 182 ++++++++ .../uri/UriComponentsParser.java | 340 +++++++++++++++ .../util/AsyncHttpProviderUtils.java | 95 +--- .../org/asynchttpclient/util/ProxyUtils.java | 56 +-- .../asynchttpclient/util/QueryComputer.java | 140 ++++++ .../util/StringCharSequence.java | 54 +++ .../asynchttpclient/util/UTF8UrlDecoder.java | 70 +++ .../asynchttpclient/util/UTF8UrlEncoder.java | 42 +- .../webdav/WebDavCompletionHandlerBase.java | 25 +- .../webdav/WebDavResponse.java | 15 +- .../async/AsyncProvidersBasicTest.java | 13 +- .../async/AsyncStreamHandlerTest.java | 8 +- .../async/ParamEncodingTest.java | 2 +- .../async/PerRequestRelative302Test.java | 26 +- .../async/PostRedirectGetTest.java | 4 +- .../asynchttpclient/async/PostWithQSTest.java | 8 +- .../async/QueryParametersTest.java | 17 +- .../async/Relative302Test.java | 27 +- .../async/RequestBuilderTest.java | 23 +- .../oauth/TestSignatureCalculator.java | 14 +- .../util/AsyncHttpProviderUtilsTest.java | 47 -- .../providers/grizzly/ConnectionManager.java | 16 +- .../providers/grizzly/EventHandler.java | 19 +- .../grizzly/GrizzlyResponseStatus.java | 4 +- .../providers/grizzly/HttpTxContext.java | 2 +- .../grizzly/ProxyAwareConnectorHandler.java | 7 +- .../providers/grizzly/Utils.java | 4 +- .../bodyhandler/ParamsBodyHandler.java | 29 +- .../filters/AsyncHttpClientFilter.java | 40 +- .../grizzly/filters/TunnelFilter.java | 10 +- .../filters/events/TunnelRequestEvent.java | 9 +- .../ProxyAuthorizationHandler.java | 2 +- .../statushandler/RedirectHandler.java | 12 +- .../GrizzlyNoTransferEncodingTest.java | 2 +- providers/netty/pom.xml | 2 +- .../providers/netty/channel/Channels.java | 6 +- .../netty/future/NettyResponseFuture.java | 10 +- .../providers/netty/handler/HttpProtocol.java | 35 +- .../providers/netty/handler/Protocol.java | 28 +- .../netty/handler/WebSocketProtocol.java | 2 +- .../netty/request/NettyRequestFactory.java | 70 ++- .../netty/request/NettyRequestSender.java | 26 +- .../netty/response/ResponseHeaders.java | 13 +- .../netty/response/ResponseStatus.java | 4 +- .../providers/netty/util/HttpUtil.java | 5 +- .../providers/netty/NettyFilterTest.java | 2 + .../netty/RetryNonBlockingIssue.java | 6 +- 67 files changed, 1539 insertions(+), 906 deletions(-) create mode 100644 api/src/main/java/org/asynchttpclient/Param.java create mode 100644 api/src/main/java/org/asynchttpclient/uri/UriComponents.java create mode 100644 api/src/main/java/org/asynchttpclient/uri/UriComponentsParser.java create mode 100644 api/src/main/java/org/asynchttpclient/util/QueryComputer.java create mode 100644 api/src/main/java/org/asynchttpclient/util/StringCharSequence.java create mode 100644 api/src/main/java/org/asynchttpclient/util/UTF8UrlDecoder.java delete mode 100644 api/src/test/java/org/asynchttpclient/util/AsyncHttpProviderUtilsTest.java diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java index 444ceda349..65fac8d2be 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -85,7 +85,7 @@ public class AsyncHttpClientConfig { protected int idleConnectionInPoolTimeoutInMs; protected int idleConnectionTimeoutInMs; protected int requestTimeoutInMs; - protected boolean redirectEnabled; + protected boolean followRedirect; protected int maxRedirects; protected boolean compressionEnabled; protected String userAgent; @@ -101,7 +101,7 @@ public class AsyncHttpClientConfig { protected int requestCompressionLevel; protected int maxRequestRetry; protected boolean allowSslConnectionPool; - protected boolean useRawUrl; + protected boolean disableUrlEncodingForBoundRequests; protected boolean removeQueryParamOnRedirect; protected HostnameVerifier hostnameVerifier; protected int ioThreadMultiplier; @@ -125,7 +125,7 @@ private AsyncHttpClientConfig(int maxTotalConnections, // int idleConnectionTimeoutInMs, // int requestTimeoutInMs, // int connectionMaxLifeTimeInMs, // - boolean redirectEnabled, // + boolean followRedirect, // int maxRedirects, // boolean compressionEnabled, // String userAgent, // @@ -143,7 +143,7 @@ private AsyncHttpClientConfig(int maxTotalConnections, // int requestCompressionLevel, // int maxRequestRetry, // boolean allowSslConnectionCaching, // - boolean useRawUrl, // + boolean disableUrlEncodingForBoundRequests, // boolean removeQueryParamOnRedirect, // HostnameVerifier hostnameVerifier, // int ioThreadMultiplier, // @@ -163,7 +163,7 @@ private AsyncHttpClientConfig(int maxTotalConnections, // this.idleConnectionTimeoutInMs = idleConnectionTimeoutInMs; this.requestTimeoutInMs = requestTimeoutInMs; this.maxConnectionLifeTimeInMs = connectionMaxLifeTimeInMs; - this.redirectEnabled = redirectEnabled; + this.followRedirect = followRedirect; this.maxRedirects = maxRedirects; this.compressionEnabled = compressionEnabled; this.userAgent = userAgent; @@ -184,7 +184,7 @@ private AsyncHttpClientConfig(int maxTotalConnections, // this.useRelativeURIsWithSSLProxies = useRelativeURIsWithSSLProxies; this.applicationThreadPool = applicationThreadPool; this.proxyServerSelector = proxyServerSelector; - this.useRawUrl = useRawUrl; + this.disableUrlEncodingForBoundRequests = disableUrlEncodingForBoundRequests; this.spdyEnabled = spdyEnabled; this.spdyInitialWindowSize = spdyInitialWindowSize; this.spdyMaxConcurrentStreams = spdyMaxConcurrentStreams; @@ -262,8 +262,8 @@ public int getRequestTimeoutInMs() { * * @return true if enabled. */ - public boolean isRedirectEnabled() { - return redirectEnabled; + public boolean isFollowRedirect() { + return followRedirect; } /** @@ -422,10 +422,10 @@ public boolean isSslConnectionPoolEnabled() { } /** - * @return the useRawUrl + * @return the disableUrlEncodingForBoundRequests */ - public boolean isUseRawUrl() { - return useRawUrl; + public boolean isDisableUrlEncodingForBoundRequests() { + return disableUrlEncodingForBoundRequests; } /** @@ -552,7 +552,7 @@ public static class Builder { private int idleConnectionTimeoutInMs = defaultIdleConnectionTimeoutInMs(); private int requestTimeoutInMs = defaultRequestTimeoutInMs(); private int maxConnectionLifeTimeInMs = defaultMaxConnectionLifeTimeInMs(); - private boolean redirectEnabled = defaultRedirectEnabled(); + private boolean followRedirect = defaultFollowRedirect(); private int maxRedirects = defaultMaxRedirects(); private boolean compressionEnabled = defaultCompressionEnabled(); private String userAgent = defaultUserAgent(); @@ -564,7 +564,7 @@ public static class Builder { private int maxRequestRetry = defaultMaxRequestRetry(); private int ioThreadMultiplier = defaultIoThreadMultiplier(); private boolean allowSslConnectionPool = defaultAllowSslConnectionPool(); - private boolean useRawUrl = defaultUseRawUrl(); + private boolean disableUrlEncodingForBoundRequests = defaultDisableUrlEncodingForBoundRequests(); private boolean removeQueryParamOnRedirect = defaultRemoveQueryParamOnRedirect(); private boolean strict302Handling = defaultStrict302Handling(); private HostnameVerifier hostnameVerifier = defaultHostnameVerifier(); @@ -676,8 +676,8 @@ public Builder setRequestTimeoutInMs(int requestTimeoutInMs) { * @param redirectEnabled true if enabled. * @return a {@link Builder} */ - public Builder setFollowRedirects(boolean redirectEnabled) { - this.redirectEnabled = redirectEnabled; + public Builder setFollowRedirects(boolean followRedirect) { + this.followRedirect = followRedirect; return this; } @@ -933,11 +933,11 @@ public Builder setAllowSslConnectionPool(boolean allowSslConnectionPool) { * Allows use unescaped URLs in requests * useful for retrieving data from broken sites * - * @param useRawUrl + * @param disableUrlEncodingForBoundRequests * @return this */ - public Builder setUseRawUrl(boolean useRawUrl) { - this.useRawUrl = useRawUrl; + public Builder setDisableUrlEncodingForBoundRequests(boolean disableUrlEncodingForBoundRequests) { + this.disableUrlEncodingForBoundRequests = disableUrlEncodingForBoundRequests; return this; } @@ -1111,7 +1111,7 @@ public Builder(AsyncHttpClientConfig prototype) { requestTimeoutInMs = prototype.getRequestTimeoutInMs(); sslContext = prototype.getSSLContext(); userAgent = prototype.getUserAgent(); - redirectEnabled = prototype.isRedirectEnabled(); + followRedirect = prototype.isFollowRedirect(); compressionEnabled = prototype.isCompressionEnabled(); applicationThreadPool = prototype.executorService(); @@ -1124,7 +1124,7 @@ public Builder(AsyncHttpClientConfig prototype) { ioExceptionFilters.addAll(prototype.getIOExceptionFilters()); requestCompressionLevel = prototype.getRequestCompressionLevel(); - useRawUrl = prototype.isUseRawUrl(); + disableUrlEncodingForBoundRequests = prototype.isDisableUrlEncodingForBoundRequests(); ioThreadMultiplier = prototype.getIoThreadMultiplier(); maxRequestRetry = prototype.getMaxRequestRetry(); allowSslConnectionPool = prototype.getAllowPoolingConnection(); @@ -1172,7 +1172,7 @@ public Thread newThread(Runnable r) { idleConnectionTimeoutInMs, // requestTimeoutInMs, // maxConnectionLifeTimeInMs, // - redirectEnabled, // + followRedirect, // maxRedirects, // compressionEnabled, // userAgent, // @@ -1190,7 +1190,7 @@ public Thread newThread(Runnable r) { requestCompressionLevel, // maxRequestRetry, // allowSslConnectionPool, // - useRawUrl, // + disableUrlEncodingForBoundRequests, // removeQueryParamOnRedirect, // hostnameVerifier, // ioThreadMultiplier, // diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java index 463d0c2c37..6c677b075e 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java @@ -53,7 +53,7 @@ void configureDefaults() { idleConnectionTimeoutInMs = defaultIdleConnectionTimeoutInMs(); requestTimeoutInMs = defaultRequestTimeoutInMs(); maxConnectionLifeTimeInMs = defaultMaxConnectionLifeTimeInMs(); - redirectEnabled = defaultRedirectEnabled(); + followRedirect = defaultFollowRedirect(); maxRedirects = defaultMaxRedirects(); compressionEnabled = defaultCompressionEnabled(); userAgent = defaultUserAgent(); @@ -63,7 +63,7 @@ void configureDefaults() { maxRequestRetry = defaultMaxRequestRetry(); ioThreadMultiplier = defaultIoThreadMultiplier(); allowSslConnectionPool = defaultAllowSslConnectionPool(); - useRawUrl = defaultUseRawUrl(); + disableUrlEncodingForBoundRequests = defaultDisableUrlEncodingForBoundRequests(); removeQueryParamOnRedirect = defaultRemoveQueryParamOnRedirect(); strict302Handling = defaultStrict302Handling(); hostnameVerifier = defaultHostnameVerifier(); @@ -123,8 +123,8 @@ public AsyncHttpClientConfigBean setRequestTimeoutInMs(int requestTimeoutInMs) { return this; } - public AsyncHttpClientConfigBean setRedirectEnabled(boolean redirectEnabled) { - this.redirectEnabled = redirectEnabled; + public AsyncHttpClientConfigBean setFollowRedirect(boolean followRedirect) { + this.followRedirect = followRedirect; return this; } @@ -216,8 +216,8 @@ public AsyncHttpClientConfigBean setAllowSslConnectionPool(boolean allowSslConne return this; } - public AsyncHttpClientConfigBean setUseRawUrl(boolean useRawUrl) { - this.useRawUrl = useRawUrl; + public AsyncHttpClientConfigBean setDisableUrlEncodingForBoundRequests(boolean disableUrlEncodingForBoundRequests) { + this.disableUrlEncodingForBoundRequests = disableUrlEncodingForBoundRequests; return this; } diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java index 3cb9ebddc7..4de9e2fcb6 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java @@ -57,8 +57,8 @@ public static int defaultMaxConnectionLifeTimeInMs() { return Integer.getInteger(ASYNC_CLIENT + "maxConnectionLifeTimeInMs", -1); } - public static boolean defaultRedirectEnabled() { - return Boolean.getBoolean(ASYNC_CLIENT + "redirectsEnabled"); + public static boolean defaultFollowRedirect() { + return Boolean.getBoolean(ASYNC_CLIENT + "followRedirect"); } public static int defaultMaxRedirects() { @@ -110,8 +110,8 @@ public static boolean defaultAllowSslConnectionPool() { return getBoolean(ASYNC_CLIENT + "allowSslConnectionPool", true); } - public static boolean defaultUseRawUrl() { - return Boolean.getBoolean(ASYNC_CLIENT + "useRawUrl"); + public static boolean defaultDisableUrlEncodingForBoundRequests() { + return Boolean.getBoolean(ASYNC_CLIENT + "disableUrlEncodingForBoundRequests"); } public static boolean defaultRemoveQueryParamOnRedirect() { diff --git a/api/src/main/java/org/asynchttpclient/BoundRequestBuilder.java b/api/src/main/java/org/asynchttpclient/BoundRequestBuilder.java index b42f2e2eae..638450a202 100644 --- a/api/src/main/java/org/asynchttpclient/BoundRequestBuilder.java +++ b/api/src/main/java/org/asynchttpclient/BoundRequestBuilder.java @@ -18,14 +18,15 @@ import java.io.IOException; import java.io.InputStream; import java.util.Collection; +import java.util.List; import java.util.Map; public class BoundRequestBuilder extends RequestBuilderBase { private final AsyncHttpClient client; - public BoundRequestBuilder(AsyncHttpClient client, String reqType, boolean useRawUrl) { - super(BoundRequestBuilder.class, reqType, useRawUrl); + public BoundRequestBuilder(AsyncHttpClient client, String method, boolean isDisableUrlEncoding) { + super(BoundRequestBuilder.class, method, isDisableUrlEncoding); this.client = client; } @@ -42,9 +43,10 @@ public ListenableFuture execute() throws IOException { return client.executeRequest(build(), new AsyncCompletionHandlerBase()); } - // Note: For now we keep the delegates in place even though they are not needed - // since otherwise Clojure (and maybe other languages) won't be able to - // access these methods - see Clojure tickets 126 and 259 + // Note: For now we keep the delegates in place even though they are not + // needed + // since otherwise Clojure (and maybe other languages) won't be able to + // access these methods - see Clojure tickets 126 and 259 @Override public BoundRequestBuilder addBodyPart(Part part) { @@ -62,13 +64,13 @@ public BoundRequestBuilder addHeader(String name, String value) { } @Override - public BoundRequestBuilder addParameter(String key, String value) { - return super.addParameter(key, value); + public BoundRequestBuilder addFormParam(String key, String value) { + return super.addFormParam(key, value); } @Override - public BoundRequestBuilder addQueryParameter(String name, String value) { - return super.addQueryParameter(name, value); + public BoundRequestBuilder addQueryParam(String name, String value) { + return super.addQueryParam(name, value); } @Override @@ -107,13 +109,13 @@ public BoundRequestBuilder setHeaders(Map> headers) { } @Override - public BoundRequestBuilder setParameters(Map> parameters) { - return super.setParameters(parameters); + public BoundRequestBuilder setFormParams(Map> params) { + return super.setFormParams(params); } @Override - public BoundRequestBuilder setParameters(FluentStringsMap parameters) { - return super.setParameters(parameters); + public BoundRequestBuilder setFormParams(List params) { + return super.setFormParams(params); } @Override @@ -126,7 +128,6 @@ public BoundRequestBuilder setVirtualHost(String virtualHost) { return super.setVirtualHost(virtualHost); } - @Override public BoundRequestBuilder setSignatureCalculator(SignatureCalculator signatureCalculator) { return super.setSignatureCalculator(signatureCalculator); } diff --git a/api/src/main/java/org/asynchttpclient/ConnectionPoolKeyStrategy.java b/api/src/main/java/org/asynchttpclient/ConnectionPoolKeyStrategy.java index adf626f307..704ed78cb1 100644 --- a/api/src/main/java/org/asynchttpclient/ConnectionPoolKeyStrategy.java +++ b/api/src/main/java/org/asynchttpclient/ConnectionPoolKeyStrategy.java @@ -15,9 +15,9 @@ */ package org.asynchttpclient; -import java.net.URI; +import org.asynchttpclient.uri.UriComponents; public interface ConnectionPoolKeyStrategy { - String getKey(URI uri, ProxyServer proxy); + String getKey(UriComponents uri, ProxyServer proxy); } diff --git a/api/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java index e6ba817ea2..63536a37c4 100644 --- a/api/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java +++ b/api/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java @@ -311,8 +311,8 @@ private static AsyncHttpProvider loadDefaultProvider(String[] providerClassNames throw new IllegalStateException("No providers found on the classpath"); } - protected BoundRequestBuilder requestBuilder(String reqType, String url) { - return new BoundRequestBuilder(this, reqType, config.isUseRawUrl()).setUrl(url).setSignatureCalculator(signatureCalculator); + protected BoundRequestBuilder requestBuilder(String method, String url) { + return new BoundRequestBuilder(this, method, config.isDisableUrlEncodingForBoundRequests()).setUrl(url).setSignatureCalculator(signatureCalculator); } protected BoundRequestBuilder requestBuilder(Request prototype) { diff --git a/api/src/main/java/org/asynchttpclient/DefaultConnectionPoolStrategy.java b/api/src/main/java/org/asynchttpclient/DefaultConnectionPoolStrategy.java index 55d1a1eff8..6b2c9ae5b7 100644 --- a/api/src/main/java/org/asynchttpclient/DefaultConnectionPoolStrategy.java +++ b/api/src/main/java/org/asynchttpclient/DefaultConnectionPoolStrategy.java @@ -15,16 +15,15 @@ */ package org.asynchttpclient; +import org.asynchttpclient.uri.UriComponents; import org.asynchttpclient.util.AsyncHttpProviderUtils; -import java.net.URI; - public enum DefaultConnectionPoolStrategy implements ConnectionPoolKeyStrategy { INSTANCE; @Override - public String getKey(URI uri, ProxyServer proxyServer) { + public String getKey(UriComponents uri, ProxyServer proxyServer) { String serverPart = AsyncHttpProviderUtils.getBaseUrl(uri); return proxyServer != null ? proxyServer.getUrl() + serverPart : serverPart; } diff --git a/api/src/main/java/org/asynchttpclient/FluentStringsMap.java b/api/src/main/java/org/asynchttpclient/FluentStringsMap.java index 34cc5ff237..b295b7a1be 100644 --- a/api/src/main/java/org/asynchttpclient/FluentStringsMap.java +++ b/api/src/main/java/org/asynchttpclient/FluentStringsMap.java @@ -195,7 +195,7 @@ public FluentStringsMap replaceAll(Map put(String key, List value) { if (key == null) { throw new NullPointerException("Null keys are not allowed"); @@ -210,7 +210,7 @@ public List put(String key, List value) { /** * {@inheritDoc} */ - @Override + /* @Override */ public void putAll(Map> values) { replaceAll(values); } @@ -259,7 +259,7 @@ public FluentStringsMap deleteAll(Collection keys) { /** * {@inheritDoc} */ - @Override + /* @Override */ public List remove(Object key) { if (key == null) { return null; @@ -274,7 +274,7 @@ public List remove(Object key) { /** * {@inheritDoc} */ - @Override + /* @Override */ public void clear() { values.clear(); } @@ -282,7 +282,7 @@ public void clear() { /** * {@inheritDoc} */ - @Override + /* @Override */ public Iterator>> iterator() { return Collections.unmodifiableSet(values.entrySet()).iterator(); } @@ -290,7 +290,7 @@ public Iterator>> iterator() { /** * {@inheritDoc} */ - @Override + /* @Override */ public Set keySet() { return Collections.unmodifiableSet(values.keySet()); } @@ -298,7 +298,7 @@ public Set keySet() { /** * {@inheritDoc} */ - @Override + /* @Override */ public Set>> entrySet() { return values.entrySet(); } @@ -306,7 +306,7 @@ public Set>> entrySet() { /** * {@inheritDoc} */ - @Override + /* @Override */ public int size() { return values.size(); } @@ -314,7 +314,7 @@ public int size() { /** * {@inheritDoc} */ - @Override + /* @Override */ public boolean isEmpty() { return values.isEmpty(); } @@ -322,7 +322,7 @@ public boolean isEmpty() { /** * {@inheritDoc} */ - @Override + /* @Override */ public boolean containsKey(Object key) { return key == null ? false : values.containsKey(key.toString()); } @@ -330,7 +330,7 @@ public boolean containsKey(Object key) { /** * {@inheritDoc} */ - @Override + /* @Override */ public boolean containsValue(Object value) { return values.containsValue(value); } @@ -383,7 +383,7 @@ public String getJoinedValue(String key, String delimiter) { /** * {@inheritDoc} */ - @Override + /* @Override */ public List get(Object key) { if (key == null) { return null; @@ -395,11 +395,25 @@ public List get(Object key) { /** * {@inheritDoc} */ - @Override + /* @Override */ public Collection> values() { return values.values(); } + public List toParams() { + if (values.isEmpty()) + return Collections.emptyList(); + else { + List params = new ArrayList(values.size()); + for (Map.Entry> entry : values.entrySet()) { + String name = entry.getKey(); + for (String value: entry.getValue()) + params.add(new Param(name, value)); + } + return params; + } + } + @Override public boolean equals(Object obj) { if (this == obj) { diff --git a/api/src/main/java/org/asynchttpclient/HttpResponseStatus.java b/api/src/main/java/org/asynchttpclient/HttpResponseStatus.java index d57a005441..ecf18cd980 100644 --- a/api/src/main/java/org/asynchttpclient/HttpResponseStatus.java +++ b/api/src/main/java/org/asynchttpclient/HttpResponseStatus.java @@ -16,28 +16,29 @@ */ package org.asynchttpclient; -import java.net.URI; import java.util.List; +import org.asynchttpclient.uri.UriComponents; + /** * A class that represent the HTTP response' status line (code + text) */ public abstract class HttpResponseStatus { - private final URI uri; + private final UriComponents uri; protected final AsyncHttpClientConfig config; - public HttpResponseStatus(URI uri, AsyncHttpClientConfig config) { + public HttpResponseStatus(UriComponents uri, AsyncHttpClientConfig config) { this.uri = uri; this.config = config; } /** - * Return the request {@link URI} + * Return the request {@link UriComponents} * - * @return the request {@link URI} + * @return the request {@link UriComponents} */ - public final URI getUri() { + public final UriComponents getUri() { return uri; } diff --git a/api/src/main/java/org/asynchttpclient/Param.java b/api/src/main/java/org/asynchttpclient/Param.java new file mode 100644 index 0000000000..788f66c425 --- /dev/null +++ b/api/src/main/java/org/asynchttpclient/Param.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2014 AsyncHttpClient Project. 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; + +/** + * A pair of (name, value) String + * @author slandelle + */ +public class Param { + + private final String name; + private final String value; + public Param(String name, String value) { + this.name = name; + this.value = value; + } + public String getName() { + return name; + } + public String getValue() { + return value; + } + + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof Param)) + return false; + Param other = (Param) obj; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (value == null) { + if (other.value != null) + return false; + } else if (!value.equals(other.value)) + return false; + return true; + } +} diff --git a/api/src/main/java/org/asynchttpclient/ProxyServerSelector.java b/api/src/main/java/org/asynchttpclient/ProxyServerSelector.java index b883b1fcf9..84862331f1 100644 --- a/api/src/main/java/org/asynchttpclient/ProxyServerSelector.java +++ b/api/src/main/java/org/asynchttpclient/ProxyServerSelector.java @@ -1,6 +1,6 @@ package org.asynchttpclient; -import java.net.URI; +import org.asynchttpclient.uri.UriComponents; /** * Selector for a proxy server @@ -13,14 +13,14 @@ public interface ProxyServerSelector { * @param uri The URI to select a proxy server for. * @return The proxy server to use, if any. May return null. */ - ProxyServer select(URI uri); + ProxyServer select(UriComponents uri); /** * A selector that always selects no proxy. */ static final ProxyServerSelector NO_PROXY_SELECTOR = new ProxyServerSelector() { @Override - public ProxyServer select(URI uri) { + public ProxyServer select(UriComponents uri) { return null; } }; diff --git a/api/src/main/java/org/asynchttpclient/Request.java b/api/src/main/java/org/asynchttpclient/Request.java index a54b96f32f..b182b2caf7 100644 --- a/api/src/main/java/org/asynchttpclient/Request.java +++ b/api/src/main/java/org/asynchttpclient/Request.java @@ -18,11 +18,11 @@ import org.asynchttpclient.cookie.Cookie; import org.asynchttpclient.multipart.Part; +import org.asynchttpclient.uri.UriComponents; import java.io.File; import java.io.InputStream; import java.net.InetAddress; -import java.net.URI; import java.util.Collection; import java.util.List; @@ -53,11 +53,7 @@ public interface Request { */ String getUrl(); - URI getOriginalURI(); - - URI getURI(); - - URI getRawURI(); + UriComponents getURI(); /** * Return the InetAddress to override @@ -68,13 +64,6 @@ public interface Request { InetAddress getLocalAddress(); - /** - * Return the undecoded url - * - * @return the undecoded url - */ - String getRawUrl(); - /** * Return the current set of Headers. * @@ -82,14 +71,6 @@ public interface Request { */ FluentCaseInsensitiveStringsMap getHeaders(); - /** - * @return return true if request headers have been added, - * otherwise, returns false. - * - * @since 2.0 - */ - boolean hasHeaders(); - /** * Return Coookie. * @@ -126,18 +107,18 @@ public interface Request { BodyGenerator getBodyGenerator(); /** - * Return the current size of the content-length header based on the body's size. + * Return the current size of the content-lenght header based on the body's size. * - * @return the current size of the content-length header based on the body's size. + * @return the current size of the content-lenght header based on the body's size. */ long getContentLength(); /** - * Return the current parameters. + * Return the current form parameters. * * @return a {@link FluentStringsMap} of parameters. */ - FluentStringsMap getParams(); + List getFormParams(); /** * Return the current {@link Part} @@ -158,7 +139,7 @@ public interface Request { * * @return {@link FluentStringsMap} of query string */ - FluentStringsMap getQueryParams(); + List getQueryParams(); /** * Return the {@link ProxyServer} @@ -182,23 +163,16 @@ public interface Request { File getFile(); /** - * Return the true> to follow redirect + * Return follow redirect * - * @return the true> to follow redirect + * @return the TRUE> to follow redirect, FALSE, if NOT to follow, whatever the client config. + * Return null if not set. */ - boolean isRedirectEnabled(); + Boolean getFollowRedirect(); /** - * - * @return true> if request's redirectEnabled setting - * should be used in place of client's - */ - boolean isRedirectOverrideSet(); - - /** - * Return the request time out in milliseconds. - * - * @return requestTimeoutInMs. + * Overrides the config default value + * @return the request timeout */ int getRequestTimeoutInMs(); @@ -216,8 +190,13 @@ public interface Request { */ String getBodyEncoding(); - boolean isUseRawUrl(); - ConnectionPoolKeyStrategy getConnectionPoolKeyStrategy(); + + /** + * @return return true if request headers have been added, + * otherwise, returns false. + * + * @since 2.0 + */ + boolean hasHeaders(); } - diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilder.java b/api/src/main/java/org/asynchttpclient/RequestBuilder.java index 57f888ff95..52fef5a98a 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilder.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilder.java @@ -15,13 +15,15 @@ */ package org.asynchttpclient; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.multipart.Part; - import java.io.InputStream; import java.util.Collection; +import java.util.List; import java.util.Map; +import org.asynchttpclient.cookie.Cookie; +import org.asynchttpclient.multipart.Part; +import org.asynchttpclient.util.QueryComputer; + /** * Builder for a {@link Request}. * Warning: mutable and not thread-safe! Beware that it holds a reference on the Request instance it builds, @@ -41,10 +43,18 @@ public RequestBuilder(String method, boolean useRawUrl) { super(RequestBuilder.class, method, useRawUrl); } + public RequestBuilder(String method, QueryComputer queryComputer) { + super(RequestBuilder.class, method, queryComputer); + } + public RequestBuilder(Request prototype) { super(RequestBuilder.class, prototype); } + public RequestBuilder(Request prototype, QueryComputer queryComputer) { + super(RequestBuilder.class, prototype, queryComputer); + } + // Note: For now we keep the delegates in place even though they are not needed // since otherwise Clojure (and maybe other languages) won't be able to // access these methods - see Clojure tickets 126 and 259 @@ -65,18 +75,28 @@ public RequestBuilder addHeader(String name, String value) { } @Override - public RequestBuilder addParameter(String key, String value) { - return super.addParameter(key, value); + public RequestBuilder addFormParam(String key, String value) { + return super.addFormParam(key, value); + } + + @Override + public RequestBuilder addQueryParam(String name, String value) { + return super.addQueryParam(name, value); + } + + @Override + public RequestBuilder addQueryParams(List queryParams) { + return super.addQueryParams(queryParams); } @Override - public RequestBuilder addQueryParameter(String name, String value) { - return super.addQueryParameter(name, value); + public RequestBuilder setQueryParams(List params) { + return super.setQueryParams(params); } @Override - public RequestBuilder setQueryParameters(FluentStringsMap parameters) { - return super.setQueryParameters(parameters); + public RequestBuilder setQueryParams(Map> params) { + return super.setQueryParams(params); } @Override @@ -89,11 +109,6 @@ public RequestBuilder setBody(byte[] data) { return super.setBody(data); } - /** - * Set a Stream for chunking - * @param stream - An {@link InputStream} - * @return a {@link RequestBuilder} - */ @Override public RequestBuilder setBody(InputStream stream) { return super.setBody(stream); @@ -120,13 +135,13 @@ public RequestBuilder setHeaders(Map> headers) { } @Override - public RequestBuilder setParameters(Map> parameters) { - return super.setParameters(parameters); + public RequestBuilder setFormParams(List params) { + return super.setFormParams(params); } @Override - public RequestBuilder setParameters(FluentStringsMap parameters) { - return super.setParameters(parameters); + public RequestBuilder setFormParams(Map> params) { + return super.setFormParams(params); } @Override diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java index 4a881f0fa5..cd38530ac2 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -19,25 +19,20 @@ import org.asynchttpclient.cookie.Cookie; import org.asynchttpclient.multipart.Part; +import org.asynchttpclient.uri.UriComponents; import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.asynchttpclient.util.StandardCharsets; -import org.asynchttpclient.util.UTF8UrlEncoder; +import org.asynchttpclient.util.QueryComputer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.net.InetAddress; -import java.net.URI; -import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; /** * Builder for {@link Request} @@ -47,26 +42,23 @@ public abstract class RequestBuilderBase> { private final static Logger logger = LoggerFactory.getLogger(RequestBuilderBase.class); - private static final URI DEFAULT_REQUEST_URL = URI.create("http://localhost/"); + private static final UriComponents DEFAULT_REQUEST_URL = UriComponents.create("http://localhost/"); private static final class RequestImpl implements Request { private String method; - private URI originalUri; - private URI uri; - private URI rawUri; + private UriComponents uri; private InetAddress address; private InetAddress localAddress; - private FluentCaseInsensitiveStringsMap headers; + private FluentCaseInsensitiveStringsMap headers = new FluentCaseInsensitiveStringsMap(); private ArrayList cookies; private byte[] byteData; private String stringData; private InputStream streamData; private BodyGenerator bodyGenerator; - private FluentStringsMap params; + private List formParams; private List parts; private String virtualHost; private long length = -1; - public FluentStringsMap queryParams; public ProxyServer proxyServer; private Realm realm; private File file; @@ -74,17 +66,16 @@ private static final class RequestImpl implements Request { private int requestTimeoutInMs; private long rangeOffset; public String charset; - private boolean useRawUrl; private ConnectionPoolKeyStrategy connectionPoolKeyStrategy = DefaultConnectionPoolStrategy.INSTANCE; + private List queryParams; - public RequestImpl(boolean useRawUrl) { - this.useRawUrl = useRawUrl; + public RequestImpl() { } public RequestImpl(Request prototype) { if (prototype != null) { this.method = prototype.getMethod(); - this.originalUri = prototype.getOriginalURI(); + this.uri = prototype.getURI(); this.address = prototype.getInetAddress(); this.localAddress = prototype.getLocalAddress(); this.headers = new FluentCaseInsensitiveStringsMap(prototype.getHeaders()); @@ -93,19 +84,17 @@ public RequestImpl(Request prototype) { this.stringData = prototype.getStringData(); this.streamData = prototype.getStreamData(); this.bodyGenerator = prototype.getBodyGenerator(); - this.params = (prototype.getParams() == null ? null : new FluentStringsMap(prototype.getParams())); - this.queryParams = (prototype.getQueryParams() == null ? null : new FluentStringsMap(prototype.getQueryParams())); - this.parts = (prototype.getParts() == null ? null : new ArrayList(prototype.getParts())); + this.formParams = prototype.getFormParams() == null ? null : new ArrayList(prototype.getFormParams()); + this.parts = prototype.getParts() == null ? null : new ArrayList(prototype.getParts()); this.virtualHost = prototype.getVirtualHost(); this.length = prototype.getContentLength(); this.proxyServer = prototype.getProxyServer(); this.realm = prototype.getRealm(); this.file = prototype.getFile(); - this.followRedirects = prototype.isRedirectOverrideSet() ? prototype.isRedirectEnabled() : null; + this.followRedirects = prototype.getFollowRedirect(); this.requestTimeoutInMs = prototype.getRequestTimeoutInMs(); this.rangeOffset = prototype.getRangeOffset(); this.charset = prototype.getBodyEncoding(); - this.useRawUrl = prototype.isUseRawUrl(); this.connectionPoolKeyStrategy = prototype.getConnectionPoolKeyStrategy(); } } @@ -125,8 +114,8 @@ public InetAddress getLocalAddress() { return localAddress; } - private String removeTrailingSlash(URI uri) { - String uriString = uri.toString(); + private String removeTrailingSlash(UriComponents uri) { + String uriString = uri.toUrl(); if (uriString.endsWith("/")) { return uriString.substring(0, uriString.length() - 1); } else { @@ -140,91 +129,15 @@ public String getUrl() { } @Override - public String getRawUrl() { - return removeTrailingSlash(getRawURI()); - } - - public URI getOriginalURI() { - return originalUri; - } - - public URI getURI() { - if (uri == null) - uri = toURI(true); + public UriComponents getURI() { return uri; } - public URI getRawURI() { - if (rawUri == null) - rawUri = toURI(false); - return rawUri; - } - - private URI toURI(boolean encode) { - - if (originalUri == null) { - logger.debug("setUrl hasn't been invoked. Using http://localhost"); - originalUri = DEFAULT_REQUEST_URL; - } - - AsyncHttpProviderUtils.validateSupportedScheme(originalUri); - - StringBuilder builder = new StringBuilder(); - builder.append(originalUri.getScheme()).append("://").append(originalUri.getRawAuthority()); - if (isNonEmpty(originalUri.getRawPath())) { - builder.append(originalUri.getRawPath()); - } else { - builder.append("/"); - } - - if (isNonEmpty(queryParams)) { - - builder.append("?"); - - for (Iterator>> i = queryParams.iterator(); i.hasNext();) { - Map.Entry> param = i.next(); - String name = param.getKey(); - for (Iterator j = param.getValue().iterator(); j.hasNext();) { - String value = j.next(); - if (encode) { - UTF8UrlEncoder.appendEncoded(builder, name); - } else { - builder.append(name); - } - if (value != null) { - builder.append('='); - if (encode) { - UTF8UrlEncoder.appendEncoded(builder, value); - } else { - builder.append(value); - } - } - if (j.hasNext()) { - builder.append('&'); - } - } - if (i.hasNext()) { - builder.append('&'); - } - } - } - - return URI.create(builder.toString()); - } - @Override public FluentCaseInsensitiveStringsMap getHeaders() { - if (headers == null) { - headers = new FluentCaseInsensitiveStringsMap(); - } return headers; } - @Override - public boolean hasHeaders() { - return headers != null && !headers.isEmpty(); - } - @Override public Collection getCookies() { return cookies != null ? Collections.unmodifiableCollection(cookies) : Collections. emptyList(); @@ -256,13 +169,13 @@ public long getContentLength() { } @Override - public FluentStringsMap getParams() { - return params; + public List getFormParams() { + return formParams != null ? formParams : Collections. emptyList(); } @Override public List getParts() { - return parts; + return parts != null ? parts : Collections. emptyList(); } @Override @@ -270,11 +183,6 @@ public String getVirtualHost() { return virtualHost; } - @Override - public FluentStringsMap getQueryParams() { - return queryParams; - } - @Override public ProxyServer getProxyServer() { return proxyServer; @@ -291,13 +199,8 @@ public File getFile() { } @Override - public boolean isRedirectEnabled() { - return followRedirects != null && followRedirects; - } - - @Override - public boolean isRedirectOverrideSet() { - return followRedirects != null; + public Boolean getFollowRedirect() { + return followRedirects; } @Override @@ -305,95 +208,108 @@ public int getRequestTimeoutInMs() { return requestTimeoutInMs; } + @Override public long getRangeOffset() { return rangeOffset; } + @Override public String getBodyEncoding() { return charset; } + @Override public ConnectionPoolKeyStrategy getConnectionPoolKeyStrategy() { return connectionPoolKeyStrategy; } + @Override + public boolean hasHeaders() { + return headers != null && !headers.isEmpty(); + } + + @Override + public List getQueryParams() { + if (queryParams == null) + // lazy load + if (isNonEmpty(uri.getQuery())) { + queryParams = new ArrayList(1); + for (String queryStringParam : uri.getQuery().split("&")) { + int pos = queryStringParam.indexOf('='); + if (pos <= 0) + queryParams.add(new Param(queryStringParam, null)); + else + queryParams.add(new Param(queryStringParam.substring(0, pos), queryStringParam.substring(pos + 1))); + } + } else + queryParams = Collections.emptyList(); + return queryParams; + } + @Override public String toString() { - StringBuilder sb = new StringBuilder(getURI().toString()); + StringBuilder sb = new StringBuilder(getURI().toUrl()); sb.append("\t"); sb.append(method); sb.append("\theaders:"); - final FluentCaseInsensitiveStringsMap headersLocal = getHeaders(); - if (headersLocal != null) { - for (String name : headersLocal.keySet()) { + if (isNonEmpty(headers)) { + for (String name : headers.keySet()) { sb.append("\t"); sb.append(name); sb.append(":"); - sb.append(headersLocal.getJoinedValue(name, ", ")); + sb.append(headers.getJoinedValue(name, ", ")); } } - sb.append("\tparams:"); - if (params != null) { - for (String name : params.keySet()) { + if (isNonEmpty(formParams)) { + sb.append("\tformParams:"); + for (Param param : formParams) { sb.append("\t"); - sb.append(name); + sb.append(param.getName()); sb.append(":"); - sb.append(params.getJoinedValue(name, ", ")); + sb.append(param.getValue()); } } return sb.toString(); } - - public boolean isUseRawUrl() { - return useRawUrl; - } } private final Class derived; protected final RequestImpl request; - protected boolean useRawUrl = false; - /** - * Calculator used for calculating request signature for the request being - * built, if any. - */ + protected QueryComputer queryComputer; + protected List queryParams; protected SignatureCalculator signatureCalculator; - /** - * URL used as the base, not including possibly query parameters. Needed for - * signature calculation - */ - protected String baseURL; + protected RequestBuilderBase(Class derived, String method, boolean disableUrlEncoding) { + this(derived, method, QueryComputer.queryComputer(disableUrlEncoding)); + } - protected RequestBuilderBase(Class derived, String method, boolean rawUrls) { + protected RequestBuilderBase(Class derived, String method, QueryComputer queryComputer) { this.derived = derived; - request = new RequestImpl(rawUrls); + request = new RequestImpl(); request.method = method; - this.useRawUrl = rawUrls; + this.queryComputer = queryComputer; } protected RequestBuilderBase(Class derived, Request prototype) { + this(derived, prototype, QueryComputer.URL_ENCODING_ENABLED_QUERY_COMPUTER); + } + + protected RequestBuilderBase(Class derived, Request prototype, QueryComputer queryComputer) { this.derived = derived; request = new RequestImpl(prototype); - this.useRawUrl = prototype.isUseRawUrl(); + this.queryComputer = queryComputer; } - + public T setUrl(String url) { - baseURL = url; - return setURI(URI.create(url)); + return setURI(UriComponents.create(url)); } - public T setURI(URI uri) { - if (uri.getHost() == null) - throw new NullPointerException("uri.host"); + public T setURI(UriComponents uri) { if (uri.getPath() == null) throw new NullPointerException("uri.path"); - - request.originalUri = uri; - addQueryParameters(request.originalUri); - request.uri = null; - request.rawUri = null; + request.uri = uri; return derived.cast(this); } @@ -407,42 +323,13 @@ public T setLocalInetAddress(InetAddress address) { return derived.cast(this); } - private void addQueryParameters(URI uri) { - if (isNonEmpty(uri.getRawQuery())) { - String[] queries = uri.getRawQuery().split("&"); - int pos; - for (String query : queries) { - pos = query.indexOf('='); - if (pos <= 0) { - addQueryParameter(query, null); - } else { - try { - if (useRawUrl) { - addQueryParameter(query.substring(0, pos), query.substring(pos + 1)); - } else { - addQueryParameter(URLDecoder.decode(query.substring(0, pos), StandardCharsets.UTF_8.name()), - URLDecoder.decode(query.substring(pos + 1), StandardCharsets.UTF_8.name())); - } - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - } - } - } - public T setVirtualHost(String virtualHost) { request.virtualHost = virtualHost; return derived.cast(this); } - public T setSignatureCalculator(SignatureCalculator signatureCalculator) { - this.signatureCalculator = signatureCalculator; - return derived.cast(this); - } - public T setHeader(String name, String value) { - request.getHeaders().replace(name, value); + request.headers.replace(name, value); return derived.cast(this); } @@ -452,19 +339,17 @@ public T addHeader(String name, String value) { value = ""; } - request.getHeaders().add(name, value); + request.headers.add(name, value); return derived.cast(this); } public T setHeaders(FluentCaseInsensitiveStringsMap headers) { - if (headers != null) - request.headers = new FluentCaseInsensitiveStringsMap(headers); + request.headers = (headers == null ? new FluentCaseInsensitiveStringsMap() : new FluentCaseInsensitiveStringsMap(headers)); return derived.cast(this); } public T setHeaders(Map> headers) { - if (headers != null) - request.headers = new FluentCaseInsensitiveStringsMap(headers); + request.headers = (headers == null ? new FluentCaseInsensitiveStringsMap() : new FluentCaseInsensitiveStringsMap(headers)); return derived.cast(this); } @@ -477,18 +362,18 @@ private void lazyInitCookies() { if (request.cookies == null) request.cookies = new ArrayList(3); } - + + public T setCookies(Collection cookies) { + request.cookies = new ArrayList(cookies); + return derived.cast(this); + } + public T addCookie(Cookie cookie) { lazyInitCookies(); request.cookies.add(cookie); return derived.cast(this); } - public void resetCookies() { - if (request.cookies != null) - request.cookies.clear(); - } - public T addOrReplaceCookie(Cookie cookie) { String cookieKey = cookie.getName(); boolean replace = false; @@ -509,13 +394,18 @@ public T addOrReplaceCookie(Cookie cookie) { return derived.cast(this); } + public void resetCookies() { + if (request.cookies != null) + request.cookies.clear(); + } - public void resetQueryParameters() { - request.queryParams = null; + public void resetQuery() { + queryParams = null; + request.uri = request.uri.withNewQuery(null); } - - public void resetParameters() { - request.params = null; + + public void resetFormParams() { + request.formParams = null; } public void resetNonMultipartData() { @@ -535,7 +425,7 @@ public T setBody(File file) { } public T setBody(byte[] data) { - resetParameters(); + resetFormParams(); resetNonMultipartData(); resetMultipartData(); request.byteData = data; @@ -543,7 +433,7 @@ public T setBody(byte[] data) { } public T setBody(String data) { - resetParameters(); + resetFormParams(); resetNonMultipartData(); resetMultipartData(); request.stringData = data; @@ -551,7 +441,7 @@ public T setBody(String data) { } public T setBody(InputStream stream) { - resetParameters(); + resetFormParams(); resetNonMultipartData(); resetMultipartData(); request.streamData = stream; @@ -563,43 +453,63 @@ public T setBody(BodyGenerator bodyGenerator) { return derived.cast(this); } - public T addQueryParameter(String name, String value) { - if (request.queryParams == null) - request.queryParams = new FluentStringsMap(); - request.queryParams.add(name, value); + public T addQueryParam(String name, String value) { + if (queryParams == null) { + queryParams = new ArrayList(1); + } + queryParams.add(new Param(name, value)); return derived.cast(this); } - public T setQueryParameters(FluentStringsMap parameters) { - request.queryParams = parameters != null? new FluentStringsMap(parameters) : null; + public T addQueryParams(List queryParams) { + for (Param queryParam: queryParams) + addQueryParam(queryParam.getName(), queryParam.getValue()); return derived.cast(this); } - public T addParameter(String key, String value) { - resetNonMultipartData(); - resetMultipartData(); - if (request.params == null) - request.params = new FluentStringsMap(); - request.params.add(key, value); - return derived.cast(this); + private List map2ParamList(Map> map) { + if (map == null) + return null; + + List params = new ArrayList(map.size()); + for (Map.Entry> entries : map.entrySet()) { + String name = entries.getKey(); + for (String value : entries.getValue()) + params.add(new Param(name, value)); + } + return params; + } + + public T setQueryParams(Map> map) { + return setQueryParams(map2ParamList(map)); } - public T setParameters(FluentStringsMap parameters) { + public T setQueryParams(List params) { + queryParams = params; + return derived.cast(this); + } + + public T addFormParam(String name, String value) { resetNonMultipartData(); resetMultipartData(); - request.params = new FluentStringsMap(parameters); + if (request.formParams == null) + request.formParams = new ArrayList(1); + request.formParams.add(new Param(name, value)); return derived.cast(this); } - public T setParameters(Map> parameters) { + public T setFormParams(Map> map) { + return setFormParams(map2ParamList(map)); + } + public T setFormParams(List params) { resetNonMultipartData(); resetMultipartData(); - request.params = new FluentStringsMap(parameters); + request.formParams = params; return derived.cast(this); } public T addBodyPart(Part part) { - resetParameters(); + resetFormParams(); resetNonMultipartData(); if (request.parts == null) request.parts = new ArrayList(); @@ -647,21 +557,20 @@ public T setConnectionPoolKeyStrategy(ConnectionPoolKeyStrategy connectionPoolKe return derived.cast(this); } + public T setSignatureCalculator(SignatureCalculator signatureCalculator) { + this.signatureCalculator = signatureCalculator; + return derived.cast(this); + } + private void executeSignatureCalculator() { /* Let's first calculate and inject signature, before finalizing actual build * (order does not matter with current implementation but may in future) */ if (signatureCalculator != null) { - String url = baseURL != null? baseURL : request.originalUri.toString(); - // Should not include query parameters, ensure: - int i = url.indexOf('?'); - if (i >= 0) { - url = url.substring(0, i); - } - signatureCalculator.calculateAndAddSignature(url, request, this); - } + signatureCalculator.calculateAndAddSignature(request, this); + } } - + private void computeRequestCharset() { if (request.charset == null) { try { @@ -679,29 +588,50 @@ private void computeRequestCharset() { } } } - - private void computeRequestContentLength() { + + private void computeRequestLength() { if (request.length < 0 && request.streamData == null) { // can't concatenate content-length - String contentLength = null; - if (request.headers != null && request.headers.isEmpty()) { - contentLength = request.headers.getFirstValue("Content-Length"); - } + final String contentLength = request.headers.getFirstValue("Content-Length"); if (contentLength != null) { try { request.length = Long.parseLong(contentLength); } catch (NumberFormatException e) { - // NoOp -- we won't specify length so it will be chunked? + // NoOp -- we wdn't specify length so it will be chunked? } } } } + private void computeFinalUri() { + + if (request.uri == null) { + logger.debug("setUrl hasn't been invoked. Using http://localhost"); + request.uri = DEFAULT_REQUEST_URL; + } + + AsyncHttpProviderUtils.validateSupportedScheme(request.uri); + + // FIXME is that right? + String newPath = isNonEmpty(request.uri.getPath()) ? request.uri.getPath() : "/"; + String newQuery = queryComputer.computeFullQueryString(request.uri.getQuery(), queryParams); + + request.uri = new UriComponents(// + request.uri.getScheme(),// + request.uri.getUserInfo(),// + request.uri.getHost(),// + request.uri.getPort(),// + newPath,// + newQuery); + } + public Request build() { + computeFinalUri(); executeSignatureCalculator(); computeRequestCharset(); - computeRequestContentLength(); + computeRequestLength(); return request; } } + diff --git a/api/src/main/java/org/asynchttpclient/Response.java b/api/src/main/java/org/asynchttpclient/Response.java index 998e883501..9c58ae1f49 100644 --- a/api/src/main/java/org/asynchttpclient/Response.java +++ b/api/src/main/java/org/asynchttpclient/Response.java @@ -17,11 +17,10 @@ package org.asynchttpclient; import org.asynchttpclient.cookie.Cookie; +import org.asynchttpclient.uri.UriComponents; 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.List; @@ -111,12 +110,11 @@ public interface Response { 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 UriComponents}. 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 + * @return the request {@link UriComponents}. */ - URI getUri() throws MalformedURLException; + UriComponents getUri(); /** * Return the content-type header value. diff --git a/api/src/main/java/org/asynchttpclient/SignatureCalculator.java b/api/src/main/java/org/asynchttpclient/SignatureCalculator.java index 2f907b9390..90339d8c59 100644 --- a/api/src/main/java/org/asynchttpclient/SignatureCalculator.java +++ b/api/src/main/java/org/asynchttpclient/SignatureCalculator.java @@ -35,7 +35,6 @@ public interface SignatureCalculator { * @param request Request that is being built; needed to access content to * be signed */ - void calculateAndAddSignature(String url, - Request request, + void calculateAndAddSignature(Request request, RequestBuilderBase requestBuilder); } diff --git a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java index b96d69a91f..3ea5d1e72c 100644 --- a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java +++ b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java @@ -26,6 +26,7 @@ import java.io.Closeable; import java.io.IOException; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; @@ -360,6 +361,11 @@ public enum ErrorDocumentBehaviour { OMIT; } + /** + * This interface contains possible configuration changes for a derived SimpleAsyncHttpClient. + * + * @see SimpleAsyncHttpClient#derive() + */ /** * This interface contains possible configuration changes for a derived SimpleAsyncHttpClient. * @@ -373,9 +379,9 @@ public interface DerivedBuilder { DerivedBuilder setUrl(String url); - DerivedBuilder setParameters(FluentStringsMap parameters); + DerivedBuilder setFormParams(List params); - DerivedBuilder setParameters(Map> parameters); + DerivedBuilder setFormParams(Map> params); DerivedBuilder setHeaders(Map> headers); @@ -383,9 +389,9 @@ public interface DerivedBuilder { DerivedBuilder setHeader(String name, String value); - DerivedBuilder addQueryParameter(String name, String value); + DerivedBuilder addQueryParam(String name, String value); - DerivedBuilder addParameter(String key, String value); + DerivedBuilder addFormParam(String key, String value); DerivedBuilder addHeader(String name, String value); @@ -443,13 +449,13 @@ public Builder addHeader(String name, String value) { return this; } - public Builder addParameter(String key, String value) { - requestBuilder.addParameter(key, value); + public Builder addFormParam(String key, String value) { + requestBuilder.addFormParam(key, value); return this; } - public Builder addQueryParameter(String name, String value) { - requestBuilder.addQueryParameter(name, value); + public Builder addQueryParam(String name, String value) { + requestBuilder.addQueryParam(name, value); return this; } @@ -468,13 +474,13 @@ public Builder setHeaders(Map> headers) { return this; } - public Builder setParameters(Map> parameters) { - requestBuilder.setParameters(parameters); + public Builder setFormParams(Map> parameters) { + requestBuilder.setFormParams(parameters); return this; } - public Builder setParameters(FluentStringsMap parameters) { - requestBuilder.setParameters(parameters); + public Builder setFormParams(List params) { + requestBuilder.setFormParams(params); return this; } diff --git a/api/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculator.java b/api/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculator.java index b5d75a4faa..5103a8be5b 100644 --- a/api/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculator.java +++ b/api/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculator.java @@ -16,10 +16,13 @@ */ package org.asynchttpclient.oauth; -import org.asynchttpclient.FluentStringsMap; +import static org.asynchttpclient.util.MiscUtil.isNonEmpty; + +import org.asynchttpclient.Param; import org.asynchttpclient.Request; import org.asynchttpclient.RequestBuilderBase; import org.asynchttpclient.SignatureCalculator; +import org.asynchttpclient.uri.UriComponents; import org.asynchttpclient.util.Base64; import org.asynchttpclient.util.StandardCharsets; import org.asynchttpclient.util.UTF8UrlEncoder; @@ -27,7 +30,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.Random; /** @@ -81,11 +83,10 @@ public OAuthSignatureCalculator(ConsumerKey consumerAuth, RequestToken userAuth) //@Override // silly 1.5; doesn't allow this for interfaces - public void calculateAndAddSignature(String baseURL, Request request, RequestBuilderBase requestBuilder) { - String method = request.getMethod(); // POST etc + public void calculateAndAddSignature(Request request, RequestBuilderBase requestBuilder) { String nonce = generateNonce(); long timestamp = System.currentTimeMillis() / 1000L; - String signature = calculateSignature(method, baseURL, timestamp, nonce, request.getParams(), request.getQueryParams()); + String signature = calculateSignature(request.getMethod(), request.getURI(), timestamp, nonce, request.getFormParams(), request.getQueryParams()); String headerValue = constructAuthHeader(signature, nonce, timestamp); requestBuilder.setHeader(HEADER_AUTHORIZATION, headerValue); } @@ -93,8 +94,8 @@ public void calculateAndAddSignature(String baseURL, Request request, RequestBui /** * Method for calculating OAuth signature using HMAC/SHA-1 method. */ - public String calculateSignature(String method, String baseURL, long oauthTimestamp, String nonce, FluentStringsMap formParams, - FluentStringsMap queryParams) { + public String calculateSignature(String method, UriComponents uri, long oauthTimestamp, String nonce, + List formParams, List queryParams) { StringBuilder signedText = new StringBuilder(100); signedText.append(method); // POST / GET etc (nothing to URL encode) signedText.append('&'); @@ -102,18 +103,23 @@ public String calculateSignature(String method, String baseURL, long oauthTimest /* 07-Oct-2010, tatu: URL may contain default port number; if so, need to extract * from base URL. */ - if (baseURL.startsWith("http:")) { - int i = baseURL.indexOf(":80/", 4); - if (i > 0) { - baseURL = baseURL.substring(0, i) + baseURL.substring(i + 3); - } - } else if (baseURL.startsWith("https:")) { - int i = baseURL.indexOf(":443/", 5); - if (i > 0) { - baseURL = baseURL.substring(0, i) + baseURL.substring(i + 4); - } - } - signedText.append(UTF8UrlEncoder.encode(baseURL)); + String scheme = uri.getScheme(); + int port = uri.getPort(); + if (scheme.equals("http")) + if (port == 80) + port = -1; + else if (scheme.equals("https")) + if (port == 443) + port = -1; + + StringBuilder sb = new StringBuilder().append(scheme).append("://").append(uri.getHost()); + if (port != -1) + sb.append(':').append(port); + if (isNonEmpty(uri.getPath())) + sb.append(uri.getPath()); + + String baseURL = sb.toString(); + UTF8UrlEncoder.appendEncoded(signedText, baseURL); /** * List of all query and form parameters added to this request; needed @@ -132,19 +138,13 @@ public String calculateSignature(String method, String baseURL, long oauthTimest allParameters.add(KEY_OAUTH_VERSION, OAUTH_VERSION_1_0); if (formParams != null) { - for (Map.Entry> entry : formParams) { - String key = entry.getKey(); - for (String value : entry.getValue()) { - allParameters.add(key, value); - } + for (Param param : formParams) { + allParameters.add(param.getName(), param.getValue()); } } if (queryParams != null) { - for (Map.Entry> entry : queryParams) { - String key = entry.getKey(); - for (String value : entry.getValue()) { - allParameters.add(key, value); - } + for (Param param : queryParams) { + allParameters.add(param.getName(), param.getValue()); } } String encodedParams = allParameters.sortAndConcat(); @@ -189,7 +189,7 @@ private synchronized String generateNonce() { random.nextBytes(nonceBuffer); // let's use base64 encoding over hex, slightly more compact than hex or decimals return Base64.encode(nonceBuffer); - // return String.valueOf(Math.abs(random.nextLong())); +// return String.valueOf(Math.abs(random.nextLong())); } /** @@ -265,17 +265,13 @@ public String toString() { @Override public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; Parameter parameter = (Parameter) o; - if (!key.equals(parameter.key)) - return false; - if (!value.equals(parameter.value)) - return false; + if (!key.equals(parameter.key)) return false; + if (!value.equals(parameter.value)) return false; return true; } diff --git a/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java b/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java index d2b4fc225f..66b39a2d4b 100644 --- a/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java +++ b/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java @@ -8,9 +8,9 @@ import org.asynchttpclient.HttpResponseStatus; import org.asynchttpclient.Response; import org.asynchttpclient.cookie.Cookie; +import org.asynchttpclient.uri.UriComponents; import org.asynchttpclient.util.AsyncHttpProviderUtils; -import java.net.URI; import java.util.Collections; import java.util.List; @@ -51,7 +51,7 @@ public final String getStatusText() { } @Override - public final URI getUri() { + public final UriComponents getUri() { return status.getUri(); } diff --git a/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java b/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java index 53101d590a..c7fd13d75a 100644 --- a/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java +++ b/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java @@ -103,7 +103,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.getUri().toURL().toString(); + url = status.getUri().toUrl(); } else { return AsyncHandler.STATE.ABORT; } diff --git a/api/src/main/java/org/asynchttpclient/uri/UriComponents.java b/api/src/main/java/org/asynchttpclient/uri/UriComponents.java new file mode 100644 index 0000000000..898557d6a5 --- /dev/null +++ b/api/src/main/java/org/asynchttpclient/uri/UriComponents.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2014 AsyncHttpClient Project. 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.uri; + +import java.net.URI; +import java.net.URISyntaxException; + +public class UriComponents { + + public static UriComponents create(String originalUrl) { + return create(null, originalUrl); + } + + public static UriComponents create(UriComponents context, final String originalUrl) { + UriComponentsParser parser = new UriComponentsParser(); + parser.parse(context, originalUrl); + + return new UriComponents(parser.scheme,// + parser.userInfo,// + parser.host,// + parser.port,// + parser.path,// + parser.query); + } + + private final String scheme; + private final String userInfo; + private final String host; + private final int port; + private final String query; + private final String path; + + public UriComponents(String scheme,// + String userInfo,// + String host,// + int port,// + String path,// + String query) { + + if (scheme == null) + throw new NullPointerException("scheme"); + if (host == null) + throw new NullPointerException("host"); + + this.scheme = scheme; + this.userInfo = userInfo; + this.host = host; + this.port = port; + this.path = path; + this.query = query; + } + + public String getQuery() { + return query; + } + + public String getPath() { + return path; + } + + public String getUserInfo() { + return userInfo; + } + + public int getPort() { + return port; + } + + public String getScheme() { + return scheme; + } + + public String getHost() { + return host; + } + + public URI toURI() throws URISyntaxException { + return new URI(toUrl()); + } + + public String toUrl() { + StringBuilder sb = new StringBuilder(); + sb.append(scheme).append("://"); + if (userInfo != null) + sb.append(userInfo).append('@'); + sb.append(host); + if (port != -1) + sb.append(':').append(port); + if (path != null) + sb.append(path); + if (query != null) + sb.append('?').append(query); + + return sb.toString(); + } + + @Override + public String toString() { + // for now, but might change + return toUrl(); + } + + public UriComponents withNewScheme(String newScheme) { + return new UriComponents(newScheme,// + userInfo,// + host,// + port,// + path,// + query); + } + + public UriComponents withNewQuery(String newQuery) { + return new UriComponents(scheme,// + userInfo,// + host,// + port,// + path,// + newQuery); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((host == null) ? 0 : host.hashCode()); + result = prime * result + ((path == null) ? 0 : path.hashCode()); + result = prime * result + port; + result = prime * result + ((query == null) ? 0 : query.hashCode()); + result = prime * result + ((scheme == null) ? 0 : scheme.hashCode()); + result = prime * result + ((userInfo == null) ? 0 : userInfo.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + UriComponents other = (UriComponents) obj; + if (host == null) { + if (other.host != null) + return false; + } else if (!host.equals(other.host)) + return false; + if (path == null) { + if (other.path != null) + return false; + } else if (!path.equals(other.path)) + return false; + if (port != other.port) + return false; + if (query == null) { + if (other.query != null) + return false; + } else if (!query.equals(other.query)) + return false; + if (scheme == null) { + if (other.scheme != null) + return false; + } else if (!scheme.equals(other.scheme)) + return false; + if (userInfo == null) { + if (other.userInfo != null) + return false; + } else if (!userInfo.equals(other.userInfo)) + return false; + return true; + } +} diff --git a/api/src/main/java/org/asynchttpclient/uri/UriComponentsParser.java b/api/src/main/java/org/asynchttpclient/uri/UriComponentsParser.java new file mode 100644 index 0000000000..b6ee9c9f1f --- /dev/null +++ b/api/src/main/java/org/asynchttpclient/uri/UriComponentsParser.java @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2014 AsyncHttpClient Project. 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.uri; + +final class UriComponentsParser { + + public String scheme; + public String host; + public int port = -1; + public String query; + public String authority; + public String path; + public String userInfo; + + private int start, end = 0; + private String urlWithoutQuery; + + private void trimRight(String originalUrl) { + end = originalUrl.length(); + while (end > 0 && originalUrl.charAt(end - 1) <= ' ') + end--; + } + + private void trimLeft(String originalUrl) { + while (start < end && originalUrl.charAt(start) <= ' ') + start++; + + if (originalUrl.regionMatches(true, start, "url:", 0, 4)) + start += 4; + } + + private boolean isFragmentOnly(String originalUrl) { + return start < originalUrl.length() && originalUrl.charAt(start) == '#'; + } + + private boolean isValidProtocolChar(char c) { + return Character.isLetterOrDigit(c) && c != '.' && c != '+' && c != '-'; + } + + private boolean isValidProtocolChars(String protocol) { + for (int i = 1; i < protocol.length(); i++) { + if (!isValidProtocolChar(protocol.charAt(i))) + return false; + } + return true; + } + + private boolean isValidProtocol(String protocol) { + return protocol.length() > 0 && Character.isLetter(protocol.charAt(0)) && isValidProtocolChars(protocol); + } + + private void computeInitialScheme(String originalUrl) { + for (int i = start; i < end; i++) { + char c = originalUrl.charAt(i); + if (c == ':') { + String s = originalUrl.substring(start, i); + if (isValidProtocol(s)) { + scheme = s.toLowerCase(); + start = i + 1; + } + break; + } else if (c == '/') + break; + } + } + + private boolean overrideWithContext(UriComponents context, String originalUrl) { + + boolean isRelative = false; + + // only use context if the schemes match + if (context != null && (scheme == null || scheme.equalsIgnoreCase(context.getScheme()))) { + + // see RFC2396 5.2.3 + String contextPath = context.getPath(); + if (isNotEmpty(contextPath) && contextPath.charAt(0) == '/') + scheme = null; + + if (scheme == null) { + scheme = context.getScheme(); + userInfo = context.getUserInfo(); + host = context.getHost(); + port = context.getPort(); + path = contextPath; + isRelative = true; + } + } + return isRelative; + } + + private void computeFragment(String originalUrl) { + int charpPosition = originalUrl.indexOf('#', start); + if (charpPosition >= 0) { + end = charpPosition; + } + } + + private void inheritContextQuery(UriComponents context, boolean isRelative) { + // see RFC2396 5.2.2: query and fragment inheritance + if (isRelative && start == end) { + query = context.getQuery(); + } + } + + private boolean splitUrlAndQuery(String originalUrl) { + boolean queryOnly = false; + urlWithoutQuery = originalUrl; + if (start < end) { + int askPosition = originalUrl.indexOf('?'); + queryOnly = askPosition == start; + if (askPosition != -1 && askPosition < end) { + query = originalUrl.substring(askPosition + 1, end); + if (end > askPosition) + end = askPosition; + urlWithoutQuery = originalUrl.substring(0, askPosition); + } + } + + return queryOnly; + } + + private boolean currentPositionStartsWith4Slashes() { + return urlWithoutQuery.regionMatches(start, "////", 0, 4); + } + + private boolean currentPositionStartsWith2Slashes() { + return urlWithoutQuery.regionMatches(start, "//", 0, 2); + } + + private void computeAuthority() { + int authorityEndPosition = urlWithoutQuery.indexOf('/', start); + if (authorityEndPosition < 0) { + authorityEndPosition = urlWithoutQuery.indexOf('?', start); + if (authorityEndPosition < 0) + authorityEndPosition = end; + } + host = authority = urlWithoutQuery.substring(start, authorityEndPosition); + start = authorityEndPosition; + } + + private void computeUserInfo() { + int atPosition = authority.indexOf('@'); + if (atPosition != -1) { + userInfo = authority.substring(0, atPosition); + host = authority.substring(atPosition + 1); + } else + userInfo = null; + } + + private boolean isMaybeIPV6() { + // If the host is surrounded by [ and ] then its an IPv6 + // literal address as specified in RFC2732 + return host.length() > 0 && host.charAt(0) == '['; + } + + private void computeIPV6() { + int positionAfterClosingSquareBrace = host.indexOf(']') + 1; + if (positionAfterClosingSquareBrace > 1) { + + port = -1; + + if (host.length() > positionAfterClosingSquareBrace) { + if (host.charAt(positionAfterClosingSquareBrace) == ':') { + // see RFC2396: port can be null + int portPosition = positionAfterClosingSquareBrace + 1; + if (host.length() > portPosition) { + port = Integer.parseInt(host.substring(portPosition)); + } + } else + throw new IllegalArgumentException("Invalid authority field: " + authority); + } + + host = host.substring(0, positionAfterClosingSquareBrace); + + } else + throw new IllegalArgumentException("Invalid authority field: " + authority); + } + + private void computeRegularHostPort() { + int colonPosition = host.indexOf(':'); + port = -1; + if (colonPosition >= 0) { + // see RFC2396: port can be null + int portPosition = colonPosition + 1; + if (host.length() > portPosition) + port = Integer.parseInt(host.substring(portPosition)); + host = host.substring(0, colonPosition); + } + } + + // /./ + private void removeEmbeddedDot() { + path = path.replace("/./", ""); + } + + // /../ + private void removeEmbedded2Dots() { + int i = 0; + while ((i = path.indexOf("/../", i)) >= 0) { + if (i > 0) { + end = path.lastIndexOf('/', i - 1); + if (end >= 0 && path.indexOf("/../", end) != 0) { + path = path.substring(0, end) + path.substring(i + 3); + i = 0; + } + } else + i = i + 3; + } + } + + private void removeTailing2Dots() { + while (path.endsWith("/..")) { + end = path.lastIndexOf('/', path.length() - 4); + if (end >= 0) + path = path.substring(0, end + 1); + else + break; + } + } + + private void removeStartingDot() { + if (path.startsWith("./") && path.length() > 2) + path = path.substring(2); + } + + private void removeTrailingDot() { + if (path.endsWith("/.")) + path = path.substring(0, path.length() - 1); + } + + private void initRelativePath() { + int lastSlashPosition = path.lastIndexOf('/'); + String pathEnd = urlWithoutQuery.substring(start, end); + + if (lastSlashPosition == -1) + path = authority != null ? "/" + pathEnd : pathEnd; + else + path = path.substring(0, lastSlashPosition + 1) + pathEnd; + } + + private void handlePathDots() { + if (path.indexOf('.') != -1) { + removeEmbeddedDot(); + removeEmbedded2Dots(); + removeTailing2Dots(); + removeStartingDot(); + removeTrailingDot(); + } + } + + private void parseAuthority() { + if (!currentPositionStartsWith4Slashes() && currentPositionStartsWith2Slashes()) { + start += 2; + + computeAuthority(); + computeUserInfo(); + + if (host != null) { + if (isMaybeIPV6()) + computeIPV6(); + else + computeRegularHostPort(); + } + + if (port < -1) + throw new IllegalArgumentException("Invalid port number :" + port); + + // see RFC2396 5.2.4: ignore context path if authority is defined + if (isNotEmpty(authority)) + path = ""; + } + } + + private void handleRelativePath() { + initRelativePath(); + handlePathDots(); + } + + private void computeRegularPath() { + if (urlWithoutQuery.charAt(start) == '/') + path = urlWithoutQuery.substring(start, end); + + else if (isNotEmpty(path)) + handleRelativePath(); + + else { + String pathEnd = urlWithoutQuery.substring(start, end); + path = authority != null ? "/" + pathEnd : pathEnd; + } + } + + private void computeQueryOnlyPath() { + int lastSlashPosition = path.lastIndexOf('/'); + path = lastSlashPosition < 0 ? "/" : path.substring(0, lastSlashPosition) + "/"; + } + + private void computePath(boolean queryOnly) { + // Parse the file path if any + if (start < end) + computeRegularPath(); + else if (queryOnly && path != null) + computeQueryOnlyPath(); + else if (path == null) + path = ""; + } + + public void parse(UriComponents context, final String originalUrl) { + + if (originalUrl == null) + throw new NullPointerException("originalUrl"); + + boolean isRelative = false; + + trimRight(originalUrl); + trimLeft(originalUrl); + if (!isFragmentOnly(originalUrl)) + computeInitialScheme(originalUrl); + overrideWithContext(context, originalUrl); + computeFragment(originalUrl); + inheritContextQuery(context, isRelative); + + boolean queryOnly = splitUrlAndQuery(originalUrl); + parseAuthority(); + computePath(queryOnly); + } + + private static boolean isNotEmpty(String string) { + return string != null && string.length() > 0; + } +} \ 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 1f614a507d..3e95e67ea6 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java @@ -12,15 +12,16 @@ */ package org.asynchttpclient.util; +import static org.asynchttpclient.util.MiscUtil.isNonEmpty; + import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.AsyncHttpProvider; import org.asynchttpclient.HttpResponseBodyPart; import org.asynchttpclient.Request; +import org.asynchttpclient.uri.UriComponents; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; import java.nio.charset.Charset; import java.util.List; @@ -39,7 +40,7 @@ public class AsyncHttpProviderUtils { public final static Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1; - public static final void validateSupportedScheme(URI uri) { + public static final void validateSupportedScheme(UriComponents uri) { final String scheme = uri.getScheme(); if (scheme == null || !scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https") && !scheme.equalsIgnoreCase("ws") && !scheme.equalsIgnoreCase("wss")) { @@ -48,36 +49,22 @@ public static final void validateSupportedScheme(URI uri) { } } - public final static URI createNonEmptyPathURI(String u) { - URI uri = URI.create(u); + public final static UriComponents createNonEmptyPathURI(String u) { + UriComponents uri = UriComponents.create(u); validateSupportedScheme(uri); String path = uri.getPath(); if (path == null) { - throw new IllegalArgumentException("The URI path, of the URI " + uri + ", must be non-null"); - } else if (path.length() > 0 && path.charAt(0) != '/') { - throw new IllegalArgumentException("The URI path, of the URI " + uri + ". must start with a '/'"); - } else if (path.length() == 0) { - return URI.create(u + "/"); + throw new IllegalArgumentException("The URI path, of the URI " + uri + ", must be non-null"); + } else if (isNonEmpty(path) && path.charAt(0) != '/') { + throw new IllegalArgumentException("The URI path, of the URI " + uri + ". must start with a '/'"); + } else if (!isNonEmpty(path)) { + return UriComponents.create(u + "/"); } return uri; } - public final static String getBaseUrl(URI uri) { - return uri.getScheme() + "://" + getAuthority(uri); - } - - public final static String getAuthority(URI uri) { - String url = uri.getAuthority(); - int port = uri.getPort(); - if (port == -1) { - port = getPort(uri); - url += ":" + port; - } - return url; - } - /** * @param bodyParts NON EMPTY body part * @param maxLen @@ -117,60 +104,16 @@ public final static byte[] contentToBytes(List bodyParts, return result; } - public final static String getHost(URI uri) { - String host = uri.getHost(); - if (host == null) { - host = uri.getAuthority(); - } - return host; + public final static String getBaseUrl(UriComponents uri) { + return uri.getScheme() + "://" + getAuthority(uri); } - public final static URI getRedirectUri(URI uri, String location) { - if (location == null) - throw new IllegalArgumentException("URI " + uri + " was redirected to null location"); - - URI locationURI = null; - try { - locationURI = new URI(location); - } catch (URISyntaxException e) { - // rich, we have a badly encoded location, let's try to encode the query params - String[] parts = location.split("\\?"); - if (parts.length != 2) { - throw new IllegalArgumentException("Don't know how to turn this location into a proper URI:" + location, e); - } else { - StringBuilder properUrl = new StringBuilder(location.length()).append(parts[0]).append("?"); - - String[] queryParams = parts[1].split("&"); - for (int i = 0; i < queryParams.length; i++) { - String queryParam = queryParams[i]; - if (i != 0) - properUrl.append("&"); - String[] nameValue = queryParam.split("=", 2); - UTF8UrlEncoder.appendEncoded(properUrl, nameValue[0]); - if (nameValue.length == 2) { - properUrl.append("="); - UTF8UrlEncoder.appendEncoded(properUrl, nameValue[1]); - } - } - - locationURI = URI.create(properUrl.toString()); - } - } - - URI redirectUri = uri.resolve(locationURI); - - String scheme = redirectUri.getScheme(); - - if (scheme == null || !scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https") && !scheme.equals("ws") - && !scheme.equals("wss")) { - throw new IllegalArgumentException("The URI scheme, of the URI " + redirectUri - + ", must be equal (ignoring case) to 'ws, 'wss', 'http', or 'https'"); - } - - return redirectUri.normalize(); + public final static String getAuthority(UriComponents uri) { + int port = uri.getPort() != -1? uri.getPort() : getDefaultPort(uri); + return uri.getHost() + ":" + port; } - public final static int getPort(URI uri) { + public final static int getDefaultPort(UriComponents uri) { int port = uri.getPort(); if (port == -1) port = uri.getScheme().equals("http") || uri.getScheme().equals("ws") ? 80 : 443; @@ -208,4 +151,8 @@ public static String keepAliveHeaderValue(AsyncHttpClientConfig config) { public static int requestTimeout(AsyncHttpClientConfig config, Request request) { return request.getRequestTimeoutInMs() != 0 ? request.getRequestTimeoutInMs() : config.getRequestTimeoutInMs(); } + + public static boolean followRedirect(AsyncHttpClientConfig config, Request request) { + return request.getFollowRedirect() != null? request.getFollowRedirect().booleanValue() : config.isFollowRedirect(); + } } diff --git a/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java b/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java index 1b183dd3e8..16fd18207f 100644 --- a/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java @@ -19,6 +19,7 @@ import org.asynchttpclient.ProxyServer.Protocol; import org.asynchttpclient.ProxyServerSelector; import org.asynchttpclient.Request; +import org.asynchttpclient.uri.UriComponents; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,8 +27,8 @@ import java.net.Proxy; import java.net.ProxySelector; import java.net.URI; +import java.net.URISyntaxException; import java.util.List; -import java.util.Locale; import java.util.Properties; /** @@ -84,17 +85,17 @@ public static ProxyServer getProxyServer(AsyncHttpClientConfig config, Request r if (proxyServer == null) { ProxyServerSelector selector = config.getProxyServerSelector(); if (selector != null) { - proxyServer = selector.select(request.getOriginalURI()); + proxyServer = selector.select(request.getURI()); } } return ProxyUtils.avoidProxy(proxyServer, request) ? null : proxyServer; } - + /** * @see #avoidProxy(ProxyServer, String) */ public static boolean avoidProxy(final ProxyServer proxyServer, final Request request) { - return avoidProxy(proxyServer, AsyncHttpProviderUtils.getHost(request.getOriginalURI())); + return avoidProxy(proxyServer, request.getURI().getHost()); } private static boolean matchNonProxyHost(String targetHost, String nonProxyHost) { @@ -199,30 +200,36 @@ public static ProxyServerSelector getJdkDefaultProxyServerSelector() { */ public static ProxyServerSelector createProxyServerSelector(final ProxySelector proxySelector) { return new ProxyServerSelector() { - @Override - public ProxyServer select(URI uri) { - List proxies = proxySelector.select(uri); - if (proxies != null) { - // Loop through them until we find one that we know how to use - for (Proxy proxy : proxies) { - switch (proxy.type()) { - case HTTP: - if (!(proxy.address() instanceof InetSocketAddress)) { - log.warn("Don't know how to connect to address " + proxy.address()); + public ProxyServer select(UriComponents uri) { + try { + URI javaUri = uri.toURI(); + + List proxies = proxySelector.select(javaUri); + if (proxies != null) { + // Loop through them until we find one that we know how to use + for (Proxy proxy : proxies) { + switch (proxy.type()) { + case HTTP: + if (!(proxy.address() instanceof InetSocketAddress)) { + log.warn("Don't know how to connect to address " + proxy.address()); + return null; + } else { + InetSocketAddress address = (InetSocketAddress) proxy.address(); + return new ProxyServer(Protocol.HTTP, address.getHostName(), address.getPort()); + } + case DIRECT: return null; - } else { - InetSocketAddress address = (InetSocketAddress) proxy.address(); - return new ProxyServer(Protocol.HTTP, address.getHostName(), address.getPort()); + default: + log.warn("ProxySelector returned proxy type that we don't know how to use: " + proxy.type()); + break; } - case DIRECT: - return null; - default: - log.warn("ProxySelector returned proxy type that we don't know how to use: " + proxy.type()); - break; } } + return null; + } catch (URISyntaxException e) { + log.warn(uri + " couldn't be turned into a java.net.URI", e); + return null; } - return null; } }; } @@ -235,8 +242,7 @@ public ProxyServer select(URI uri) { */ public static ProxyServerSelector createProxyServerSelector(final ProxyServer proxyServer) { return new ProxyServerSelector() { - @Override - public ProxyServer select(URI uri) { + public ProxyServer select(UriComponents uri) { return proxyServer; } }; diff --git a/api/src/main/java/org/asynchttpclient/util/QueryComputer.java b/api/src/main/java/org/asynchttpclient/util/QueryComputer.java new file mode 100644 index 0000000000..058c070c0e --- /dev/null +++ b/api/src/main/java/org/asynchttpclient/util/QueryComputer.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2014 AsyncHttpClient Project. 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.util; + +import java.util.List; + +import static org.asynchttpclient.util.MiscUtil.isNonEmpty; + +import org.asynchttpclient.Param; + +public enum QueryComputer { + + URL_ENCODING_ENABLED_QUERY_COMPUTER { + + private final void encodeAndAppendQueryParam(final StringBuilder sb, final CharSequence name, final CharSequence value) { + UTF8UrlEncoder.appendEncoded(sb, name); + if (value != null) { + sb.append('='); + UTF8UrlEncoder.appendEncoded(sb, value); + } + sb.append('&'); + } + + private final void encodeAndAppendQueryParams(final StringBuilder sb, final List queryParams) { + for (Param param : queryParams) + encodeAndAppendQueryParam(sb, param.getName(), param.getValue()); + } + + // FIXME this could be improved: remove split + private final void encodeAndAppendQuery(final StringBuilder sb, final String query) { + int pos; + for (String queryParamString : query.split("&")) { + pos = queryParamString.indexOf('='); + if (pos <= 0) { + CharSequence decodedName = UTF8UrlDecoder.decode(queryParamString); + encodeAndAppendQueryParam(sb, decodedName, null); + } else { + CharSequence decodedName = UTF8UrlDecoder.decode(queryParamString, 0, pos); + int valueStart = pos + 1; + CharSequence decodedValue = UTF8UrlDecoder.decode(queryParamString, valueStart, queryParamString.length() - valueStart); + encodeAndAppendQueryParam(sb, decodedName, decodedValue); + } + } + } + + protected final String withQueryWithParams(final String query, final List queryParams) { + // concatenate encoded query + encoded query params + StringBuilder sb = new StringBuilder(query.length() + queryParams.size() * 16); + encodeAndAppendQuery(sb, query); + encodeAndAppendQueryParams(sb, queryParams); + sb.setLength(sb.length() - 1); + return sb.toString(); + } + + protected final String withQueryWithoutParams(final String query) { + // encode query + StringBuilder sb = new StringBuilder(query.length() + 6); + encodeAndAppendQuery(sb, query); + sb.setLength(sb.length() - 1); + return sb.toString(); + } + + protected final String withoutQueryWithParams(final List queryParams) { + // concatenate encoded query params + StringBuilder sb = new StringBuilder(queryParams.size() * 16); + encodeAndAppendQueryParams(sb, queryParams); + sb.setLength(sb.length() - 1); + return sb.toString(); + } + }, // + + URL_ENCODING_DISABLED_QUERY_COMPUTER { + + private final void appendRawQueryParam(StringBuilder sb, String name, String value) { + sb.append(name); + if (value != null) + sb.append('=').append(value); + sb.append('&'); + } + + private final void appendRawQueryParams(final StringBuilder sb, final List queryParams) { + for (Param param : queryParams) + appendRawQueryParam(sb, param.getName(), param.getValue()); + } + + protected final String withQueryWithParams(final String query, final List queryParams) { + // concatenate raw query + raw query params + StringBuilder sb = new StringBuilder(query.length() + queryParams.size() * 16); + sb.append(query); + appendRawQueryParams(sb, queryParams); + sb.setLength(sb.length() - 1); + return sb.toString(); + } + + protected final String withQueryWithoutParams(final String query) { + // return raw query as is + return query; + } + + protected final String withoutQueryWithParams(final List queryParams) { + // concatenate raw queryParams + StringBuilder sb = new StringBuilder(queryParams.size() * 16); + appendRawQueryParams(sb, queryParams); + sb.setLength(sb.length() - 1); + return sb.toString(); + } + }; + + public static QueryComputer queryComputer(boolean disableUrlEncoding) { + return disableUrlEncoding ? URL_ENCODING_DISABLED_QUERY_COMPUTER : URL_ENCODING_ENABLED_QUERY_COMPUTER; + } + + protected abstract String withQueryWithParams(final String query, final List queryParams); + + protected abstract String withQueryWithoutParams(final String query); + + protected abstract String withoutQueryWithParams(final List queryParams); + + private final String withQuery(final String query, final List queryParams) { + return isNonEmpty(queryParams) ? withQueryWithParams(query, queryParams) : withQueryWithoutParams(query); + } + + private final String withoutQuery(final List queryParams) { + return isNonEmpty(queryParams) ? withoutQueryWithParams(queryParams) : null; + } + + public final String computeFullQueryString(final String query, final List queryParams) { + return isNonEmpty(query) ? withQuery(query, queryParams) : withoutQuery(queryParams); + } +} diff --git a/api/src/main/java/org/asynchttpclient/util/StringCharSequence.java b/api/src/main/java/org/asynchttpclient/util/StringCharSequence.java new file mode 100644 index 0000000000..a1cf2192f2 --- /dev/null +++ b/api/src/main/java/org/asynchttpclient/util/StringCharSequence.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2014 AsyncHttpClient Project. 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.util; + +/** + * A CharSequence String wrapper that doesn't copy the char[] (damn new String implementation!!!) + * + * @author slandelle + */ +public class StringCharSequence implements CharSequence { + + private final String value; + private final int offset; + public final int length; + + public StringCharSequence(String value, int offset, int length) { + this.value = value; + this.offset = offset; + this.length = length; + } + + @Override + public int length() { + return length; + } + + @Override + public char charAt(int index) { + return value.charAt(offset + index); + } + + @Override + public CharSequence subSequence(int start, int end) { + int offsetedEnd = offset + end; + if (offsetedEnd < length) + throw new ArrayIndexOutOfBoundsException(); + return new StringCharSequence(value, offset + start, end - start); + } + + @Override + public String toString() { + return value.substring(offset, length); + } +} diff --git a/api/src/main/java/org/asynchttpclient/util/UTF8UrlDecoder.java b/api/src/main/java/org/asynchttpclient/util/UTF8UrlDecoder.java new file mode 100644 index 0000000000..7184aea114 --- /dev/null +++ b/api/src/main/java/org/asynchttpclient/util/UTF8UrlDecoder.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2014 AsyncHttpClient Project. 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.util; + +public final class UTF8UrlDecoder { + + private UTF8UrlDecoder() { + } + + private static StringBuilder initSb(StringBuilder sb, String s, int i, int offset, int length) { + if (sb != null) { + return sb; + } else { + int initialSbLength = length > 500 ? length / 2 : length; + return new StringBuilder(initialSbLength).append(s, offset, i); + } + } + + private static int hexaDigit(char c) { + return Character.digit(c, 16); + } + + public static CharSequence decode(String s) { + return decode(s, 0, s.length()); + } + + public static CharSequence decode(final String s, final int offset, final int length) { + + StringBuilder sb = null; + int i = offset; + int end = length + offset; + + while (i < end) { + char c = s.charAt(i); + if (c == '+') { + sb = initSb(sb, s, i, offset, length); + sb.append(' '); + i++; + + } else if (c == '%') { + if (end - i < 3) // We expect 3 chars. 0 based i vs. 1 based length! + throw new IllegalArgumentException("UTF8UrlDecoder: Incomplete trailing escape (%) pattern"); + + int x, y; + if ((x = hexaDigit(s.charAt(i + 1))) == -1 || (y = hexaDigit(s.charAt(i + 2))) == -1) + throw new IllegalArgumentException("UTF8UrlDecoder: Malformed"); + + sb = initSb(sb, s, i, offset, length); + sb.append((char) (x * 16 + y)); + i += 3; + } else { + if (sb != null) + sb.append(c); + i++; + } + } + + return sb != null ? sb.toString() : new StringCharSequence(s, offset, length); + } +} diff --git a/api/src/main/java/org/asynchttpclient/util/UTF8UrlEncoder.java b/api/src/main/java/org/asynchttpclient/util/UTF8UrlEncoder.java index 765a67f368..78cb74d9e0 100644 --- a/api/src/main/java/org/asynchttpclient/util/UTF8UrlEncoder.java +++ b/api/src/main/java/org/asynchttpclient/util/UTF8UrlEncoder.java @@ -26,25 +26,25 @@ public class UTF8UrlEncoder { * Encoding table used for figuring out ascii characters that must be escaped * (all non-Ascii characters need to be encoded anyway) */ - private final static int[] SAFE_ASCII = new int[128]; + private final static boolean[] SAFE_ASCII = new boolean[128]; static { for (int i = 'a'; i <= 'z'; ++i) { - SAFE_ASCII[i] = 1; + SAFE_ASCII[i] = true; } for (int i = 'A'; i <= 'Z'; ++i) { - SAFE_ASCII[i] = 1; + SAFE_ASCII[i] = true; } for (int i = '0'; i <= '9'; ++i) { - SAFE_ASCII[i] = 1; + SAFE_ASCII[i] = true; } - SAFE_ASCII['-'] = 1; - SAFE_ASCII['.'] = 1; - SAFE_ASCII['_'] = 1; - SAFE_ASCII['~'] = 1; + SAFE_ASCII['-'] = true; + SAFE_ASCII['.'] = true; + SAFE_ASCII['_'] = true; + SAFE_ASCII['~'] = true; } - private final static char[] HEX = "0123456789ABCDEF".toCharArray(); + private static final char[] HEX = "0123456789ABCDEF".toCharArray(); private UTF8UrlEncoder() { } @@ -55,25 +55,22 @@ public static String encode(String input) { return sb.toString(); } - public static StringBuilder appendEncoded(StringBuilder sb, String input) { - final int[] safe = SAFE_ASCII; - - for (int c, i = 0, len = input.length(); i < len; i += Character.charCount(c)) { - c = input.codePointAt(i); - if (c <= 127) { - if (safe[c] != 0) { + public static StringBuilder appendEncoded(StringBuilder sb, CharSequence input) { + int c; + for (int i = 0; i < input.length(); i+= Character.charCount(c)) { + c = Character.codePointAt(input, i); + if (c <= 127) + if (SAFE_ASCII[c]) sb.append((char) c); - } else { + else appendSingleByteEncoded(sb, c); - } - } else { + else appendMultiByteEncoded(sb, c); - } } return sb; } - private static void appendSingleByteEncoded(StringBuilder sb, int value) { + private final static void appendSingleByteEncoded(StringBuilder sb, int value) { if (encodeSpaceUsingPlus && value == 32) { sb.append('+'); @@ -85,7 +82,7 @@ private static void appendSingleByteEncoded(StringBuilder sb, int value) { sb.append(HEX[value & 0xF]); } - private static void appendMultiByteEncoded(StringBuilder sb, int value) { + private final static void appendMultiByteEncoded(StringBuilder sb, int value) { if (value < 0x800) { appendSingleByteEncoded(sb, (0xc0 | (value >> 6))); appendSingleByteEncoded(sb, (0x80 | (value & 0x3f))); @@ -100,5 +97,4 @@ private static void appendMultiByteEncoded(StringBuilder sb, int value) { appendSingleByteEncoded(sb, (0x80 | (value & 0x3f))); } } - } diff --git a/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java b/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java index 347d5127ef..6d3b0e0a60 100644 --- a/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java +++ b/api/src/main/java/org/asynchttpclient/webdav/WebDavCompletionHandlerBase.java @@ -13,6 +13,16 @@ package org.asynchttpclient.webdav; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + import org.asynchttpclient.AsyncCompletionHandlerBase; import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.FluentCaseInsensitiveStringsMap; @@ -21,6 +31,7 @@ import org.asynchttpclient.HttpResponseStatus; import org.asynchttpclient.Response; import org.asynchttpclient.cookie.Cookie; +import org.asynchttpclient.uri.UriComponents; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -29,18 +40,6 @@ import org.w3c.dom.NodeList; import org.xml.sax.SAXException; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -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; - /** * Simple {@link AsyncHandler} that add support for WebDav's response manipulation. * @@ -180,7 +179,7 @@ public String getResponseBody() throws IOException { } @Override - public URI getUri() throws MalformedURLException { + public UriComponents getUri() { return wrappedResponse.getUri(); } diff --git a/api/src/main/java/org/asynchttpclient/webdav/WebDavResponse.java b/api/src/main/java/org/asynchttpclient/webdav/WebDavResponse.java index 69de7e2da8..cbeb2ad0b1 100644 --- a/api/src/main/java/org/asynchttpclient/webdav/WebDavResponse.java +++ b/api/src/main/java/org/asynchttpclient/webdav/WebDavResponse.java @@ -12,18 +12,17 @@ */ package org.asynchttpclient.webdav; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.Response; -import org.asynchttpclient.cookie.Cookie; -import org.w3c.dom.Document; - import java.io.IOException; import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URI; import java.nio.ByteBuffer; import java.util.List; +import org.asynchttpclient.FluentCaseInsensitiveStringsMap; +import org.asynchttpclient.Response; +import org.asynchttpclient.cookie.Cookie; +import org.asynchttpclient.uri.UriComponents; +import org.w3c.dom.Document; + /** * Customized {@link Response} which add support for getting the response's body as an XML document (@link WebDavResponse#getBodyAsXML} */ @@ -74,7 +73,7 @@ public String getResponseBody(String charset) throws IOException { return response.getResponseBody(charset); } - public URI getUri() throws MalformedURLException { + public UriComponents getUri() { return response.getUri(); } diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java index accbe651a7..2a17d861d5 100755 --- a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java @@ -49,7 +49,6 @@ import java.net.URL; import java.nio.channels.UnresolvedAddressException; import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -95,7 +94,7 @@ public void onThrowable(Throwable t) { public void asyncProviderEncodingTest2() throws Exception { AsyncHttpClient client = getAsyncHttpClient(null); try { - Request request = new RequestBuilder("GET").setUrl(getTargetUrl() + "").addQueryParameter("q", "a b").build(); + Request request = new RequestBuilder("GET").setUrl(getTargetUrl() + "").addQueryParam("q", "a b").build(); String url = client.executeRequest(request, new AsyncCompletionHandler() { @Override @@ -288,11 +287,11 @@ public void asyncParamPOSTTest() throws Exception { FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); h.add("Content-Type", "application/x-www-form-urlencoded"); - Map> m = new HashMap>(); + Map> m = new HashMap>(); for (int i = 0; i < 5; i++) { m.put("param_" + i, Arrays.asList("value_" + i)); } - Request request = new RequestBuilder("POST").setUrl(getTargetUrl()).setHeaders(h).setParameters(m).build(); + Request request = new RequestBuilder("POST").setUrl(getTargetUrl()).setHeaders(h).setFormParams(m).build(); client.executeRequest(request, new AsyncCompletionHandlerAdapter() { @Override @@ -501,7 +500,7 @@ public void asyncDoPostDefaultContentType() throws Exception { AsyncHttpClient client = getAsyncHttpClient(null); try { final CountDownLatch l = new CountDownLatch(1); - client.preparePost(getTargetUrl()).addParameter("foo", "bar").execute(new AsyncCompletionHandlerAdapter() { + client.preparePost(getTargetUrl()).addFormParam("foo", "bar").execute(new AsyncCompletionHandlerAdapter() { @Override public Response onCompleted(Response response) throws Exception { @@ -756,11 +755,11 @@ public void asyncRequestVirtualServerPOSTTest() throws Exception { FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); h.add("Content-Type", "application/x-www-form-urlencoded"); - Map> m = new HashMap>(); + Map> m = new HashMap>(); for (int i = 0; i < 5; i++) { m.put("param_" + i, Arrays.asList("value_" + i)); } - Request request = new RequestBuilder("POST").setUrl(getTargetUrl()).setHeaders(h).setParameters(m).setVirtualHost("localhost:" + port1).build(); + Request request = new RequestBuilder("POST").setUrl(getTargetUrl()).setHeaders(h).setFormParams(m).setVirtualHost("localhost:" + port1).build(); Response response = client.executeRequest(request, new AsyncCompletionHandlerAdapter()).get(); diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java index 80838a4f7e..985b077695 100644 --- a/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java @@ -97,7 +97,7 @@ public void asyncStreamPOSTTest() throws Exception { try { Future f = c.preparePost(getTargetUrl())// .setHeader("Content-Type", "application/x-www-form-urlencoded")// - .addParameter("param_1", "value_1")// + .addFormParam("param_1", "value_1")// .execute(new AsyncHandlerAdapter() { private StringBuilder builder = new StringBuilder(); @@ -142,7 +142,7 @@ public void asyncStreamInterruptTest() throws Exception { try { c.preparePost(getTargetUrl())// .setHeader("Content-Type", "application/x-www-form-urlencoded")// - .addParameter("param_1", "value_1")// + .addFormParam("param_1", "value_1")// .execute(new AsyncHandlerAdapter() { @Override @@ -182,7 +182,7 @@ public void asyncStreamFutureTest() throws Exception { final AtomicReference responseHeaders = new AtomicReference(); final AtomicReference throwable = new AtomicReference(); try { - Future f = c.preparePost(getTargetUrl()).addParameter("param_1", "value_1").execute(new AsyncHandlerAdapter() { + Future f = c.preparePost(getTargetUrl()).addFormParam("param_1", "value_1").execute(new AsyncHandlerAdapter() { private StringBuilder builder = new StringBuilder(); @Override @@ -262,7 +262,7 @@ public void asyncStreamReusePOSTTest() throws Exception { try { BoundRequestBuilder rb = c.preparePost(getTargetUrl())// .setHeader("Content-Type", "application/x-www-form-urlencoded") - .addParameter("param_1", "value_1"); + .addFormParam("param_1", "value_1"); Future f = rb.execute(new AsyncHandlerAdapter() { private StringBuilder builder = new StringBuilder(); diff --git a/api/src/test/java/org/asynchttpclient/async/ParamEncodingTest.java b/api/src/test/java/org/asynchttpclient/async/ParamEncodingTest.java index e7bd9dd9fa..f423831b01 100644 --- a/api/src/test/java/org/asynchttpclient/async/ParamEncodingTest.java +++ b/api/src/test/java/org/asynchttpclient/async/ParamEncodingTest.java @@ -61,7 +61,7 @@ public void testParameters() throws IOException, ExecutionException, TimeoutExce String value = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKQLMNOPQRSTUVWXYZ1234567809`~!@#$%^&*()_+-=,.<>/?;:'\"[]{}\\| "; AsyncHttpClient client = getAsyncHttpClient(null); try { - Future f = client.preparePost("http://127.0.0.1/" + port1).addParameter("test", value).execute(); + Future f = client.preparePost("http://127.0.0.1/" + port1).addFormParam("test", value).execute(); Response resp = f.get(10, TimeUnit.SECONDS); assertNotNull(resp); assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); diff --git a/api/src/test/java/org/asynchttpclient/async/PerRequestRelative302Test.java b/api/src/test/java/org/asynchttpclient/async/PerRequestRelative302Test.java index 746e2390bd..90e99a4567 100644 --- a/api/src/test/java/org/asynchttpclient/async/PerRequestRelative302Test.java +++ b/api/src/test/java/org/asynchttpclient/async/PerRequestRelative302Test.java @@ -22,25 +22,25 @@ import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; +import java.io.IOException; +import java.net.ConnectException; +import java.util.Enumeration; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.Response; +import org.asynchttpclient.uri.UriComponents; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.net.ConnectException; -import java.net.URI; -import java.util.Enumeration; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; - public abstract class PerRequestRelative302Test extends AbstractBasicTest { private final AtomicBoolean isSet = new AtomicBoolean(false); @@ -123,7 +123,7 @@ public void notRedirected302Test() throws Exception { } } - private String getBaseUrl(URI uri) { + private String getBaseUrl(UriComponents uri) { String url = uri.toString(); int port = uri.getPort(); if (port == -1) { @@ -133,7 +133,7 @@ private String getBaseUrl(URI uri) { return url.substring(0, url.lastIndexOf(":") + String.valueOf(port).length() + 1); } - private static int getPort(URI uri) { + private static int getPort(UriComponents uri) { int port = uri.getPort(); if (port == -1) port = uri.getScheme().equals("http") ? 80 : 443; diff --git a/api/src/test/java/org/asynchttpclient/async/PostRedirectGetTest.java b/api/src/test/java/org/asynchttpclient/async/PostRedirectGetTest.java index 3907683f9d..80865b5e6b 100644 --- a/api/src/test/java/org/asynchttpclient/async/PostRedirectGetTest.java +++ b/api/src/test/java/org/asynchttpclient/async/PostRedirectGetTest.java @@ -87,7 +87,7 @@ public FilterContext filter(FilterContext ctx) throws FilterException } }).build()); try { - Request request = new RequestBuilder("POST").setUrl(getTargetUrl()).addParameter("q", "a b").addHeader("x-redirect", +status + "@" + "http://localhost/" + port1 + "/foo/bar/baz").addHeader("x-negative", "true").build(); + Request request = new RequestBuilder("POST").setUrl(getTargetUrl()).addFormParam("q", "a b").addHeader("x-redirect", +status + "@" + "http://localhost/" + port1 + "/foo/bar/baz").addHeader("x-negative", "true").build(); Future responseFuture = p.executeRequest(request, new AsyncCompletionHandler() { @Override @@ -122,7 +122,7 @@ public FilterContext filter(FilterContext ctx) throws FilterException } }).build()); try { - Request request = new RequestBuilder("POST").setUrl(getTargetUrl()).addParameter("q", "a b").addHeader("x-redirect", +status + "@" + "http://localhost/" + port1 + "/foo/bar/baz").build(); + Request request = new RequestBuilder("POST").setUrl(getTargetUrl()).addFormParam("q", "a b").addHeader("x-redirect", +status + "@" + "http://localhost/" + port1 + "/foo/bar/baz").build(); Future responseFuture = p.executeRequest(request, new AsyncCompletionHandler() { @Override diff --git a/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java b/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java index d2356e782c..fe8909583a 100644 --- a/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java +++ b/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java @@ -92,8 +92,8 @@ public void postWithNulParamQS() throws IOException, ExecutionException, Timeout @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()); + if (!status.getUri().toUrl().equals("http://127.0.0.1/" + port1 + "/?a=")) { + throw new IOException(status.getUri().toUrl()); } return super.onStatusReceived(status); } @@ -115,7 +115,7 @@ public void postWithNulParamsQS() throws IOException, ExecutionException, Timeou @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")) { + if (!status.getUri().toUrl().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); @@ -138,7 +138,7 @@ public void postWithEmptyParamsQS() throws IOException, ExecutionException, Time @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")) { + if (!status.getUri().toUrl().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/api/src/test/java/org/asynchttpclient/async/QueryParametersTest.java b/api/src/test/java/org/asynchttpclient/async/QueryParametersTest.java index 7d267111ca..cfd3b37db4 100644 --- a/api/src/test/java/org/asynchttpclient/async/QueryParametersTest.java +++ b/api/src/test/java/org/asynchttpclient/async/QueryParametersTest.java @@ -74,7 +74,7 @@ public AbstractHandler configureHandler() throws Exception { public void testQueryParameters() throws IOException, ExecutionException, TimeoutException, InterruptedException { AsyncHttpClient client = getAsyncHttpClient(null); try { - Future f = client.prepareGet("http://127.0.0.1/" + port1).addQueryParameter("a", "1").addQueryParameter("b", "2").execute(); + Future f = client.prepareGet("http://127.0.0.1/" + port1).addQueryParam("a", "1").addQueryParam("b", "2").execute(); Response resp = f.get(3, TimeUnit.SECONDS); assertNotNull(resp); assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); @@ -103,20 +103,7 @@ public void testUrlRequestParametersEncoding() throws IOException, ExecutionExce } @Test(groups = { "standalone", "default_provider" }) - public void urlWithColonTest_Netty() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - try { - String query = "test:colon:"; - Response response = c.prepareGet(String.format("http://127.0.0.1:%d/foo/test/colon?q=%s", port1, query)).setHeader("Content-Type", "text/html").execute().get(TIMEOUT, TimeUnit.SECONDS); - - assertEquals(response.getHeader("q"), URLEncoder.encode(query, StandardCharsets.UTF_8.name())); - } finally { - c.close(); - } - } - - @Test(groups = { "standalone", "default_provider" }) - public void urlWithColonTest_JDK() throws Exception { + public void urlWithColonTest() throws Exception { AsyncHttpClient c = getAsyncHttpClient(null); try { String query = "test:colon:"; diff --git a/api/src/test/java/org/asynchttpclient/async/Relative302Test.java b/api/src/test/java/org/asynchttpclient/async/Relative302Test.java index 9eb0cadef0..56fac7b88b 100644 --- a/api/src/test/java/org/asynchttpclient/async/Relative302Test.java +++ b/api/src/test/java/org/asynchttpclient/async/Relative302Test.java @@ -22,25 +22,26 @@ import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; +import java.io.IOException; +import java.net.ConnectException; +import java.net.URI; +import java.util.Enumeration; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.Response; +import org.asynchttpclient.uri.UriComponents; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.net.ConnectException; -import java.net.URI; -import java.util.Enumeration; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; - public abstract class Relative302Test extends AbstractBasicTest { private final AtomicBoolean isSet = new AtomicBoolean(false); @@ -167,7 +168,7 @@ public void relativePathRedirectTest() throws Exception { } } - private String getBaseUrl(URI uri) { + private String getBaseUrl(UriComponents uri) { String url = uri.toString(); int port = uri.getPort(); if (port == -1) { @@ -177,7 +178,7 @@ private String getBaseUrl(URI uri) { return url.substring(0, url.lastIndexOf(":") + String.valueOf(port).length() + 1); } - private static int getPort(URI uri) { + private static int getPort(UriComponents uri) { int port = uri.getPort(); if (port == -1) port = uri.getScheme().equals("http") ? 80 : 443; diff --git a/api/src/test/java/org/asynchttpclient/async/RequestBuilderTest.java b/api/src/test/java/org/asynchttpclient/async/RequestBuilderTest.java index a6e9214ab4..fe560de3ac 100644 --- a/api/src/test/java/org/asynchttpclient/async/RequestBuilderTest.java +++ b/api/src/test/java/org/asynchttpclient/async/RequestBuilderTest.java @@ -17,15 +17,16 @@ import static org.testng.Assert.assertEquals; -import org.asynchttpclient.FluentStringsMap; -import org.asynchttpclient.Request; -import org.asynchttpclient.RequestBuilder; -import org.testng.annotations.Test; - import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.util.List; import java.util.concurrent.ExecutionException; +import org.asynchttpclient.Param; +import org.asynchttpclient.Request; +import org.asynchttpclient.RequestBuilder; +import org.testng.annotations.Test; + public class RequestBuilderTest { private final static String SAFE_CHARS = @@ -54,7 +55,7 @@ public void testEncodesQueryParameters() throws UnsupportedEncodingException { for (String value : values) { RequestBuilder builder = new RequestBuilder("GET"). setUrl("http://example.com/"). - addQueryParameter("name", value); + addQueryParam("name", value); StringBuilder sb = new StringBuilder(); for (int i = 0, len = value.length(); i < len; ++i) { @@ -77,7 +78,7 @@ public void testEncodesQueryParameters() throws UnsupportedEncodingException { public void testChaining() throws IOException, ExecutionException, InterruptedException { Request request = new RequestBuilder("GET") .setUrl("http://foo.com/") - .addQueryParameter("x", "value") + .addQueryParam("x", "value") .build(); Request request2 = new RequestBuilder(request).build(); @@ -89,14 +90,14 @@ public void testChaining() throws IOException, ExecutionException, InterruptedEx public void testParsesQueryParams() throws IOException, ExecutionException, InterruptedException { Request request = new RequestBuilder("GET") .setUrl("http://foo.com/?param1=value1") - .addQueryParameter("param2", "value2") + .addQueryParam("param2", "value2") .build(); assertEquals(request.getUrl(), "http://foo.com/?param1=value1¶m2=value2"); - FluentStringsMap params = request.getQueryParams(); + List params = request.getQueryParams(); assertEquals(params.size(), 2); - assertEquals(params.get("param1").get(0), "value1"); - assertEquals(params.get("param2").get(0), "value2"); + assertEquals(params.get(0), new Param("param1", "value1")); + assertEquals(params.get(1), new Param("param2", "value2")); } @Test(groups = {"standalone", "default_provider"}) diff --git a/api/src/test/java/org/asynchttpclient/oauth/TestSignatureCalculator.java b/api/src/test/java/org/asynchttpclient/oauth/TestSignatureCalculator.java index 69b36f1e1d..268cd58e98 100644 --- a/api/src/test/java/org/asynchttpclient/oauth/TestSignatureCalculator.java +++ b/api/src/test/java/org/asynchttpclient/oauth/TestSignatureCalculator.java @@ -17,7 +17,11 @@ import static org.testng.Assert.assertEquals; -import org.asynchttpclient.FluentStringsMap; +import java.util.ArrayList; +import java.util.List; + +import org.asynchttpclient.Param; +import org.asynchttpclient.uri.UriComponents; import org.testng.annotations.Test; public class TestSignatureCalculator { @@ -40,11 +44,11 @@ public void test() { ConsumerKey consumer = new ConsumerKey(CONSUMER_KEY, CONSUMER_SECRET); RequestToken user = new RequestToken(TOKEN_KEY, TOKEN_SECRET); OAuthSignatureCalculator calc = new OAuthSignatureCalculator(consumer, user); - FluentStringsMap queryParams = new FluentStringsMap(); - queryParams.add("file", "vacation.jpg"); - queryParams.add("size", "original"); + List queryParams = new ArrayList(); + queryParams.add(new Param("file", "vacation.jpg")); + queryParams.add(new Param("size", "original")); String url = "http://photos.example.net/photos"; - String sig = calc.calculateSignature("GET", url, TIMESTAMP, NONCE, null, queryParams); + String sig = calc.calculateSignature("GET", UriComponents.create(url), TIMESTAMP, NONCE, null, queryParams); assertEquals(sig, "tR3+Ty81lMeYAr/Fid0kMTYa/WM="); } diff --git a/api/src/test/java/org/asynchttpclient/util/AsyncHttpProviderUtilsTest.java b/api/src/test/java/org/asynchttpclient/util/AsyncHttpProviderUtilsTest.java deleted file mode 100644 index 06ab8fbc12..0000000000 --- a/api/src/test/java/org/asynchttpclient/util/AsyncHttpProviderUtilsTest.java +++ /dev/null @@ -1,47 +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.util; - -import static org.testng.Assert.assertEquals; - -import org.testng.annotations.Test; - -import java.net.URI; - -public class AsyncHttpProviderUtilsTest { - - @Test(groups = "fast") - public void getRedirectUriShouldHandleProperlyEncodedLocation() { - - String url = "http://www.ebay.de/sch/sis.html;jsessionid=92D73F80262E3EBED7E115ED01035DDA?_nkw=FSC%20Lifebook%20E8310%20Core2Duo%20T8100%202%201GHz%204GB%20DVD%20RW&_itemId=150731406505"; - URI uri = AsyncHttpProviderUtils.getRedirectUri( - URI.create("http://www.ebay.de/"), url); - assertEquals( uri.toString(), "http://www.ebay.de/sch/sis.html;jsessionid=92D73F80262E3EBED7E115ED01035DDA?_nkw=FSC%20Lifebook%20E8310%20Core2Duo%20T8100%202%201GHz%204GB%20DVD%20RW&_itemId=150731406505"); - } - - @Test(groups = "fast") - public void getRedirectUriShouldHandleRawQueryParamsLocation() { - - String url = "http://www.ebay.de/sch/sis.html;jsessionid=92D73F80262E3EBED7E115ED01035DDA?_nkw=FSC%20Lifebook%20E8310%20Core2Duo%20T8100%202%201GHz%204GB%20DVD%20RW&_itemId=150731406505"; - URI uri = AsyncHttpProviderUtils.getRedirectUri(URI.create("http://www.ebay.de/"), url); - assertEquals(uri.toString(), "http://www.ebay.de/sch/sis.html;jsessionid=92D73F80262E3EBED7E115ED01035DDA?_nkw=FSC%20Lifebook%20E8310%20Core2Duo%20T8100%202%201GHz%204GB%20DVD%20RW&_itemId=150731406505"); - } - - @Test(groups = "fast") - public void getRedirectUriShouldHandleRelativeLocation() { - - String url = "/sch/sis.html;jsessionid=92D73F80262E3EBED7E115ED01035DDA?_nkw=FSC Lifebook E8310 Core2Duo T8100 2 1GHz 4GB DVD RW&_itemId=150731406505"; - URI uri = AsyncHttpProviderUtils.getRedirectUri(URI.create("http://www.ebay.de/"), url); - assertEquals(uri.toString(), "http://www.ebay.de/sch/sis.html;jsessionid=92D73F80262E3EBED7E115ED01035DDA?_nkw=FSC%20Lifebook%20E8310%20Core2Duo%20T8100%202%201GHz%204GB%20DVD%20RW&_itemId=150731406505"); - } -} diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java index e90f418664..3130f83f51 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java @@ -20,6 +20,7 @@ import org.asynchttpclient.ConnectionPoolKeyStrategy; import org.asynchttpclient.ProxyServer; import org.asynchttpclient.Request; +import org.asynchttpclient.uri.UriComponents; import org.asynchttpclient.util.Base64; import org.glassfish.grizzly.CompletionHandler; import org.glassfish.grizzly.Connection; @@ -45,7 +46,6 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.net.URI; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -134,7 +134,7 @@ public void doTrackedConnection(final Request request,// } public Connection obtainConnection(final Request request, final GrizzlyResponseFuture requestFuture) throws ExecutionException, - InterruptedException, TimeoutException { + InterruptedException, TimeoutException, IOException { final Connection c = obtainConnection0(request, requestFuture); markConnectionAsDoNotCache(c); @@ -146,7 +146,7 @@ public Connection obtainConnection(final Request request, final GrizzlyResponseF static CompletionHandler wrapHandler(final Request request, final HostnameVerifier verifier, final CompletionHandler delegate, final SSLFilter sslFilter) { - final URI uri = request.getURI(); + final UriComponents uri = request.getURI(); if (Utils.isSecure(uri) && verifier != null) { SSLBaseFilter.HandshakeListener handshakeListener = new SSLBaseFilter.HandshakeListener() { @Override @@ -190,7 +190,7 @@ private HostnameVerifier getVerifier() { return provider.getClientConfig().getHostnameVerifier(); } - private EndpointKey getEndPointKey(final Request request, final ProxyServer proxyServer) { + private EndpointKey getEndPointKey(final Request request, final ProxyServer proxyServer) throws IOException { final String stringKey = getPoolKey(request, proxyServer); EndpointKey key = endpointKeyMap.get(stringKey); if (key == null) { @@ -203,6 +203,7 @@ private EndpointKey getEndPointKey(final Request request, final P if (localAddress != null) { localSocketAddress = new InetSocketAddress(localAddress.getHostName(), 0); } + ProxyAwareConnectorHandler handler = ProxyAwareConnectorHandler.builder(provider.clientTransport) .nonSecureFilterChainTemplate(nonSecureBuilder).secureFilterChainTemplate(secureBuilder) .asyncHttpClientConfig(provider.getClientConfig()).uri(request.getURI()).proxyServer(proxyServer).build(); @@ -216,13 +217,13 @@ private EndpointKey getEndPointKey(final Request request, final P } private SocketAddress getRemoteAddress(final Request request, final ProxyServer proxyServer) { - final URI requestUri = request.getURI(); + final UriComponents requestUri = request.getURI(); final String host = ((proxyServer != null) ? proxyServer.getHost() : requestUri.getHost()); final int port = ((proxyServer != null) ? proxyServer.getPort() : requestUri.getPort()); return new InetSocketAddress(host, getPort(request.getURI(), port)); } - private static int getPort(final URI uri, final int p) { + private static int getPort(final UriComponents uri, final int p) { int port = p; if (port == -1) { final String protocol = uri.getScheme().toLowerCase(Locale.ENGLISH); @@ -238,7 +239,7 @@ private static int getPort(final URI uri, final int p) { } private Connection obtainConnection0(final Request request, final GrizzlyResponseFuture requestFuture) throws ExecutionException, - InterruptedException, TimeoutException { + InterruptedException, TimeoutException, IOException { final int cTimeout = provider.getClientConfig().getConnectionTimeoutInMs(); final FutureImpl future = Futures.createSafeFuture(); @@ -246,6 +247,7 @@ private Connection obtainConnection0(final Request request, final GrizzlyRespons createConnectionCompletionHandler(request, requestFuture, null)); final ProxyServer proxyServer = requestFuture.getProxyServer(); final SocketAddress address = getRemoteAddress(request, proxyServer); + ProxyAwareConnectorHandler handler = ProxyAwareConnectorHandler.builder(provider.clientTransport) .nonSecureFilterChainTemplate(nonSecureBuilder)// .secureFilterChainTemplate(secureBuilder)// 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 934bc52bc4..dd8d457fd6 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 @@ -34,6 +34,7 @@ import org.asynchttpclient.providers.grizzly.statushandler.RedirectHandler; import org.asynchttpclient.providers.grizzly.statushandler.StatusHandler; import org.asynchttpclient.providers.grizzly.websocket.GrizzlyWebSocketAdapter; +import org.asynchttpclient.uri.UriComponents; import org.asynchttpclient.websocket.WebSocketUpgradeHandler; import org.glassfish.grizzly.Connection; import org.glassfish.grizzly.filterchain.FilterChainContext; @@ -49,7 +50,6 @@ import org.glassfish.grizzly.websockets.SimpleWebSocket; import org.glassfish.grizzly.websockets.WebSocketHolder; -import java.net.URI; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -367,14 +367,7 @@ private static GrizzlyWebSocketAdapter createWebSocketAdapter(final HttpTxContex } private static boolean isRedirectAllowed(final HttpTxContext ctx) { - boolean allowed = ctx.getRequest().isRedirectEnabled(); - if (ctx.getRequest().isRedirectOverrideSet()) { - return allowed; - } - if (!allowed) { - allowed = ctx.isRedirectsAllowed(); - } - return allowed; + return ctx.getRequest().getFollowRedirect() != null? ctx.getRequest().getFollowRedirect().booleanValue() : ctx.isRedirectsAllowed(); } @SuppressWarnings("rawtypes") @@ -413,7 +406,7 @@ public static boolean isRedirect(final int status) { // ----------------------------------------------------- Private Methods - public static Request newRequest(final URI uri, final HttpResponsePacket response, final HttpTxContext ctx, boolean asGet) { + public static Request newRequest(final UriComponents uri, final HttpResponsePacket response, final HttpTxContext ctx, boolean asGet) { final RequestBuilder builder = new RequestBuilder(ctx.getRequest()); if (asGet) { @@ -421,9 +414,9 @@ public static Request newRequest(final URI uri, final HttpResponsePacket respons } builder.setUrl(uri.toString()); - if (ctx.getProvider().getClientConfig().isRemoveQueryParamOnRedirect()) { - builder.setQueryParameters(null); - } + if (!ctx.getProvider().getClientConfig().isRemoveQueryParamOnRedirect()) + builder.addQueryParams(ctx.getRequest().getQueryParams()); + if (response.getHeader(Header.Cookie) != null) { for (String cookieStr : response.getHeaders().values(Header.Cookie)) { Cookie c = CookieDecoder.decode(cookieStr); 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 8aa2e02cb4..328a75735e 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 @@ -18,9 +18,9 @@ import org.asynchttpclient.HttpResponseHeaders; import org.asynchttpclient.HttpResponseStatus; import org.asynchttpclient.Response; +import org.asynchttpclient.uri.UriComponents; import org.glassfish.grizzly.http.HttpResponsePacket; -import java.net.URI; import java.util.List; /** @@ -42,7 +42,7 @@ public class GrizzlyResponseStatus extends HttpResponseStatus { // ------------------------------------------------------------ Constructors - public GrizzlyResponseStatus(final HttpResponsePacket response, final URI uri, AsyncHttpClientConfig config) { + public GrizzlyResponseStatus(final HttpResponsePacket response, final UriComponents uri, AsyncHttpClientConfig config) { super(uri, config); statusCode = response.getStatus(); 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 a12fc65748..308558b228 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 @@ -95,7 +95,7 @@ private HttpTxContext(final GrizzlyAsyncHttpProvider provider, final GrizzlyResp this.future = future; this.request = request; this.handler = handler; - redirectsAllowed = this.provider.getClientConfig().isRedirectEnabled(); + redirectsAllowed = this.provider.getClientConfig().isFollowRedirect(); maxRedirectCount = this.provider.getClientConfig().getMaxRedirects(); this.requestUrl = request.getUrl(); } diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ProxyAwareConnectorHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ProxyAwareConnectorHandler.java index c0e1b1fc4c..adff9edb41 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ProxyAwareConnectorHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ProxyAwareConnectorHandler.java @@ -17,6 +17,7 @@ import org.asynchttpclient.ProxyServer; import org.asynchttpclient.providers.grizzly.filters.ProxyFilter; import org.asynchttpclient.providers.grizzly.filters.TunnelFilter; +import org.asynchttpclient.uri.UriComponents; import org.glassfish.grizzly.Processor; import org.glassfish.grizzly.filterchain.FilterChain; import org.glassfish.grizzly.filterchain.FilterChainBuilder; @@ -24,14 +25,12 @@ import org.glassfish.grizzly.nio.transport.TCPNIOConnectorHandler; import org.glassfish.grizzly.nio.transport.TCPNIOTransport; -import java.net.URI; - final class ProxyAwareConnectorHandler extends TCPNIOConnectorHandler { private FilterChainBuilder nonSecureTemplate; private FilterChainBuilder secureTemplate; private AsyncHttpClientConfig clientConfig; - private URI uri; + private UriComponents uri; private ProxyServer proxyServer; // ------------------------------------------------------------ Constructors @@ -113,7 +112,7 @@ public Builder asyncHttpClientConfig(final AsyncHttpClientConfig clientConfig) { return this; } - public Builder uri(final URI uri) { + public Builder uri(final UriComponents uri) { connectorHandler.uri = uri; return this; } 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 340b70527f..4a4a434cca 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,12 +13,12 @@ package org.asynchttpclient.providers.grizzly; +import org.asynchttpclient.uri.UriComponents; import org.glassfish.grizzly.Connection; import org.glassfish.grizzly.Grizzly; import org.glassfish.grizzly.attributes.Attribute; import org.glassfish.grizzly.attributes.AttributeStorage; -import java.net.URI; import java.util.concurrent.atomic.AtomicInteger; public final class Utils { @@ -36,7 +36,7 @@ private Utils() { // ---------------------------------------------------------- Public Methods - public static boolean isSecure(final URI uri) { + public static boolean isSecure(final UriComponents uri) { final String scheme = uri.getScheme(); return ("https".equals(scheme) || "wss".equals(scheme)); } diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ParamsBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ParamsBodyHandler.java index 69c4a63236..afe14577ce 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ParamsBodyHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ParamsBodyHandler.java @@ -15,7 +15,7 @@ import static org.asynchttpclient.util.MiscUtil.isNonEmpty; -import org.asynchttpclient.FluentStringsMap; +import org.asynchttpclient.Param; import org.asynchttpclient.Request; import org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider; import org.glassfish.grizzly.Buffer; @@ -29,7 +29,6 @@ import java.io.IOException; import java.net.URLEncoder; import java.util.List; -import java.util.Map; public final class ParamsBodyHandler extends BodyHandler { @@ -42,7 +41,7 @@ public ParamsBodyHandler(GrizzlyAsyncHttpProvider grizzlyAsyncHttpProvider) { // -------------------------------------------- Methods from BodyHandler public boolean handlesBodyType(final Request request) { - final FluentStringsMap params = request.getParams(); + final List params = request.getFormParams(); return isNonEmpty(params); } @@ -57,24 +56,16 @@ public boolean doHandle(final FilterChainContext ctx, final Request request, fin if (charset == null) { charset = Charsets.ASCII_CHARSET.name(); } - final FluentStringsMap params = request.getParams(); + final List params = request.getFormParams(); if (!params.isEmpty()) { - for (Map.Entry> entry : params.entrySet()) { - String name = entry.getKey(); - List values = entry.getValue(); - if (isNonEmpty(values)) { - if (sb == null) { - sb = new StringBuilder(128); - } - for (int i = 0, len = values.size(); i < len; i++) { - final String value = values.get(i); - if (sb.length() > 0) { - sb.append('&'); - } - sb.append(URLEncoder.encode(name, charset)).append('=').append(URLEncoder.encode(value, charset)); - } - } + if (sb == null) { + sb = new StringBuilder(128); + } + for (Param param : params) { + sb.append(URLEncoder.encode(param.getName(), charset)).append('=').append(URLEncoder.encode(param.getValue(), charset)); + sb.append('&'); } + sb.setLength(sb.length() - 1); } if (sb != null) { final byte[] data = sb.toString().getBytes(charset); 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 90e57d95d1..80db9da2a9 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 @@ -20,12 +20,10 @@ import static org.asynchttpclient.util.AuthenticatorUtils.computeDigestAuthentication; import static org.asynchttpclient.util.MiscUtil.isNonEmpty; - import org.asynchttpclient.AsyncCompletionHandler; import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.FluentStringsMap; import org.asynchttpclient.ProxyServer; import org.asynchttpclient.Realm; import org.asynchttpclient.Request; @@ -45,7 +43,7 @@ import org.asynchttpclient.providers.grizzly.filters.events.ContinueEvent; import org.asynchttpclient.providers.grizzly.filters.events.SSLSwitchingEvent; import org.asynchttpclient.providers.grizzly.filters.events.TunnelRequestEvent; -import org.asynchttpclient.util.StandardCharsets; +import org.asynchttpclient.uri.UriComponents; import org.glassfish.grizzly.Buffer; import org.glassfish.grizzly.Connection; import org.glassfish.grizzly.Grizzly; @@ -74,10 +72,8 @@ import org.slf4j.Logger; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; -import java.net.URLEncoder; import java.util.Collection; import java.util.List; import java.util.Map; @@ -158,7 +154,7 @@ public NextAction handleEvent(final FilterChainContext ctx, final FilterChainEve ctx.suspend(); TunnelRequestEvent tunnelRequestEvent = (TunnelRequestEvent) event; final ProxyServer proxyServer = tunnelRequestEvent.getProxyServer(); - final URI requestUri = tunnelRequestEvent.getUri(); + final UriComponents requestUri = tunnelRequestEvent.getUri(); RequestBuilder builder = new RequestBuilder(); builder.setMethod(Method.CONNECT.getMethodString()); @@ -211,7 +207,7 @@ private boolean sendAsGrizzlyRequest( } final Request request = httpTxContext.getRequest(); - final URI uri = request.isUseRawUrl() ? request.getRawURI() : request.getURI(); + final UriComponents uri = request.getURI(); boolean secure = Utils.isSecure(uri); // If the request is secure, check to see if an error occurred during @@ -419,7 +415,7 @@ private static FilterChainContext obtainProtocolChainContext(final FilterChainCo return newFilterChainContext; } - private static void addHostHeader(final Request request, final URI uri, final HttpRequestPacket requestPacket) { + private static void addHostHeader(final Request request, final UriComponents uri, final HttpRequestPacket requestPacket) { String host = request.getVirtualHost(); if (host != null) { requestPacket.addHeader(Header.Host, host); @@ -542,31 +538,9 @@ private static void convertCookies(final Collection cookies, final org.g private static void addQueryString(final Request request, final HttpRequestPacket requestPacket) { - final FluentStringsMap map = request.getQueryParams(); - if (isNonEmpty(map)) { - StringBuilder sb = new StringBuilder(128); - for (final Map.Entry> entry : map.entrySet()) { - final String name = entry.getKey(); - final List values = entry.getValue(); - if (isNonEmpty(values)) { - try { - for (int i = 0, len = values.size(); i < len; i++) { - final String value = values.get(i); - if (isNonEmpty(value)) { - sb.append(URLEncoder.encode(name, StandardCharsets.UTF_8.name())).append('=') - .append(URLEncoder.encode(values.get(i), StandardCharsets.UTF_8.name())).append('&'); - } else { - sb.append(URLEncoder.encode(name, StandardCharsets.UTF_8.name())).append('&'); - } - } - } catch (UnsupportedEncodingException ignored) { - } - } - } - sb.setLength(sb.length() - 1); - String queryString = sb.toString(); - - requestPacket.setQueryString(queryString); + String query = request.getURI().getQuery(); + if (isNonEmpty(query)) { + requestPacket.setQueryString(query); } } diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/TunnelFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/TunnelFilter.java index 081fd60314..ed9723fa96 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/TunnelFilter.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/TunnelFilter.java @@ -13,18 +13,18 @@ package org.asynchttpclient.providers.grizzly.filters; +import java.io.IOException; + import org.asynchttpclient.ProxyServer; import org.asynchttpclient.providers.grizzly.Utils; import org.asynchttpclient.providers.grizzly.filters.events.TunnelRequestEvent; +import org.asynchttpclient.uri.UriComponents; import org.glassfish.grizzly.IOEvent; import org.glassfish.grizzly.filterchain.BaseFilter; import org.glassfish.grizzly.filterchain.FilterChainContext; import org.glassfish.grizzly.filterchain.FilterChainEvent; import org.glassfish.grizzly.filterchain.NextAction; -import java.io.IOException; -import java.net.URI; - /** * This Filter is responsible for HTTP CONNECT * tunnelling when a connection should be secure and required to @@ -36,11 +36,11 @@ public final class TunnelFilter extends BaseFilter { private final ProxyServer proxyServer; - private final URI uri; + private final UriComponents uri; // ------------------------------------------------------------ Constructors - public TunnelFilter(final ProxyServer proxyServer, final URI uri) { + public TunnelFilter(final ProxyServer proxyServer, final UriComponents uri) { this.proxyServer = proxyServer; this.uri = uri; } diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/TunnelRequestEvent.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/TunnelRequestEvent.java index 41f49894c4..0e403dd014 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/TunnelRequestEvent.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/events/TunnelRequestEvent.java @@ -13,11 +13,10 @@ package org.asynchttpclient.providers.grizzly.filters.events; import org.asynchttpclient.ProxyServer; +import org.asynchttpclient.uri.UriComponents; import org.glassfish.grizzly.filterchain.FilterChainContext; import org.glassfish.grizzly.filterchain.FilterChainEvent; -import java.net.URI; - /** * {@link FilterChainEvent} to initiate CONNECT tunnelling with a proxy server. * @@ -28,11 +27,11 @@ public final class TunnelRequestEvent implements FilterChainEvent { private final FilterChainContext suspendedContext; private final ProxyServer proxyServer; - private final URI uri; + private final UriComponents uri; // ------------------------------------------------------------ Constructors - public TunnelRequestEvent(final FilterChainContext suspendedContext, final ProxyServer proxyServer, final URI uri) { + public TunnelRequestEvent(final FilterChainContext suspendedContext, final ProxyServer proxyServer, final UriComponents uri) { this.suspendedContext = suspendedContext; this.proxyServer = proxyServer; this.uri = uri; @@ -55,7 +54,7 @@ public ProxyServer getProxyServer() { return proxyServer; } - public URI getUri() { + public UriComponents getUri() { return uri; } } 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 313770759d..1e090e69ea 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 @@ -57,7 +57,7 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpT final Request req = httpTransactionContext.getRequest(); ProxyServer proxyServer = httpTransactionContext.getProvider().getClientConfig().getProxyServerSelector() - .select(req.getOriginalURI()); + .select(req.getURI()); String principal = proxyServer.getPrincipal(); String password = proxyServer.getPassword(); Realm realm = new Realm.RealmBuilder().setPrincipal(principal).setPassword(password).setUri("/") 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 5d914b0f75..a59d23876b 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 @@ -19,14 +19,12 @@ import org.asynchttpclient.providers.grizzly.ConnectionManager; import org.asynchttpclient.providers.grizzly.EventHandler; import org.asynchttpclient.providers.grizzly.HttpTxContext; -import org.asynchttpclient.util.AsyncHttpProviderUtils; +import org.asynchttpclient.uri.UriComponents; import org.glassfish.grizzly.Connection; import org.glassfish.grizzly.filterchain.FilterChainContext; import org.glassfish.grizzly.http.HttpResponsePacket; import org.glassfish.grizzly.http.util.Header; -import java.net.URI; - public final class RedirectHandler implements StatusHandler { public static final RedirectHandler INSTANCE = new RedirectHandler(); @@ -46,17 +44,17 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpT throw new IllegalStateException("redirect received, but no location header was present"); } - URI orig; + UriComponents orig; if (httpTransactionContext.getLastRedirectURI() == null) { orig = httpTransactionContext.getRequest().getURI(); } else { - orig = AsyncHttpProviderUtils.getRedirectUri(httpTransactionContext.getRequest().getURI(), + orig = UriComponents.create(httpTransactionContext.getRequest().getURI(), httpTransactionContext.getLastRedirectURI()); } httpTransactionContext.setLastRedirectURI(redirectURL); Request requestToSend; - URI uri = AsyncHttpProviderUtils.getRedirectUri(orig, redirectURL); - if (!uri.toString().equalsIgnoreCase(orig.toString())) { + UriComponents uri = UriComponents.create(orig, redirectURL); + if (!uri.toUrl().equalsIgnoreCase(orig.toUrl())) { requestToSend = EventHandler.newRequest(uri, responsePacket, httpTransactionContext, sendAsGet(responsePacket, httpTransactionContext)); } else { diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java index d3b796fc0e..301308ddcc 100644 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java @@ -90,7 +90,7 @@ public void testNoTransferEncoding() throws Exception { .setConnectionTimeoutInMs(15000) .setRequestTimeoutInMs(15000) .setAllowPoolingConnection(false) - .setUseRawUrl(true) + .setDisableUrlEncodingForBoundRequests(true) .setIOThreadMultiplier(2) // 2 is default .build(); diff --git a/providers/netty/pom.xml b/providers/netty/pom.xml index d6abb3a8ef..bc9e908b63 100644 --- a/providers/netty/pom.xml +++ b/providers/netty/pom.xml @@ -39,7 +39,7 @@ io.netty netty-all - 4.0.21.Final + 4.0.20.Final org.javassist 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 0045be2233..f17d81a80c 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 @@ -32,6 +32,7 @@ import org.asynchttpclient.providers.netty.handler.Processor; import org.asynchttpclient.providers.netty.request.NettyRequestSender; import org.asynchttpclient.providers.netty.util.CleanupChannelGroup; +import org.asynchttpclient.uri.UriComponents; import org.asynchttpclient.util.SslUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,7 +64,6 @@ import java.io.IOException; import java.lang.reflect.Field; -import java.net.URI; import java.security.GeneralSecurityException; import java.util.HashMap; import java.util.Map; @@ -296,7 +296,7 @@ protected void initChannel(Channel ch) throws Exception { }); } - public Bootstrap getBootstrap(URI uri, boolean useSSl, boolean useProxy) { + public Bootstrap getBootstrap(UriComponents uri, boolean useSSl, boolean useProxy) { return (uri.getScheme().startsWith(WEBSOCKET) && !useProxy) ? (useSSl ? secureWebSocketBootstrap : webSocketBootstrap) : (useSSl ? secureBootstrap : plainBootstrap); } @@ -374,7 +374,7 @@ public static void upgradePipelineForWebSockets(Channel channel) { channel.pipeline().addBefore(WS_PROCESSOR, WS_DECODER_HANDLER, new WebSocket08FrameDecoder(false, false, 10 * 1024)); } - public Channel pollAndVerifyCachedChannel(URI uri, ProxyServer proxy, ConnectionPoolKeyStrategy connectionPoolKeyStrategy) { + public Channel pollAndVerifyCachedChannel(UriComponents uri, ProxyServer proxy, ConnectionPoolKeyStrategy connectionPoolKeyStrategy) { final Channel channel = channelPool.poll(connectionPoolKeyStrategy.getKey(uri, proxy)); if (channel != null) { 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 06f1d538e4..2d60a7443f 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 @@ -27,6 +27,7 @@ import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.request.NettyRequest; import org.asynchttpclient.providers.netty.request.timeout.TimeoutsHolder; +import org.asynchttpclient.uri.UriComponents; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +36,6 @@ import io.netty.handler.codec.http.HttpResponse; import java.net.SocketAddress; -import java.net.URI; import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -88,7 +88,7 @@ public enum STATE { // state mutated only inside the event loop private Channel channel; - private URI uri; + private UriComponents uri; private boolean keepAlive = true; private Request request; private NettyRequest nettyRequest; @@ -101,7 +101,7 @@ public enum STATE { private boolean dontWriteBodyBecauseExpectContinue; private boolean allowConnect; - public NettyResponseFuture(URI uri,// + public NettyResponseFuture(UriComponents uri,// Request request,// AsyncHandler asyncHandler,// NettyRequest nettyRequest,// @@ -121,11 +121,11 @@ public NettyResponseFuture(URI uri,// maxRetry = Integer.getInteger(MAX_RETRY, config.getMaxRequestRetry()); } - public URI getURI() { + public UriComponents getURI() { return uri; } - public void setURI(URI uri) { + public void setURI(UriComponents uri) { this.uri = uri; } 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 436d16ea3f..bb52ccf29a 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 @@ -45,6 +45,7 @@ import org.asynchttpclient.providers.netty.response.ResponseHeaders; import org.asynchttpclient.providers.netty.response.ResponseStatus; import org.asynchttpclient.spnego.SpnegoEngine; +import org.asynchttpclient.uri.UriComponents; import org.asynchttpclient.util.AsyncHttpProviderUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,9 +60,6 @@ import io.netty.handler.codec.http.LastHttpContent; import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; import java.util.List; final class HttpProtocol extends Protocol { @@ -80,8 +78,8 @@ private Realm.RealmBuilder newRealmBuilder(Realm realm) { private Realm kerberosChallenge(List proxyAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, NettyResponseFuture future, boolean proxyInd) throws NTLMEngineException { - URI uri = request.getURI(); - String host = request.getVirtualHost() == null ? AsyncHttpProviderUtils.getHost(uri) : request.getVirtualHost(); + UriComponents uri = request.getURI(); + String host = request.getVirtualHost() == null ? uri.getHost() : request.getVirtualHost(); String server = proxyServer == null ? host : proxyServer.getHost(); try { String challengeHeader = SpnegoEngine.instance().generateToken(server); @@ -89,7 +87,7 @@ private Realm kerberosChallenge(List proxyAuth, Request request, ProxySe headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader); return newRealmBuilder(realm)// - .setUri(uri.getRawPath())// + .setUri(uri.getPath())// .setMethodName(request.getMethod())// .setScheme(Realm.AuthScheme.KERBEROS)// .build(); @@ -124,12 +122,12 @@ private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer p if (realm != null && !realm.isNtlmMessageType2Received()) { String challengeHeader = NTLMEngine.INSTANCE.generateType1Msg(ntlmDomain, ntlmHost); - URI uri = request.getURI(); + UriComponents uri = request.getURI(); addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd); future.getAndSetAuth(false); return newRealmBuilder(realm)// .setScheme(realm.getAuthScheme())// - .setUri(uri.getRawPath())// + .setUri(uri.getPath())// .setMethodName(request.getMethod())// .setNtlmMessageType2Received(true)// .build(); @@ -192,7 +190,7 @@ private final boolean updateBodyAndInterrupt(NettyResponseFuture future, Asyn return state; } - private void markAsDone(NettyResponseFuture future, final Channel channel) throws MalformedURLException { + private void markAsDone(NettyResponseFuture future, final Channel channel) { // We need to make sure everything is OK before adding the // connection back to the pool. try { @@ -207,15 +205,10 @@ private void markAsDone(NettyResponseFuture future, final Channel channel) th } } - private final String computeRealmURI(Realm realm, URI requestURI) throws URISyntaxException { + private final String computeRealmURI(Realm realm, UriComponents requestURI) { if (realm.isUseAbsoluteURI()) { if (realm.isOmitQuery() && isNonEmpty(requestURI.getQuery())) { - return new URI( - requestURI.getScheme(), - requestURI.getAuthority(), - requestURI.getPath(), - null, - null).toString(); + return requestURI.withNewQuery(null).toString(); } else { return requestURI.toString(); } @@ -346,11 +339,11 @@ private boolean handleConnectOKAndExit(int statusCode, Realm realm, final Reques } try { - URI requestURI = request.getURI(); + UriComponents requestURI = request.getURI(); String scheme = requestURI.getScheme(); LOGGER.debug("Connecting to proxy {} for scheme {}", proxyServer, scheme); - String host = AsyncHttpProviderUtils.getHost(requestURI); - int port = AsyncHttpProviderUtils.getPort(requestURI); + String host = requestURI.getHost(); + int port = AsyncHttpProviderUtils.getDefaultPort(requestURI); channels.upgradeProtocol(channel.pipeline(), scheme, host, port); } catch (Throwable ex) { @@ -388,7 +381,7 @@ private boolean handleResponseAndExit(final Channel channel, final NettyResponse int statusCode = response.getStatus().code(); Request request = future.getRequest(); Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - HttpResponseHeaders responseHeaders = new ResponseHeaders(future.getURI(), response.headers()); + HttpResponseHeaders responseHeaders = new ResponseHeaders(response.headers()); return handleResponseFiltersReplayRequestAndExit(channel, future, status, responseHeaders)// || handleUnauthorizedAndExit(statusCode, realm, request, response, future, proxyServer, channel)// @@ -438,7 +431,7 @@ && handleResponseAndExit(channel, future, handler, nettyRequest.getHttpRequest() LastHttpContent lastChunk = (LastHttpContent) chunk; HttpHeaders trailingHeaders = lastChunk.trailingHeaders(); if (!trailingHeaders.isEmpty()) { - ResponseHeaders responseHeaders = new ResponseHeaders(future.getURI(), future.getHttpHeaders(), trailingHeaders); + ResponseHeaders responseHeaders = new ResponseHeaders(future.getHttpHeaders(), trailingHeaders); interrupt = handler.onHeadersReceived(responseHeaders) != STATE.CONTINUE; } } 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 7f9032cb9a..0c482212df 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 @@ -37,6 +37,7 @@ import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.request.NettyRequestSender; +import org.asynchttpclient.uri.UriComponents; import org.asynchttpclient.util.AsyncHttpProviderUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,7 +48,8 @@ import io.netty.handler.codec.http.HttpResponse; import java.io.IOException; -import java.net.URI; +import java.util.HashSet; +import java.util.Set; public abstract class Protocol { @@ -62,6 +64,15 @@ public abstract class Protocol { protected final boolean hasIOExceptionFilters; private final TimeConverter timeConverter; + public static final Set REDIRECT_STATUSES = new HashSet(); + + static { + REDIRECT_STATUSES.add(MOVED_PERMANENTLY); + REDIRECT_STATUSES.add(FOUND); + REDIRECT_STATUSES.add(SEE_OTHER); + REDIRECT_STATUSES.add(TEMPORARY_REDIRECT); + } + public Protocol(Channels channels, AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, NettyRequestSender requestSender) { this.channels = channels; @@ -84,11 +95,8 @@ protected boolean handleRedirectAndExit(Request request, NettyResponseFuture 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 (AsyncHttpProviderUtils.followRedirect(config, request) && REDIRECT_STATUSES.contains(status)) { if (future.incrementAndGetCurrentRedirectCount() >= config.getMaxRedirects()) { throw new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects()); @@ -97,13 +105,13 @@ protected boolean handleRedirectAndExit(Request request, NettyResponseFuture future.getAndSetAuth(false); String location = response.headers().get(HttpHeaders.Names.LOCATION); - URI uri = AsyncHttpProviderUtils.getRedirectUri(future.getURI(), location); + UriComponents uri = UriComponents.create(future.getURI(), location); - if (!uri.toString().equals(future.getURI().toString())) { + if (!uri.equals(future.getURI())) { final RequestBuilder requestBuilder = new RequestBuilder(future.getRequest()); - if (config.isRemoveQueryParamOnRedirect()) { - requestBuilder.setQueryParameters(null); - } + + if (!config.isRemoveQueryParamOnRedirect()) + requestBuilder.addQueryParams(future.getRequest().getQueryParams()); // FIXME why not do that for 301 and 307 too? // FIXME I think condition is wrong 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 459643fe45..5aa2ce0663 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 @@ -80,7 +80,7 @@ public void handle(Channel channel, NettyResponseFuture future, Object e) thr if (e instanceof HttpResponse) { HttpResponse response = (HttpResponse) e; HttpResponseStatus status = new ResponseStatus(future.getURI(), response, config); - HttpResponseHeaders responseHeaders = new ResponseHeaders(future.getURI(), response.headers()); + HttpResponseHeaders responseHeaders = new ResponseHeaders(response.headers()); if (handleResponseFiltersReplayRequestAndExit(channel, future, status, responseHeaders)) { return; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java index 77fda9c612..a0dfad4bad 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java @@ -20,9 +20,22 @@ import static org.asynchttpclient.providers.netty.util.HttpUtil.isWebSocket; import static org.asynchttpclient.util.AsyncHttpProviderUtils.DEFAULT_CHARSET; import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.DefaultHttpRequest; +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.HttpVersion; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.Map.Entry; import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.FluentStringsMap; +import org.asynchttpclient.Param; import org.asynchttpclient.ProxyServer; import org.asynchttpclient.Realm; import org.asynchttpclient.Request; @@ -41,25 +54,11 @@ import org.asynchttpclient.providers.netty.request.body.NettyMultipartBody; import org.asynchttpclient.providers.netty.ws.WebSocketUtil; import org.asynchttpclient.spnego.SpnegoEngine; +import org.asynchttpclient.uri.UriComponents; import org.asynchttpclient.util.AsyncHttpProviderUtils; import org.asynchttpclient.util.AuthenticatorUtils; import org.asynchttpclient.util.UTF8UrlEncoder; -import io.netty.buffer.Unpooled; -import io.netty.handler.codec.http.DefaultFullHttpRequest; -import io.netty.handler.codec.http.DefaultHttpRequest; -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.HttpVersion; - -import java.io.IOException; -import java.net.URI; -import java.nio.charset.Charset; -import java.security.NoSuchAlgorithmException; -import java.util.List; -import java.util.Map.Entry; - public final class NettyRequestFactory { public static final String GZIP_DEFLATE = HttpHeaders.Values.GZIP + "," + HttpHeaders.Values.DEFLATE; @@ -72,25 +71,25 @@ public NettyRequestFactory(AsyncHttpClientConfig config, NettyAsyncHttpProviderC this.nettyConfig = nettyConfig; } - private String requestUri(URI uri, ProxyServer proxyServer, HttpMethod method) { + private String requestUri(UriComponents uri, ProxyServer proxyServer, HttpMethod method) { if (method == HttpMethod.CONNECT) return AsyncHttpProviderUtils.getAuthority(uri); else if (proxyServer != null && !(isSecure(uri) && config.isUseRelativeURIsWithSSLProxies())) return uri.toString(); - else if (uri.getRawQuery() != null) - return uri.getRawPath() + "?" + uri.getRawQuery(); + else if (uri.getQuery() != null) + return uri.getPath() + "?" + uri.getQuery(); else - return uri.getRawPath(); + return uri.getPath(); } - private String hostHeader(Request request, URI uri, Realm realm) { + private String hostHeader(Request request, UriComponents uri, Realm realm) { String hostHeader = null; - String host = request.getVirtualHost() != null ? request.getVirtualHost() : AsyncHttpProviderUtils.getHost(uri); + String host = request.getVirtualHost() != null ? request.getVirtualHost() : uri.getHost(); if (host != null) { if (request.getVirtualHost() != null || uri.getPort() == -1) @@ -102,7 +101,7 @@ private String hostHeader(Request request, URI uri, Realm realm) { return hostHeader; } - private String authorizationHeader(Request request, URI uri, ProxyServer proxyServer, Realm realm) throws IOException { + private String authorizationHeader(Request request, UriComponents uri, ProxyServer proxyServer, Realm realm) throws IOException { String authorizationHeader = null; @@ -144,7 +143,7 @@ private String authorizationHeader(Request request, URI uri, ProxyServer proxySe else if (request.getVirtualHost() != null) host = request.getVirtualHost(); else - host = AsyncHttpProviderUtils.getHost(uri); + host = uri.getHost(); if (host == null) host = "127.0.0.1"; @@ -196,17 +195,14 @@ private String proxyAuthorizationHeader(Request request, ProxyServer proxyServer return proxyAuthorization; } - private byte[] computeBodyFromParams(FluentStringsMap params, Charset bodyCharset) { + private byte[] computeBodyFromParams(List params, Charset bodyCharset) { StringBuilder sb = new StringBuilder(); - for (Entry> paramEntry : params) { - String key = paramEntry.getKey(); - for (String value : paramEntry.getValue()) { - UTF8UrlEncoder.appendEncoded(sb, key); - sb.append("="); - UTF8UrlEncoder.appendEncoded(sb, value); - sb.append("&"); - } + for (Param param : params) { + UTF8UrlEncoder.appendEncoded(sb, param.getName()); + sb.append("="); + UTF8UrlEncoder.appendEncoded(sb, param.getValue()); + sb.append("&"); } sb.setLength(sb.length() - 1); return sb.toString().getBytes(bodyCharset); @@ -227,15 +223,15 @@ private NettyBody body(Request request, HttpMethod method) throws IOException { } else if (request.getStreamData() != null) { nettyBody = new NettyInputStreamBody(request.getStreamData()); - } else if (isNonEmpty(request.getParams())) { + } else if (isNonEmpty(request.getFormParams())) { String contentType = null; if (!request.getHeaders().containsKey(HttpHeaders.Names.CONTENT_TYPE)) contentType = HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED; - nettyBody = new NettyByteArrayBody(computeBodyFromParams(request.getParams(), bodyCharset), contentType); + nettyBody = new NettyByteArrayBody(computeBodyFromParams(request.getFormParams(), bodyCharset), contentType); - } else if (request.getParts() != null) { + } else if (isNonEmpty(request.getParts())) { nettyBody = new NettyMultipartBody(request.getParts(), request.getHeaders(), nettyConfig); } else if (request.getFile() != null) { @@ -257,7 +253,7 @@ private NettyBody body(Request request, HttpMethod method) throws IOException { return nettyBody; } - public NettyRequest newNettyRequest(Request request, URI uri, boolean forceConnect, ProxyServer proxyServer) throws IOException { + public NettyRequest newNettyRequest(Request request, UriComponents uri, boolean forceConnect, ProxyServer proxyServer) throws IOException { HttpMethod method = forceConnect ? HttpMethod.CONNECT : HttpMethod.valueOf(request.getMethod()); HttpVersion httpVersion = method == HttpMethod.CONNECT ? HttpVersion.HTTP_1_0 : HttpVersion.HTTP_1_1; 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 48d6cfb3de..2fdaab8d50 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 @@ -36,6 +36,7 @@ import org.asynchttpclient.providers.netty.request.timeout.IdleConnectionTimeoutTimerTask; import org.asynchttpclient.providers.netty.request.timeout.RequestTimeoutTimerTask; import org.asynchttpclient.providers.netty.request.timeout.TimeoutsHolder; +import org.asynchttpclient.uri.UriComponents; import org.asynchttpclient.util.AsyncHttpProviderUtils; import org.asynchttpclient.util.ProxyUtils; import org.asynchttpclient.websocket.WebSocketUpgradeHandler; @@ -52,7 +53,6 @@ import java.io.IOException; import java.net.InetSocketAddress; -import java.net.URI; import java.util.Map; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicBoolean; @@ -147,7 +147,7 @@ private final boolean validateWebSocketRequest(Request request, AsyncHandler return request.getMethod().equals(HttpMethod.GET.name()) && asyncHandler instanceof WebSocketUpgradeHandler; } - private Channel getCachedChannel(NettyResponseFuture future, URI uri, ConnectionPoolKeyStrategy poolKeyGen, ProxyServer proxyServer) { + private Channel getCachedChannel(NettyResponseFuture future, UriComponents uri, ConnectionPoolKeyStrategy poolKeyGen, ProxyServer proxyServer) { if (future != null && future.reuseChannel() && isChannelValid(future.channel())) return future.channel(); @@ -155,7 +155,7 @@ private Channel getCachedChannel(NettyResponseFuture future, URI uri, Connect return channels.pollAndVerifyCachedChannel(uri, proxyServer, poolKeyGen); } - private ListenableFuture sendRequestWithCachedChannel(Request request, URI uri, ProxyServer proxy, + private ListenableFuture sendRequestWithCachedChannel(Request request, UriComponents uri, ProxyServer proxy, NettyResponseFuture future, AsyncHandler asyncHandler, Channel channel) throws IOException { future.setState(NettyResponseFuture.STATE.POOLED); @@ -185,18 +185,18 @@ private ListenableFuture sendRequestWithCachedChannel(Request request, UR return future; } - private InetSocketAddress remoteAddress(Request request, URI uri, ProxyServer proxy, boolean useProxy) { + private InetSocketAddress remoteAddress(Request request, UriComponents uri, ProxyServer proxy, boolean useProxy) { if (request.getInetAddress() != null) - return new InetSocketAddress(request.getInetAddress(), AsyncHttpProviderUtils.getPort(uri)); + return new InetSocketAddress(request.getInetAddress(), AsyncHttpProviderUtils.getDefaultPort(uri)); else if (!useProxy || ProxyUtils.avoidProxy(proxy, uri.getHost())) - return new InetSocketAddress(AsyncHttpProviderUtils.getHost(uri), AsyncHttpProviderUtils.getPort(uri)); + return new InetSocketAddress(uri.getHost(), AsyncHttpProviderUtils.getDefaultPort(uri)); else return new InetSocketAddress(proxy.getHost(), proxy.getPort()); } - private ChannelFuture connect(Request request, URI uri, ProxyServer proxy, boolean useProxy, Bootstrap bootstrap) { + private ChannelFuture connect(Request request, UriComponents uri, ProxyServer proxy, boolean useProxy, Bootstrap bootstrap) { InetSocketAddress remoteAddress = remoteAddress(request, uri, proxy, useProxy); if (request.getLocalAddress() != null) @@ -207,7 +207,7 @@ private ChannelFuture connect(Request request, URI uri, ProxyServer proxy, boole private ListenableFuture sendRequestWithNewChannel(// Request request,// - URI uri,// + UriComponents uri,// ProxyServer proxy,// boolean useProxy,// NettyResponseFuture future,// @@ -249,7 +249,7 @@ private ListenableFuture sendRequestWithNewChannel(// return connectListener.future(); } - private NettyResponseFuture newNettyResponseFuture(URI uri, Request request, AsyncHandler asyncHandler, + private NettyResponseFuture newNettyResponseFuture(UriComponents uri, Request request, AsyncHandler asyncHandler, NettyRequest nettyRequest, ProxyServer proxyServer) { int requestTimeout = AsyncHttpProviderUtils.requestTimeout(config, request); @@ -271,7 +271,7 @@ private NettyResponseFuture newNettyResponseFuture(URI uri, Request reque } private NettyResponseFuture newNettyRequestAndResponseFuture(final Request request, final AsyncHandler asyncHandler, - NettyResponseFuture originalFuture, URI uri, ProxyServer proxy, boolean forceConnect) throws IOException { + NettyResponseFuture originalFuture, UriComponents uri, ProxyServer proxy, boolean forceConnect) throws IOException { NettyRequest nettyRequest = requestFactory.newNettyRequest(request, uri, forceConnect, proxy); @@ -293,7 +293,7 @@ private ListenableFuture sendRequestThroughSslProxy(// AsyncHandler asyncHandler,// NettyResponseFuture future,// boolean reclaimCache,// - URI uri,// + UriComponents uri,// ProxyServer proxyServer) throws IOException { // Using CONNECT depends on wither we can fetch a valid channel or not @@ -323,7 +323,7 @@ private ListenableFuture sendRequestWithCertainForceConnect(// AsyncHandler asyncHandler,// NettyResponseFuture future,// boolean reclaimCache,// - URI uri,// + UriComponents uri,// ProxyServer proxyServer,// boolean useProxy,// boolean forceConnect) throws IOException { @@ -353,7 +353,7 @@ public ListenableFuture sendRequest(final Request request,// throw new IOException("WebSocket method must be a GET"); } - URI uri = config.isUseRawUrl() ? request.getRawURI() : request.getURI(); + UriComponents uri = request.getURI(); ProxyServer proxyServer = ProxyUtils.getProxyServer(config, request); boolean resultOfAConnect = future != null && future.getNettyRequest() != null && future.getNettyRequest().getHttpRequest().getMethod() == HttpMethod.CONNECT; 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 8f08d5f08e..5f9f56da92 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 @@ -15,14 +15,13 @@ */ package org.asynchttpclient.providers.netty.response; -import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.HttpResponseHeaders; - import io.netty.handler.codec.http.HttpHeaders; -import java.net.URI; import java.util.Map; +import org.asynchttpclient.FluentCaseInsensitiveStringsMap; +import org.asynchttpclient.HttpResponseHeaders; + /** * A class that represent the HTTP headers. */ @@ -33,11 +32,11 @@ public class ResponseHeaders extends HttpResponseHeaders { private final FluentCaseInsensitiveStringsMap headers; // FIXME unused AsyncHttpProvider provider - public ResponseHeaders(URI uri, HttpHeaders responseHeaders) { - this(uri, responseHeaders, null); + public ResponseHeaders(HttpHeaders responseHeaders) { + this(responseHeaders, null); } - public ResponseHeaders(URI uri, HttpHeaders responseHeaders, HttpHeaders traillingHeaders) { + public ResponseHeaders(HttpHeaders responseHeaders, HttpHeaders traillingHeaders) { super(traillingHeaders != null); this.responseHeaders = responseHeaders; this.trailingHeaders = traillingHeaders; 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 43d3f8a0e1..996038de10 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 @@ -21,10 +21,10 @@ import org.asynchttpclient.HttpResponseHeaders; import org.asynchttpclient.HttpResponseStatus; import org.asynchttpclient.Response; +import org.asynchttpclient.uri.UriComponents; import io.netty.handler.codec.http.HttpResponse; -import java.net.URI; import java.util.List; /** @@ -34,7 +34,7 @@ public class ResponseStatus extends HttpResponseStatus { private final HttpResponse response; - public ResponseStatus(URI uri, HttpResponse response, AsyncHttpClientConfig config) { + public ResponseStatus(UriComponents uri, HttpResponse response, AsyncHttpClientConfig config) { super(uri, config); this.response = response; } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtil.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtil.java index 57b99ae2a5..c94ed864ad 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtil.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtil.java @@ -14,9 +14,10 @@ import static org.asynchttpclient.util.MiscUtil.isNonEmpty; -import java.net.URI; import java.util.List; +import org.asynchttpclient.uri.UriComponents; + public final class HttpUtil { public static final String HTTPS = "https"; @@ -39,7 +40,7 @@ public static boolean isSecure(String scheme) { return HTTPS.equalsIgnoreCase(scheme) || WEBSOCKET_SSL.equalsIgnoreCase(scheme); } - public static boolean isSecure(URI uri) { + public static boolean isSecure(UriComponents uri) { return isSecure(uri.getScheme()); } } diff --git a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyFilterTest.java b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyFilterTest.java index d1c6fd0ebb..f725ea2288 100644 --- a/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyFilterTest.java +++ b/providers/netty/src/test/java/org/asynchttpclient/providers/netty/NettyFilterTest.java @@ -15,7 +15,9 @@ import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.async.FilterTest; +import org.testng.annotations.Test; +@Test public class NettyFilterTest extends FilterTest { @Override public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { 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 de5fbc896e..7c29f993e3 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 @@ -70,9 +70,9 @@ protected String getTargetUrl() { 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)// + .addQueryParam(action, "1")// + .addQueryParam("maxRequests", "" + requests)// + .addQueryParam("id", id)// .build(); return client.executeRequest(r); } From aa79b4c8e1accc6fe6af60bc4549028cf69cca47 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 9 Jul 2014 00:33:16 +0200 Subject: [PATCH 0072/2020] Rename Realm.domain, close #593 --- api/src/main/java/org/asynchttpclient/Realm.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/Realm.java b/api/src/main/java/org/asynchttpclient/Realm.java index 3214651fac..765896b5ca 100644 --- a/api/src/main/java/org/asynchttpclient/Realm.java +++ b/api/src/main/java/org/asynchttpclient/Realm.java @@ -48,7 +48,7 @@ public class Realm { private final String enc; private final String host; private final boolean messageType2Received; - private final String domain; + private final String ntlmDomain; private final Charset charset; private final boolean useAbsoluteURI; private final boolean omitQuery; @@ -58,7 +58,7 @@ public enum AuthScheme { } private Realm(AuthScheme scheme, String principal, String password, String realmName, String nonce, String algorithm, String response, - String qop, String nc, String cnonce, String uri, String method, boolean usePreemptiveAuth, String domain, String enc, + String qop, String nc, String cnonce, String uri, String method, boolean usePreemptiveAuth, String ntlmDomain, String enc, String host, boolean messageType2Received, String opaque, boolean useAbsoluteURI, boolean omitQuery) { this.principal = principal; @@ -75,7 +75,7 @@ private Realm(AuthScheme scheme, String principal, String password, String realm this.uri = uri; this.methodName = method; this.usePreemptiveAuth = usePreemptiveAuth; - this.domain = domain; + this.ntlmDomain = ntlmDomain; this.enc = enc; this.host = host; this.messageType2Received = messageType2Received; @@ -164,7 +164,7 @@ public boolean getUsePreemptiveAuth() { * @return the NTLM domain */ public String getNtlmDomain() { - return domain; + return ntlmDomain; } /** From 7e78f67a28593b5f602093b23c44db350fbc79ab Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 9 Jul 2014 00:56:15 +0200 Subject: [PATCH 0073/2020] Use followRedirect everywhere, close #604 --- .../org/asynchttpclient/AsyncHttpClientConfig.java | 2 +- .../java/org/asynchttpclient/RequestBuilder.java | 4 ++-- .../java/org/asynchttpclient/RequestBuilderBase.java | 10 +++++----- .../org/asynchttpclient/SimpleAsyncHttpClient.java | 6 +++--- .../async/AsyncProvidersBasicTest.java | 2 +- .../async/AsyncStreamHandlerTest.java | 2 +- .../org/asynchttpclient/async/BasicAuthTest.java | 2 +- .../java/org/asynchttpclient/async/ChunkingTest.java | 2 +- .../asynchttpclient/async/FollowingThreadTest.java | 2 +- .../async/HttpToHttpsRedirectTest.java | 6 +++--- .../asynchttpclient/async/MultipartUploadTest.java | 2 +- .../asynchttpclient/async/NoNullResponseTest.java | 2 +- .../async/PerRequestRelative302Test.java | 10 +++++----- .../asynchttpclient/async/PostRedirectGetTest.java | 4 ++-- .../asynchttpclient/async/ProxyTunnellingTest.java | 6 +++--- .../async/RedirectConnectionUsageTest.java | 2 +- .../org/asynchttpclient/async/Relative302Test.java | 8 ++++---- .../org/asynchttpclient/async/RemoteSiteTest.java | 12 ++++++------ .../org/asynchttpclient/websocket/RedirectTest.java | 2 +- .../grizzly/GrizzlyNoTransferEncodingTest.java | 2 +- 20 files changed, 44 insertions(+), 44 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java index 65fac8d2be..11b2cbe28d 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -676,7 +676,7 @@ public Builder setRequestTimeoutInMs(int requestTimeoutInMs) { * @param redirectEnabled true if enabled. * @return a {@link Builder} */ - public Builder setFollowRedirects(boolean followRedirect) { + public Builder setFollowRedirect(boolean followRedirect) { this.followRedirect = followRedirect; return this; } diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilder.java b/api/src/main/java/org/asynchttpclient/RequestBuilder.java index 52fef5a98a..b5d2e91444 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilder.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilder.java @@ -165,8 +165,8 @@ public RequestBuilder setVirtualHost(String virtualHost) { } @Override - public RequestBuilder setFollowRedirects(boolean followRedirects) { - return super.setFollowRedirects(followRedirects); + public RequestBuilder setFollowRedirect(boolean followRedirect) { + return super.setFollowRedirect(followRedirect); } @Override diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java index cd38530ac2..3e0457c862 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -62,7 +62,7 @@ private static final class RequestImpl implements Request { public ProxyServer proxyServer; private Realm realm; private File file; - private Boolean followRedirects; + private Boolean followRedirect; private int requestTimeoutInMs; private long rangeOffset; public String charset; @@ -91,7 +91,7 @@ public RequestImpl(Request prototype) { this.proxyServer = prototype.getProxyServer(); this.realm = prototype.getRealm(); this.file = prototype.getFile(); - this.followRedirects = prototype.getFollowRedirect(); + this.followRedirect = prototype.getFollowRedirect(); this.requestTimeoutInMs = prototype.getRequestTimeoutInMs(); this.rangeOffset = prototype.getRangeOffset(); this.charset = prototype.getBodyEncoding(); @@ -200,7 +200,7 @@ public File getFile() { @Override public Boolean getFollowRedirect() { - return followRedirects; + return followRedirect; } @Override @@ -527,8 +527,8 @@ public T setRealm(Realm realm) { return derived.cast(this); } - public T setFollowRedirects(boolean followRedirects) { - request.followRedirects = followRedirects; + public T setFollowRedirect(boolean followRedirect) { + request.followRedirect = followRedirect; return derived.cast(this); } diff --git a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java index 3ea5d1e72c..6dc6036fa3 100644 --- a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java +++ b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java @@ -373,7 +373,7 @@ public enum ErrorDocumentBehaviour { */ public interface DerivedBuilder { - DerivedBuilder setFollowRedirects(boolean followRedirects); + DerivedBuilder setFollowRedirect(boolean followRedirect); DerivedBuilder setVirtualHost(String virtualHost); @@ -494,8 +494,8 @@ public Builder setVirtualHost(String virtualHost) { return this; } - public Builder setFollowRedirects(boolean followRedirects) { - requestBuilder.setFollowRedirects(followRedirects); + public Builder setFollowRedirect(boolean followRedirect) { + requestBuilder.setFollowRedirect(followRedirect); return this; } diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java index 2a17d861d5..4e2906eea4 100755 --- a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java @@ -1339,7 +1339,7 @@ public Response onCompleted(Response response) throws Exception { @Test(groups = { "online", "default_provider", "async" }) public void asyncDoGetMaxRedirectTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new Builder().setMaxRedirects(0).setFollowRedirects(true).build()); + AsyncHttpClient client = getAsyncHttpClient(new Builder().setMaxRedirects(0).setFollowRedirect(true).build()); try { // Use a l in case the assert fail final CountDownLatch l = new CountDownLatch(1); diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java index 985b077695..835bdf31be 100644 --- a/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java @@ -370,7 +370,7 @@ public String onCompleted() throws Exception { @Test(groups = { "online", "default_provider" }) public void asyncStream302RedirectWithBody() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirects(true).build()); + AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build()); final AtomicReference statusCode = new AtomicReference(0); final AtomicReference responseHeaders = new AtomicReference(); try { diff --git a/api/src/test/java/org/asynchttpclient/async/BasicAuthTest.java b/api/src/test/java/org/asynchttpclient/async/BasicAuthTest.java index bb388d908e..2c0c3c553c 100644 --- a/api/src/test/java/org/asynchttpclient/async/BasicAuthTest.java +++ b/api/src/test/java/org/asynchttpclient/async/BasicAuthTest.java @@ -197,7 +197,7 @@ public void basicAuthTest() throws IOException, ExecutionException, TimeoutExcep @Test(groups = { "standalone", "default_provider" }) public void redirectAndBasicAuthTest() throws Exception, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirects(true).setMaxRedirects(10).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).setMaxRedirects(10).build()); try { Future f = client.prepareGet(getTargetUrl2())// .setRealm((new Realm.RealmBuilder()).setPrincipal(USER).setPassword(ADMIN).build())// diff --git a/api/src/test/java/org/asynchttpclient/async/ChunkingTest.java b/api/src/test/java/org/asynchttpclient/async/ChunkingTest.java index c590a94a3c..8d8845faa7 100644 --- a/api/src/test/java/org/asynchttpclient/async/ChunkingTest.java +++ b/api/src/test/java/org/asynchttpclient/async/ChunkingTest.java @@ -50,7 +50,7 @@ public void testCustomChunking() throws Exception { bc.setMaxConnectionsTotal(1); bc.setConnectionTimeoutInMs(1000); bc.setRequestTimeoutInMs(1000); - bc.setFollowRedirects(true); + bc.setFollowRedirect(true); AsyncHttpClient c = getAsyncHttpClient(bc.build()); try { diff --git a/api/src/test/java/org/asynchttpclient/async/FollowingThreadTest.java b/api/src/test/java/org/asynchttpclient/async/FollowingThreadTest.java index 5ccb8ff6c8..2897d772e3 100644 --- a/api/src/test/java/org/asynchttpclient/async/FollowingThreadTest.java +++ b/api/src/test/java/org/asynchttpclient/async/FollowingThreadTest.java @@ -50,7 +50,7 @@ public void testFollowRedirect() throws IOException, ExecutionException, Timeout public void run() { final CountDownLatch l = new CountDownLatch(1); - final AsyncHttpClient ahc = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirects(true).build()); + final AsyncHttpClient ahc = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build()); try { ahc.prepareGet("http://www.google.com/").execute(new AsyncHandler() { diff --git a/api/src/test/java/org/asynchttpclient/async/HttpToHttpsRedirectTest.java b/api/src/test/java/org/asynchttpclient/async/HttpToHttpsRedirectTest.java index 132c87f453..f186cbb3dd 100644 --- a/api/src/test/java/org/asynchttpclient/async/HttpToHttpsRedirectTest.java +++ b/api/src/test/java/org/asynchttpclient/async/HttpToHttpsRedirectTest.java @@ -98,7 +98,7 @@ public void httpToHttpsRedirect() throws Exception { AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder()// .setMaxRedirects(5)// - .setFollowRedirects(true)// + .setFollowRedirect(true)// .setAcceptAnyCertificate(true)// .build(); AsyncHttpClient c = getAsyncHttpClient(cg); @@ -118,7 +118,7 @@ public void httpToHttpsProperConfig() throws Exception { AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder()// .setMaxRedirects(5)// - .setFollowRedirects(true)// + .setFollowRedirect(true)// .setAcceptAnyCertificate(true)// .build(); AsyncHttpClient c = getAsyncHttpClient(cg); @@ -144,7 +144,7 @@ public void relativeLocationUrl() throws Exception { AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder()// .setMaxRedirects(5)// - .setFollowRedirects(true)// + .setFollowRedirect(true)// .setAcceptAnyCertificate(true)// .build(); AsyncHttpClient c = getAsyncHttpClient(cg); diff --git a/api/src/test/java/org/asynchttpclient/async/MultipartUploadTest.java b/api/src/test/java/org/asynchttpclient/async/MultipartUploadTest.java index cbf13877de..1222e4408d 100644 --- a/api/src/test/java/org/asynchttpclient/async/MultipartUploadTest.java +++ b/api/src/test/java/org/asynchttpclient/async/MultipartUploadTest.java @@ -166,7 +166,7 @@ public void testSendingSmallFilesAndByteArray() { AsyncHttpClientConfig.Builder bc = new AsyncHttpClientConfig.Builder(); - bc.setFollowRedirects(true); + bc.setFollowRedirect(true); AsyncHttpClient c = getAsyncHttpClient(bc.build()); diff --git a/api/src/test/java/org/asynchttpclient/async/NoNullResponseTest.java b/api/src/test/java/org/asynchttpclient/async/NoNullResponseTest.java index 2b78e7972a..0e27a0fb86 100644 --- a/api/src/test/java/org/asynchttpclient/async/NoNullResponseTest.java +++ b/api/src/test/java/org/asynchttpclient/async/NoNullResponseTest.java @@ -56,7 +56,7 @@ public void multipleSslRequestsWithDelayAndKeepAlive() throws Exception { } private AsyncHttpClient create() throws GeneralSecurityException { - final AsyncHttpClientConfig.Builder configBuilder = new AsyncHttpClientConfig.Builder().setCompressionEnabled(true).setFollowRedirects(true).setSSLContext(getSSLContext()).setAllowPoolingConnection(true).setConnectionTimeoutInMs(10000) + final AsyncHttpClientConfig.Builder configBuilder = new AsyncHttpClientConfig.Builder().setCompressionEnabled(true).setFollowRedirect(true).setSSLContext(getSSLContext()).setAllowPoolingConnection(true).setConnectionTimeoutInMs(10000) .setIdleConnectionInPoolTimeoutInMs(60000).setRequestTimeoutInMs(10000).setMaxConnectionsPerHost(-1).setMaxConnectionsTotal(-1); return getAsyncHttpClient(configBuilder.build()); } diff --git a/api/src/test/java/org/asynchttpclient/async/PerRequestRelative302Test.java b/api/src/test/java/org/asynchttpclient/async/PerRequestRelative302Test.java index 90e99a4567..76450d53c2 100644 --- a/api/src/test/java/org/asynchttpclient/async/PerRequestRelative302Test.java +++ b/api/src/test/java/org/asynchttpclient/async/PerRequestRelative302Test.java @@ -94,7 +94,7 @@ public void redirected302Test() throws Exception { isSet.getAndSet(false); AsyncHttpClient c = getAsyncHttpClient(null); try { - Response response = c.prepareGet(getTargetUrl()).setFollowRedirects(true).setHeader("X-redirect", "http://www.microsoft.com/").execute().get(); + Response response = c.prepareGet(getTargetUrl()).setFollowRedirect(true).setHeader("X-redirect", "http://www.microsoft.com/").execute().get(); assertNotNull(response); assertEquals(response.getStatusCode(), 200); @@ -111,10 +111,10 @@ public void redirected302Test() throws Exception { // @Test(groups = { "online", "default_provider" }) public void notRedirected302Test() throws Exception { isSet.getAndSet(false); - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirects(true).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { - Response response = c.prepareGet(getTargetUrl()).setFollowRedirects(false).setHeader("X-redirect", "http://www.microsoft.com/").execute().get(); + Response response = c.prepareGet(getTargetUrl()).setFollowRedirect(false).setHeader("X-redirect", "http://www.microsoft.com/").execute().get(); assertNotNull(response); assertEquals(response.getStatusCode(), 302); @@ -146,7 +146,7 @@ public void redirected302InvalidTest() throws Exception { AsyncHttpClient c = getAsyncHttpClient(null); try { // If the test hit a proxy, no ConnectException will be thrown and instead of 404 will be returned. - Response response = c.preparePost(getTargetUrl()).setFollowRedirects(true).setHeader("X-redirect", String.format("http://127.0.0.1:%d/", port2)).execute().get(); + Response response = c.preparePost(getTargetUrl()).setFollowRedirect(true).setHeader("X-redirect", String.format("http://127.0.0.1:%d/", port2)).execute().get(); assertNotNull(response); assertEquals(response.getStatusCode(), 404); @@ -163,7 +163,7 @@ public void relativeLocationUrl() throws Exception { AsyncHttpClient c = getAsyncHttpClient(null); try { - Response response = c.preparePost(getTargetUrl()).setFollowRedirects(true).setHeader("X-redirect", "/foo/test").execute().get(); + Response response = c.preparePost(getTargetUrl()).setFollowRedirect(true).setHeader("X-redirect", "/foo/test").execute().get(); assertNotNull(response); assertEquals(response.getStatusCode(), 302); assertEquals(response.getUri().toString(), getTargetUrl()); diff --git a/api/src/test/java/org/asynchttpclient/async/PostRedirectGetTest.java b/api/src/test/java/org/asynchttpclient/async/PostRedirectGetTest.java index 80865b5e6b..306db3efed 100644 --- a/api/src/test/java/org/asynchttpclient/async/PostRedirectGetTest.java +++ b/api/src/test/java/org/asynchttpclient/async/PostRedirectGetTest.java @@ -75,7 +75,7 @@ public void postRedirectGet307Test() throws Exception { // --------------------------------------------------------- Private Methods private void doTestNegative(final int status, boolean strict) throws Exception { - AsyncHttpClient p = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirects(true).setStrict302Handling(strict).addResponseFilter(new ResponseFilter() { + AsyncHttpClient p = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).setStrict302Handling(strict).addResponseFilter(new ResponseFilter() { @Override public FilterContext filter(FilterContext ctx) throws FilterException { // pass on the x-expect-get and remove the x-redirect @@ -110,7 +110,7 @@ public void onThrowable(Throwable t) { } private void doTestPositive(final int status) throws Exception { - AsyncHttpClient p = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirects(true).addResponseFilter(new ResponseFilter() { + AsyncHttpClient p = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).addResponseFilter(new ResponseFilter() { @Override public FilterContext filter(FilterContext ctx) throws FilterException { // pass on the x-expect-get and remove the x-redirect diff --git a/api/src/test/java/org/asynchttpclient/async/ProxyTunnellingTest.java b/api/src/test/java/org/asynchttpclient/async/ProxyTunnellingTest.java index 01d85ac608..203f52a26b 100644 --- a/api/src/test/java/org/asynchttpclient/async/ProxyTunnellingTest.java +++ b/api/src/test/java/org/asynchttpclient/async/ProxyTunnellingTest.java @@ -78,7 +78,7 @@ public void testRequestProxy() throws IOException, InterruptedException, Executi ProxyServer ps = new ProxyServer(ProxyServer.Protocol.HTTPS, "127.0.0.1", port1); AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// - .setFollowRedirects(true)// + .setFollowRedirect(true)// .setAcceptAnyCertificate(true)// .build(); @@ -108,7 +108,7 @@ public Response onCompleted(Response response) throws Exception { @Test(groups = { "online", "default_provider" }) public void testConfigProxy() throws IOException, InterruptedException, ExecutionException, TimeoutException { AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// - .setFollowRedirects(true)// + .setFollowRedirect(true)// .setProxyServer(new ProxyServer(ProxyServer.Protocol.HTTPS, "127.0.0.1", port1))// .setAcceptAnyCertificate(true)// .build(); @@ -142,7 +142,7 @@ public void testSimpleAHCConfigProxy() throws IOException, InterruptedException, .setProxyProtocol(ProxyServer.Protocol.HTTPS)// .setProxyHost("127.0.0.1")// .setProxyPort(port1)// - .setFollowRedirects(true)// + .setFollowRedirect(true)// .setUrl(getTargetUrl2())// .setAcceptAnyCertificate(true)// .setHeader("Content-Type", "text/html")// diff --git a/api/src/test/java/org/asynchttpclient/async/RedirectConnectionUsageTest.java b/api/src/test/java/org/asynchttpclient/async/RedirectConnectionUsageTest.java index 6849615298..8241e49f4d 100644 --- a/api/src/test/java/org/asynchttpclient/async/RedirectConnectionUsageTest.java +++ b/api/src/test/java/org/asynchttpclient/async/RedirectConnectionUsageTest.java @@ -79,7 +79,7 @@ public void testGetRedirectFinalUrl() throws Exception { .setMaxConnectionsTotal(1)// .setConnectionTimeoutInMs(1000)// .setRequestTimeoutInMs(1000)// - .setFollowRedirects(true)// + .setFollowRedirect(true)// .build(); AsyncHttpClient c = getAsyncHttpClient(config); diff --git a/api/src/test/java/org/asynchttpclient/async/Relative302Test.java b/api/src/test/java/org/asynchttpclient/async/Relative302Test.java index 56fac7b88b..081ddd1e0a 100644 --- a/api/src/test/java/org/asynchttpclient/async/Relative302Test.java +++ b/api/src/test/java/org/asynchttpclient/async/Relative302Test.java @@ -90,7 +90,7 @@ public void testAllSequentiallyBecauseNotThreadSafe() throws Exception { // @Test(groups = { "online", "default_provider" }) public void redirected302Test() throws Exception { isSet.getAndSet(false); - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirects(true).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { @@ -110,7 +110,7 @@ public void redirected302Test() throws Exception { // @Test(groups = { "standalone", "default_provider" }) public void redirected302InvalidTest() throws Exception { isSet.getAndSet(false); - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirects(true).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); AsyncHttpClient c = getAsyncHttpClient(cg); // If the test hit a proxy, no ConnectException will be thrown and instead of 404 will be returned. @@ -130,7 +130,7 @@ public void redirected302InvalidTest() throws Exception { public void absolutePathRedirectTest() throws Exception { isSet.getAndSet(false); - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirects(true).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { String redirectTarget = "/bar/test"; @@ -151,7 +151,7 @@ public void absolutePathRedirectTest() throws Exception { public void relativePathRedirectTest() throws Exception { isSet.getAndSet(false); - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirects(true).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { String redirectTarget = "bar/test1"; diff --git a/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java b/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java index d03c666900..36a735b610 100644 --- a/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java +++ b/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java @@ -127,7 +127,7 @@ public void testGoogleComWithTimeout() throws Exception { @Test(groups = { "online", "default_provider" }) public void asyncStatusHEADContentLenghtTest() throws Exception { - AsyncHttpClient p = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirects(true).build()); + AsyncHttpClient p = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build()); try { final CountDownLatch l = new CountDownLatch(1); Request request = new RequestBuilder("HEAD").setUrl("http://www.google.com/").build(); @@ -154,7 +154,7 @@ public Response onCompleted(Response response) throws Exception { @Test(groups = { "online", "default_provider" }, enabled = false) public void invalidStreamTest2() throws Exception { - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(10000).setFollowRedirects(true) + AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(10000).setFollowRedirect(true) .setAllowPoolingConnection(false).setMaxRedirects(6).build(); AsyncHttpClient c = getAsyncHttpClient(config); @@ -219,7 +219,7 @@ public void testAHC60() throws Exception { @Test(groups = { "online", "default_provider" }) public void stripQueryStringTest() throws Exception { - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirects(true).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { Response response = c.prepareGet("http://www.freakonomics.com/?p=55846").execute().get(); @@ -234,7 +234,7 @@ public void stripQueryStringTest() throws Exception { @Test(groups = { "online", "default_provider" }) public void stripQueryStringNegativeTest() throws Exception { - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setRemoveQueryParamsOnRedirect(false).setFollowRedirects(true) + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setRemoveQueryParamsOnRedirect(false).setFollowRedirect(true) .build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { @@ -252,7 +252,7 @@ public void evilCoookieTest() throws Exception { AsyncHttpClient c = getAsyncHttpClient(null); try { RequestBuilder builder2 = new RequestBuilder("GET"); - builder2.setFollowRedirects(true); + builder2.setFollowRedirect(true); builder2.setUrl("http://www.google.com/"); builder2.addHeader("Content-Type", "text/plain"); builder2.addCookie(new Cookie("evilcookie", "test", "test", ".google.com", "/", -1L, 10, false, false)); @@ -268,7 +268,7 @@ public void evilCoookieTest() throws Exception { @Test(groups = { "online", "default_provider" }, enabled = false) public void testAHC62Com() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirects(true).build()); + AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build()); try { Response response = c.prepareGet("http://api.crunchbase.com/v/1/financial-organization/kinsey-hills-group.js") .execute(new AsyncHandler() { diff --git a/api/src/test/java/org/asynchttpclient/websocket/RedirectTest.java b/api/src/test/java/org/asynchttpclient/websocket/RedirectTest.java index c0a6c2fc55..b0b825fa86 100644 --- a/api/src/test/java/org/asynchttpclient/websocket/RedirectTest.java +++ b/api/src/test/java/org/asynchttpclient/websocket/RedirectTest.java @@ -76,7 +76,7 @@ public void configure(WebSocketServletFactory factory) { @Test(timeOut = 60000) public void testRedirectToWSResource() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirects(true).build()); + AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build()); try { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference text = new AtomicReference(""); diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java index 301308ddcc..8655f47753 100644 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java @@ -86,7 +86,7 @@ public void testNoTransferEncoding() throws Exception { AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder() .setCompressionEnabled(true) - .setFollowRedirects(false) + .setFollowRedirect(false) .setConnectionTimeoutInMs(15000) .setRequestTimeoutInMs(15000) .setAllowPoolingConnection(false) From b0056567b1dcad3faea7a02686bee8f32004fc67 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 9 Jul 2014 15:35:08 +0200 Subject: [PATCH 0074/2020] Rename getAllowPoolingConnection into isAllowPoolingConnection, close #605 --- .../java/org/asynchttpclient/AsyncHttpClientConfig.java | 6 +++--- .../org/asynchttpclient/util/AsyncHttpProviderUtils.java | 2 +- .../asynchttpclient/providers/netty/channel/Channels.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java index 11b2cbe28d..8c8a4ba980 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -280,7 +280,7 @@ public int getMaxRedirects() { * * @return true if keep-alive is enabled */ - public boolean getAllowPoolingConnection() { + public boolean isAllowPoolingConnection() { return allowPoolingConnection; } @@ -1097,7 +1097,7 @@ public Builder setAcceptAnyCertificate(boolean acceptAnyCertificate) { * @param prototype the configuration to use as a prototype. */ public Builder(AsyncHttpClientConfig prototype) { - allowPoolingConnection = prototype.getAllowPoolingConnection(); + allowPoolingConnection = prototype.isAllowPoolingConnection(); providerConfig = prototype.getAsyncHttpProviderConfig(); connectionTimeOutInMs = prototype.getConnectionTimeoutInMs(); idleConnectionInPoolTimeoutInMs = prototype.getIdleConnectionInPoolTimeoutInMs(); @@ -1127,7 +1127,7 @@ public Builder(AsyncHttpClientConfig prototype) { disableUrlEncodingForBoundRequests = prototype.isDisableUrlEncodingForBoundRequests(); ioThreadMultiplier = prototype.getIoThreadMultiplier(); maxRequestRetry = prototype.getMaxRequestRetry(); - allowSslConnectionPool = prototype.getAllowPoolingConnection(); + allowSslConnectionPool = prototype.isAllowPoolingConnection(); removeQueryParamOnRedirect = prototype.isRemoveQueryParamOnRedirect(); hostnameVerifier = prototype.getHostnameVerifier(); strict302Handling = prototype.isStrict302Handling(); diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java index 3e95e67ea6..1fe0aa5521 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java @@ -145,7 +145,7 @@ public static String parseCharset(String contentType) { } public static String keepAliveHeaderValue(AsyncHttpClientConfig config) { - return config.getAllowPoolingConnection() ? "keep-alive" : "close"; + return config.isAllowPoolingConnection() ? "keep-alive" : "close"; } public static int requestTimeout(AsyncHttpClientConfig config, Request request) { 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 f17d81a80c..34f9338bf7 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 @@ -147,7 +147,7 @@ public Channels(final AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig ChannelPool cp = nettyProviderConfig.getChannelPool(); if (cp == null) { - if (config.getAllowPoolingConnection()) { + if (config.isAllowPoolingConnection()) { cp = new DefaultChannelPool(config, nettyTimer); } else { cp = new NonChannelPool(); From 140296f1ee47286fb84f6e4814cbc31afcbb58cb Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 9 Jul 2014 16:48:14 +0200 Subject: [PATCH 0075/2020] Clean up --- .../AsyncHttpClientConfigDefaults.java | 2 +- .../FluentCaseInsensitiveStringsMap.java | 2 +- .../org/asynchttpclient/FluentStringsMap.java | 2 +- .../main/java/org/asynchttpclient/Realm.java | 2 +- .../asynchttpclient/RequestBuilderBase.java | 2 +- .../multipart/MultipartUtils.java | 2 +- .../oauth/OAuthSignatureCalculator.java | 2 +- .../providers/ResponseBase.java | 2 +- .../util/AsyncHttpProviderUtils.java | 2 +- .../util/AuthenticatorUtils.java | 2 +- .../org/asynchttpclient/util/DateUtil.java | 228 ------------------ .../org/asynchttpclient/util/DateUtils.java | 23 ++ .../util/{MiscUtil.java => MiscUtils.java} | 4 +- .../org/asynchttpclient/util/ProxyUtils.java | 2 +- .../asynchttpclient/util/QueryComputer.java | 2 +- .../async/AsyncProvidersBasicTest.java | 2 +- .../async/ParamEncodingTest.java | 2 +- .../async/PerRequestTimeoutTest.java | 2 +- .../asynchttpclient/async/PostWithQSTest.java | 2 +- .../async/QueryParametersTest.java | 2 +- .../providers/grizzly/GrizzlyResponse.java | 2 +- .../bodyhandler/ParamsBodyHandler.java | 2 +- .../grizzly/bodyhandler/PartsBodyHandler.java | 2 +- .../filters/AsyncHttpClientFilter.java | 2 +- .../websocket/GrizzlyWebSocketAdapter.java | 2 +- .../netty/channel/DefaultChannelPool.java | 2 +- .../netty/future/NettyResponseFuture.java | 2 +- .../providers/netty/handler/HttpProtocol.java | 2 +- .../netty/request/NettyRequestFactory.java | 2 +- .../IdleConnectionTimeoutTimerTask.java | 2 +- .../timeout/RequestTimeoutTimerTask.java | 2 +- .../netty/response/NettyResponse.java | 6 +- .../providers/netty/util/HttpUtil.java | 2 +- 33 files changed, 57 insertions(+), 262 deletions(-) delete mode 100644 api/src/main/java/org/asynchttpclient/util/DateUtil.java create mode 100644 api/src/main/java/org/asynchttpclient/util/DateUtils.java rename api/src/main/java/org/asynchttpclient/util/{MiscUtil.java => MiscUtils.java} (97%) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java index 4de9e2fcb6..fa05cec469 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java @@ -12,7 +12,7 @@ */ package org.asynchttpclient; -import static org.asynchttpclient.util.MiscUtil.getBoolean; +import static org.asynchttpclient.util.MiscUtils.getBoolean; import org.asynchttpclient.util.DefaultHostnameVerifier; diff --git a/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java b/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java index 0b9b1ad192..1cbe5baa58 100644 --- a/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java +++ b/api/src/main/java/org/asynchttpclient/FluentCaseInsensitiveStringsMap.java @@ -16,7 +16,7 @@ */ package org.asynchttpclient; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import java.util.ArrayList; import java.util.Arrays; diff --git a/api/src/main/java/org/asynchttpclient/FluentStringsMap.java b/api/src/main/java/org/asynchttpclient/FluentStringsMap.java index b295b7a1be..3e3c45f06e 100644 --- a/api/src/main/java/org/asynchttpclient/FluentStringsMap.java +++ b/api/src/main/java/org/asynchttpclient/FluentStringsMap.java @@ -16,7 +16,7 @@ */ package org.asynchttpclient; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import java.util.ArrayList; import java.util.Arrays; diff --git a/api/src/main/java/org/asynchttpclient/Realm.java b/api/src/main/java/org/asynchttpclient/Realm.java index 765896b5ca..45594ad4ec 100644 --- a/api/src/main/java/org/asynchttpclient/Realm.java +++ b/api/src/main/java/org/asynchttpclient/Realm.java @@ -16,7 +16,7 @@ */ package org.asynchttpclient; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.util.StandardCharsets; diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java index 3e0457c862..a303e86f7e 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -15,7 +15,7 @@ */ package org.asynchttpclient; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.cookie.Cookie; import org.asynchttpclient.multipart.Part; diff --git a/api/src/main/java/org/asynchttpclient/multipart/MultipartUtils.java b/api/src/main/java/org/asynchttpclient/multipart/MultipartUtils.java index 9abc5b67d6..b546d1f7ba 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/MultipartUtils.java +++ b/api/src/main/java/org/asynchttpclient/multipart/MultipartUtils.java @@ -17,7 +17,7 @@ import static org.asynchttpclient.multipart.Part.CRLF_BYTES; import static org.asynchttpclient.multipart.Part.EXTRA_BYTES; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.FluentCaseInsensitiveStringsMap; import org.asynchttpclient.util.StandardCharsets; diff --git a/api/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculator.java b/api/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculator.java index 5103a8be5b..6d376fa733 100644 --- a/api/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculator.java +++ b/api/src/main/java/org/asynchttpclient/oauth/OAuthSignatureCalculator.java @@ -16,7 +16,7 @@ */ package org.asynchttpclient.oauth; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.Param; import org.asynchttpclient.Request; diff --git a/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java b/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java index 66b39a2d4b..202a8bc494 100644 --- a/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java +++ b/api/src/main/java/org/asynchttpclient/providers/ResponseBase.java @@ -1,6 +1,6 @@ package org.asynchttpclient.providers; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.FluentCaseInsensitiveStringsMap; import org.asynchttpclient.HttpResponseBodyPart; diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java index 1fe0aa5521..b753ec9c6a 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java @@ -12,7 +12,7 @@ */ package org.asynchttpclient.util; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.AsyncHttpProvider; diff --git a/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java b/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java index f8957cb443..349b50cf14 100644 --- a/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java @@ -12,7 +12,7 @@ */ package org.asynchttpclient.util; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.ProxyServer; import org.asynchttpclient.Realm; diff --git a/api/src/main/java/org/asynchttpclient/util/DateUtil.java b/api/src/main/java/org/asynchttpclient/util/DateUtil.java deleted file mode 100644 index a41ec9deba..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/DateUtil.java +++ /dev/null @@ -1,228 +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.util; - -/* - * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/util/DateUtil.java,v 1.2 2004/12/24 20:36:13 olegk Exp $ - * $Revision: 480424 $ - * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $ - * - * ==================================================================== - * - * 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 - * . - * - */ - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.Iterator; -import java.util.Locale; -import java.util.TimeZone; - -/** - * A utility class for parsing and formatting HTTP dates as used in cookies and - * other headers. This class handles dates as defined by RFC 2616 section - * 3.3.1 as well as some other common non-standard formats. - * - * @author Christopher Brown - * @author Michael Becke - */ -public class DateUtil { - - /** - * Date format pattern used to parse HTTP date headers in RFC 1123 format. - */ - public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz"; - - /** - * Date format pattern used to parse HTTP date headers in RFC 1036 format. - */ - public static final String PATTERN_RFC1036 = "EEEE, dd-MMM-yy HH:mm:ss zzz"; - - /** - * Date format pattern used to parse HTTP date headers in ANSI C - * asctime() format. - */ - public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy"; - - private static final Collection DEFAULT_PATTERNS = Arrays.asList(new String[] { PATTERN_ASCTIME, PATTERN_RFC1036, - PATTERN_RFC1123 }); - - private static final Date DEFAULT_TWO_DIGIT_YEAR_START; - - static { - Calendar calendar = Calendar.getInstance(); - calendar.set(2000, Calendar.JANUARY, 1, 0, 0); - DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime(); - } - - private static final TimeZone GMT = TimeZone.getTimeZone("GMT"); - - /** - * Parses a date value. The formats used for parsing the date value are retrieved from - * the default http params. - * - * @param dateValue the date value to parse - * @return the parsed date - * @throws DateParseException if the value could not be parsed using any of the - * supported date formats - */ - public static Date parseDate(String dateValue) throws DateParseException { - return parseDate(dateValue, null, null); - } - - /** - * Parses the date value using the given date formats. - * - * @param dateValue the date value to parse - * @param dateFormats the date formats to use - * @return the parsed date - * @throws DateParseException if none of the dataFormats could parse the dateValue - */ - public static Date parseDate(String dateValue, Collection dateFormats) throws DateParseException { - return parseDate(dateValue, dateFormats, null); - } - - /** - * Parses the date value using the given date formats. - * - * @param dateValue the date value to parse - * @param dateFormats the date formats to use - * @param startDate During parsing, two digit years will be placed in the range - * startDate to startDate + 100 years. This value may - * be null. When null is given as a parameter, year - * 2000 will be used. - * @return the parsed date - * @throws DateParseException if none of the dataFormats could parse the dateValue - */ - public static Date parseDate(String dateValue, Collection dateFormats, Date startDate) throws DateParseException { - - if (dateValue == null) { - throw new NullPointerException("dateValue"); - } - if (dateFormats == null) { - dateFormats = DEFAULT_PATTERNS; - } - if (startDate == null) { - startDate = DEFAULT_TWO_DIGIT_YEAR_START; - } - // trim single quotes around date if present - // see issue #5279 - if (dateValue.length() > 1 && dateValue.startsWith("'") && dateValue.endsWith("'")) { - dateValue = dateValue.substring(1, dateValue.length() - 1); - } - - SimpleDateFormat dateParser = null; - Iterator formatIter = dateFormats.iterator(); - - while (formatIter.hasNext()) { - String format = formatIter.next(); - if (dateParser == null) { - dateParser = new SimpleDateFormat(format, Locale.US); - dateParser.setTimeZone(TimeZone.getTimeZone("GMT")); - dateParser.set2DigitYearStart(startDate); - } else { - dateParser.applyPattern(format); - } - try { - return dateParser.parse(dateValue); - } catch (ParseException pe) { - // ignore this exception, we will try the next format - } - } - - // we were unable to parse the date - throw new DateParseException("Unable to parse the date " + dateValue); - } - - /** - * Formats the given date according to the RFC 1123 pattern. - * - * @param date The date to format. - * @return An RFC 1123 formatted date string. - * @see #PATTERN_RFC1123 - */ - public static String formatDate(Date date) { - return formatDate(date, PATTERN_RFC1123); - } - - /** - * Formats the given date according to the specified pattern. The pattern - * must conform to that used by the {@link java.text.SimpleDateFormat simple date - * format} class. - * - * @param date The date to format. - * @param pattern The pattern to use for formatting the date. - * @return A formatted date string. - * @throws NullPointerException If the given date pattern is invalid. - * @see java.text.SimpleDateFormat - */ - public static String formatDate(Date date, String pattern) { - if (date == null) - throw new NullPointerException("date"); - if (pattern == null) - throw new NullPointerException("pattern"); - - SimpleDateFormat formatter = new SimpleDateFormat(pattern, Locale.US); - formatter.setTimeZone(GMT); - return formatter.format(date); - } - - /** - * This class should not be instantiated. - */ - private DateUtil() { - } - - public static class DateParseException extends Exception { - private static final long serialVersionUID = 1L; - - public DateParseException() { - super(); - } - - /** - * @param message the exception message - */ - public DateParseException(String message) { - super(message); - } - } - - public static long millisTime() { - return System.nanoTime() / 1000000; - } -} diff --git a/api/src/main/java/org/asynchttpclient/util/DateUtils.java b/api/src/main/java/org/asynchttpclient/util/DateUtils.java new file mode 100644 index 0000000000..c0b2d49c65 --- /dev/null +++ b/api/src/main/java/org/asynchttpclient/util/DateUtils.java @@ -0,0 +1,23 @@ +/* + * 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.util; + +public final class DateUtils { + + private DateUtils() { + } + + public static long millisTime() { + return System.nanoTime() / 1000000; + } +} diff --git a/api/src/main/java/org/asynchttpclient/util/MiscUtil.java b/api/src/main/java/org/asynchttpclient/util/MiscUtils.java similarity index 97% rename from api/src/main/java/org/asynchttpclient/util/MiscUtil.java rename to api/src/main/java/org/asynchttpclient/util/MiscUtils.java index 3cad6cfd37..db508f8513 100644 --- a/api/src/main/java/org/asynchttpclient/util/MiscUtil.java +++ b/api/src/main/java/org/asynchttpclient/util/MiscUtils.java @@ -15,9 +15,9 @@ import java.util.Collection; import java.util.Map; -public class MiscUtil { +public class MiscUtils { - private MiscUtil() { + private MiscUtils() { } public static boolean isNonEmpty(String string) { diff --git a/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java b/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java index 16fd18207f..b80f5e605e 100644 --- a/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/ProxyUtils.java @@ -12,7 +12,7 @@ */ package org.asynchttpclient.util; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.ProxyServer; diff --git a/api/src/main/java/org/asynchttpclient/util/QueryComputer.java b/api/src/main/java/org/asynchttpclient/util/QueryComputer.java index 058c070c0e..1480dd479d 100644 --- a/api/src/main/java/org/asynchttpclient/util/QueryComputer.java +++ b/api/src/main/java/org/asynchttpclient/util/QueryComputer.java @@ -14,7 +14,7 @@ import java.util.List; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.Param; diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java index 4e2906eea4..2b84c806e7 100755 --- a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java @@ -17,7 +17,7 @@ import static org.asynchttpclient.async.util.TestUtils.TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET; import static org.asynchttpclient.async.util.TestUtils.findFreePort; -import static org.asynchttpclient.util.DateUtil.millisTime; +import static org.asynchttpclient.util.DateUtils.millisTime; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; diff --git a/api/src/test/java/org/asynchttpclient/async/ParamEncodingTest.java b/api/src/test/java/org/asynchttpclient/async/ParamEncodingTest.java index f423831b01..4b5027f9a6 100644 --- a/api/src/test/java/org/asynchttpclient/async/ParamEncodingTest.java +++ b/api/src/test/java/org/asynchttpclient/async/ParamEncodingTest.java @@ -15,7 +15,7 @@ */ package org.asynchttpclient.async; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; diff --git a/api/src/test/java/org/asynchttpclient/async/PerRequestTimeoutTest.java b/api/src/test/java/org/asynchttpclient/async/PerRequestTimeoutTest.java index b0d2ef76bd..f6cc5764ec 100644 --- a/api/src/test/java/org/asynchttpclient/async/PerRequestTimeoutTest.java +++ b/api/src/test/java/org/asynchttpclient/async/PerRequestTimeoutTest.java @@ -15,7 +15,7 @@ */ package org.asynchttpclient.async; -import static org.asynchttpclient.util.DateUtil.millisTime; +import static org.asynchttpclient.util.DateUtils.millisTime; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; diff --git a/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java b/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java index fe8909583a..f5bb672154 100644 --- a/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java +++ b/api/src/test/java/org/asynchttpclient/async/PostWithQSTest.java @@ -15,7 +15,7 @@ */ package org.asynchttpclient.async; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; diff --git a/api/src/test/java/org/asynchttpclient/async/QueryParametersTest.java b/api/src/test/java/org/asynchttpclient/async/QueryParametersTest.java index cfd3b37db4..6bbd6c80ac 100644 --- a/api/src/test/java/org/asynchttpclient/async/QueryParametersTest.java +++ b/api/src/test/java/org/asynchttpclient/async/QueryParametersTest.java @@ -15,7 +15,7 @@ */ package org.asynchttpclient.async; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponse.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponse.java index e6664c7d7c..a1825f1b59 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponse.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/GrizzlyResponse.java @@ -13,7 +13,7 @@ package org.asynchttpclient.providers.grizzly; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.HttpResponseBodyPart; import org.asynchttpclient.HttpResponseHeaders; diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ParamsBodyHandler.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ParamsBodyHandler.java index afe14577ce..47941e1933 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ParamsBodyHandler.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/bodyhandler/ParamsBodyHandler.java @@ -13,7 +13,7 @@ package org.asynchttpclient.providers.grizzly.bodyhandler; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.Param; import org.asynchttpclient.Request; 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 11171d06a7..2c6e506165 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,7 +13,7 @@ package org.asynchttpclient.providers.grizzly.bodyhandler; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.Body; import org.asynchttpclient.Request; 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 80db9da2a9..c431bfc7ac 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 @@ -18,7 +18,7 @@ import static org.asynchttpclient.util.AsyncHttpProviderUtils.getAuthority; import static org.asynchttpclient.util.AuthenticatorUtils.computeBasicAuthentication; import static org.asynchttpclient.util.AuthenticatorUtils.computeDigestAuthentication; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.AsyncCompletionHandler; import org.asynchttpclient.AsyncHandler; diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyWebSocketAdapter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyWebSocketAdapter.java index 8374e89ab8..341c91ffda 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyWebSocketAdapter.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyWebSocketAdapter.java @@ -13,7 +13,7 @@ package org.asynchttpclient.providers.grizzly.websocket; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.websocket.WebSocket; import org.asynchttpclient.websocket.WebSocketListener; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java index 245723ff6e..54308e010a 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java @@ -12,7 +12,7 @@ */ package org.asynchttpclient.providers.netty.channel; -import static org.asynchttpclient.util.DateUtil.millisTime; +import static org.asynchttpclient.util.DateUtils.millisTime; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.providers.netty.DiscardEvent; 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 2d60a7443f..0af60186d3 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 @@ -15,7 +15,7 @@ */ package org.asynchttpclient.providers.netty.future; -import static org.asynchttpclient.util.DateUtil.millisTime; +import static org.asynchttpclient.util.DateUtils.millisTime; import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpClientConfig; 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 bb52ccf29a..3326fde184 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 @@ -20,7 +20,7 @@ import static io.netty.handler.codec.http.HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED; import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED; import static org.asynchttpclient.providers.netty.util.HttpUtil.isNTLM; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHandler.STATE; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java index a0dfad4bad..c62cbce5eb 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java @@ -19,7 +19,7 @@ import static org.asynchttpclient.providers.netty.util.HttpUtil.isSecure; import static org.asynchttpclient.providers.netty.util.HttpUtil.isWebSocket; import static org.asynchttpclient.util.AsyncHttpProviderUtils.DEFAULT_CHARSET; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.DefaultHttpRequest; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/IdleConnectionTimeoutTimerTask.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/IdleConnectionTimeoutTimerTask.java index 25a9d17dd8..bea473c9d8 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/IdleConnectionTimeoutTimerTask.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/IdleConnectionTimeoutTimerTask.java @@ -15,7 +15,7 @@ */ package org.asynchttpclient.providers.netty.request.timeout; -import static org.asynchttpclient.util.DateUtil.millisTime; +import static org.asynchttpclient.util.DateUtils.millisTime; import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java index 6035974823..13cae2eb43 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java @@ -15,7 +15,7 @@ */ package org.asynchttpclient.providers.netty.request.timeout; -import static org.asynchttpclient.util.DateUtil.millisTime; +import static org.asynchttpclient.util.DateUtils.millisTime; import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; 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 d3e1f95451..94eb15d70d 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 @@ -23,7 +23,7 @@ import org.asynchttpclient.date.TimeConverter; import org.asynchttpclient.providers.ResponseBase; import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.asynchttpclient.util.MiscUtil; +import org.asynchttpclient.util.MiscUtils; import io.netty.handler.codec.http.HttpHeaders; @@ -66,11 +66,11 @@ protected List buildCookies() { List setCookieHeaders = headers.getHeaders().get(HttpHeaders.Names.SET_COOKIE2); - if (!MiscUtil.isNonEmpty(setCookieHeaders)) { + if (!MiscUtils.isNonEmpty(setCookieHeaders)) { setCookieHeaders = headers.getHeaders().get(HttpHeaders.Names.SET_COOKIE); } - if (MiscUtil.isNonEmpty(setCookieHeaders)) { + if (MiscUtils.isNonEmpty(setCookieHeaders)) { List cookies = new ArrayList(); for (String value : setCookieHeaders) { Cookie c = CookieDecoder.decode(value, timeConverter); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtil.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtil.java index c94ed864ad..906bc7e8eb 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtil.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtil.java @@ -12,7 +12,7 @@ */ package org.asynchttpclient.providers.netty.util; -import static org.asynchttpclient.util.MiscUtil.isNonEmpty; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import java.util.List; From d159274839243f7876db873c94c21d0e8ced21a2 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 9 Jul 2014 16:50:39 +0200 Subject: [PATCH 0076/2020] Remove useless hasHeaders method --- api/src/main/java/org/asynchttpclient/Request.java | 8 -------- .../main/java/org/asynchttpclient/RequestBuilderBase.java | 5 ----- .../providers/grizzly/filters/AsyncHttpClientFilter.java | 2 +- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/Request.java b/api/src/main/java/org/asynchttpclient/Request.java index b182b2caf7..7b419385e8 100644 --- a/api/src/main/java/org/asynchttpclient/Request.java +++ b/api/src/main/java/org/asynchttpclient/Request.java @@ -191,12 +191,4 @@ public interface Request { String getBodyEncoding(); ConnectionPoolKeyStrategy getConnectionPoolKeyStrategy(); - - /** - * @return return true if request headers have been added, - * otherwise, returns false. - * - * @since 2.0 - */ - boolean hasHeaders(); } diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java index a303e86f7e..f00056c7a6 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -223,11 +223,6 @@ public ConnectionPoolKeyStrategy getConnectionPoolKeyStrategy() { return connectionPoolKeyStrategy; } - @Override - public boolean hasHeaders() { - return headers != null && !headers.isEmpty(); - } - @Override public List getQueryParams() { if (queryParams == null) 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 c431bfc7ac..4b9d7fdf19 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 @@ -481,7 +481,7 @@ private static void convertToUpgradeRequest(final HttpTxContext ctx) { private void addGeneralHeaders(final Request request, final HttpRequestPacket requestPacket) { - if (request.hasHeaders()) { + if (isNonEmpty(request.getHeaders())) { final FluentCaseInsensitiveStringsMap map = request.getHeaders(); for (final Map.Entry> entry : map.entrySet()) { final String headerName = entry.getKey(); From bd8bdbb010fe586e60cb810a96151dbcbcfb435e Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 9 Jul 2014 16:55:50 +0200 Subject: [PATCH 0077/2020] Have SimpleAHCTransferListener receive an UriComponents --- .../SimpleAsyncHttpClient.java | 24 +++++++++---------- .../simple/SimpleAHCTransferListener.java | 21 ++++++++-------- .../async/SimpleAsyncHttpClientTest.java | 21 ++++++++-------- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java index 6dc6036fa3..d601d4d82c 100644 --- a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java +++ b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java @@ -18,6 +18,7 @@ import org.asynchttpclient.resumable.ResumableIOExceptionFilter; import org.asynchttpclient.simple.HeaderMap; import org.asynchttpclient.simple.SimpleAHCTransferListener; +import org.asynchttpclient.uri.UriComponents; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -283,7 +284,7 @@ private Future execute(RequestBuilder rb, BodyConsumer bodyConsumer, T Request request = rb.build(); ProgressAsyncHandler handler = new BodyConsumerAsyncHandler(bodyConsumer, throwableHandler, errorDocumentBehaviour, - request.getUrl(), listener); + request.getURI(), listener); if (resumeEnabled && request.getMethod().equals("GET") && bodyConsumer != null && bodyConsumer instanceof ResumableBodyConsumer) { ResumableBodyConsumer fileBodyConsumer = (ResumableBodyConsumer) bodyConsumer; @@ -735,7 +736,7 @@ private final static class BodyConsumerAsyncHandler extends AsyncCompletionHandl private final BodyConsumer bodyConsumer; private final ThrowableHandler exceptionHandler; private final ErrorDocumentBehaviour errorDocumentBehaviour; - private final String url; + private final UriComponents uri; private final SimpleAHCTransferListener listener; private boolean accumulateBody = false; @@ -744,11 +745,11 @@ private final static class BodyConsumerAsyncHandler extends AsyncCompletionHandl private long total = -1; public BodyConsumerAsyncHandler(BodyConsumer bodyConsumer, ThrowableHandler exceptionHandler, - ErrorDocumentBehaviour errorDocumentBehaviour, String url, SimpleAHCTransferListener listener) { + ErrorDocumentBehaviour errorDocumentBehaviour, UriComponents uri, SimpleAHCTransferListener listener) { this.bodyConsumer = bodyConsumer; this.exceptionHandler = exceptionHandler; this.errorDocumentBehaviour = errorDocumentBehaviour; - this.url = url; + this.uri = uri; this.listener = listener; } @@ -846,13 +847,13 @@ private void calculateTotal(HttpResponseHeaders headers) { @Override public STATE onContentWriteProgress(long amount, long current, long total) { - fireSent(url, amount, current, total); + fireSent(uri, amount, current, total); return super.onContentWriteProgress(amount, current, total); } private void fireStatus(HttpResponseStatus status) { if (listener != null) { - listener.onStatus(url, status.getStatusCode(), status.getStatusText()); + listener.onStatus(uri, status.getStatusCode(), status.getStatusText()); } } @@ -862,27 +863,26 @@ private void fireReceived(HttpResponseBodyPart content) { amount += remaining; if (listener != null) { - listener.onBytesReceived(url, amount, remaining, total); + listener.onBytesReceived(uri, amount, remaining, total); } } private void fireHeaders(HttpResponseHeaders headers) { if (listener != null) { - listener.onHeaders(url, new HeaderMap(headers.getHeaders())); + listener.onHeaders(uri, new HeaderMap(headers.getHeaders())); } } - private void fireSent(String url, long amount, long current, long total) { + private void fireSent(UriComponents uri, long amount, long current, long total) { if (listener != null) { - listener.onBytesSent(url, amount, current, total); + listener.onBytesSent(uri, amount, current, total); } } private void fireCompleted(Response response) { if (listener != null) { - listener.onCompleted(url, response.getStatusCode(), response.getStatusText()); + listener.onCompleted(uri, response.getStatusCode(), response.getStatusText()); } } } - } diff --git a/api/src/main/java/org/asynchttpclient/simple/SimpleAHCTransferListener.java b/api/src/main/java/org/asynchttpclient/simple/SimpleAHCTransferListener.java index 51448ee38e..02702a0421 100644 --- a/api/src/main/java/org/asynchttpclient/simple/SimpleAHCTransferListener.java +++ b/api/src/main/java/org/asynchttpclient/simple/SimpleAHCTransferListener.java @@ -14,6 +14,7 @@ */ import org.asynchttpclient.SimpleAsyncHttpClient; +import org.asynchttpclient.uri.UriComponents; /** * A simple transfer listener for use with the {@link SimpleAsyncHttpClient}. @@ -29,51 +30,51 @@ public interface SimpleAHCTransferListener { /** * This method is called after the connection status is received. * - * @param url the url for the connection. + * @param uri the uri * @param statusCode the received status code. * @param statusText the received status text. */ - void onStatus(String url, int statusCode, String statusText); + void onStatus(UriComponents uri, int statusCode, String statusText); /** * This method is called after the response headers are received. * - * @param url the url for the connection. + * @param uri the uri * @param headers the received headers, never {@code null}. */ - void onHeaders(String url, HeaderMap headers); + void onHeaders(UriComponents uri, HeaderMap headers); /** * This method is called when bytes of the responses body are received. * - * @param url the url for the connection. + * @param uri the uri * @param amount the number of transferred bytes so far. * @param current the number of transferred bytes since the last call to this * method. * @param total the total number of bytes to be transferred. This is taken * from the Content-Length-header and may be unspecified (-1). */ - void onBytesReceived(String url, long amount, long current, long total); + void onBytesReceived(UriComponents uri, long amount, long current, long total); /** * This method is called when bytes are sent. * - * @param url the url for the connection. + * @param uri the uri * @param amount the number of transferred bytes so far. * @param current the number of transferred bytes since the last call to this * method. * @param total the total number of bytes to be transferred. This is taken * from the Content-Length-header and may be unspecified (-1). */ - void onBytesSent(String url, long amount, long current, long total); + void onBytesSent(UriComponents uri, long amount, long current, long total); /** * This method is called when the request is completed. * - * @param url the url for the connection. + * @param uri the uri * @param statusCode the received status code. * @param statusText the received status text. */ - void onCompleted(String url, int statusCode, String statusText); + void onCompleted(UriComponents uri, int statusCode, String statusText); } diff --git a/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java b/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java index 895144f989..c4aa459a2b 100644 --- a/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java +++ b/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java @@ -27,6 +27,7 @@ import org.asynchttpclient.multipart.ByteArrayPart; import org.asynchttpclient.simple.HeaderMap; import org.asynchttpclient.simple.SimpleAHCTransferListener; +import org.asynchttpclient.uri.UriComponents; import org.asynchttpclient.util.StandardCharsets; import org.testng.annotations.Test; @@ -176,19 +177,19 @@ public void testSimpleTransferListener() throws Exception { SimpleAHCTransferListener listener = new SimpleAHCTransferListener() { - public void onStatus(String url, int statusCode, String statusText) { + public void onStatus(UriComponents uri, int statusCode, String statusText) { try { assertEquals(statusCode, 200); - assertEquals(url, getTargetUrl()); + assertEquals(uri.toUrl(), getTargetUrl()); } catch (Error e) { errors.add(e); throw e; } } - public void onHeaders(String url, HeaderMap headers) { + public void onHeaders(UriComponents uri, HeaderMap headers) { try { - assertEquals(url, getTargetUrl()); + assertEquals(uri.toUrl(), getTargetUrl()); assertNotNull(headers); assertTrue(!headers.isEmpty()); assertEquals(headers.getFirstValue("X-Custom"), "custom"); @@ -198,19 +199,19 @@ public void onHeaders(String url, HeaderMap headers) { } } - public void onCompleted(String url, int statusCode, String statusText) { + public void onCompleted(UriComponents uri, int statusCode, String statusText) { try { assertEquals(statusCode, 200); - assertEquals(url, getTargetUrl()); + assertEquals(uri.toUrl(), getTargetUrl()); } catch (Error e) { errors.add(e); throw e; } } - public void onBytesSent(String url, long amount, long current, long total) { + public void onBytesSent(UriComponents uri, long amount, long current, long total) { try { - assertEquals(url, getTargetUrl()); + assertEquals(uri.toUrl(), getTargetUrl()); // FIXME Netty bug, see https://github.com/netty/netty/issues/1855 // assertEquals(total, MY_MESSAGE.getBytes().length); } catch (Error e) { @@ -219,9 +220,9 @@ public void onBytesSent(String url, long amount, long current, long total) { } } - public void onBytesReceived(String url, long amount, long current, long total) { + public void onBytesReceived(UriComponents uri, long amount, long current, long total) { try { - assertEquals(url, getTargetUrl()); + assertEquals(uri.toUrl(), getTargetUrl()); assertEquals(total, -1); } catch (Error e) { errors.add(e); From 6fa9742072a7154a844818911f8de84815afde1e Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 9 Jul 2014 18:11:56 +0200 Subject: [PATCH 0078/2020] Don't replace empty path with a / in Request, close #606 --- .../java/org/asynchttpclient/Request.java | 7 ----- .../asynchttpclient/RequestBuilderBase.java | 22 ++------------- .../resumable/ResumableAsyncHandler.java | 2 +- .../asynchttpclient/uri/UriComponents.java | 11 ++++++++ .../util/AsyncHttpProviderUtils.java | 18 ------------- .../async/AsyncProvidersBasicTest.java | 10 +++---- .../async/RequestBuilderTest.java | 10 +++---- .../resumable/ResumableAsyncHandlerTest.java | 4 +-- .../providers/grizzly/HttpTxContext.java | 20 +++++++------- .../filters/AsyncHttpClientFilter.java | 27 +++++++++---------- .../grizzly/filters/ProxyFilter.java | 2 +- .../statushandler/AuthorizationHandler.java | 2 +- .../statushandler/RedirectHandler.java | 2 +- .../providers/netty/handler/HttpProtocol.java | 14 +++++----- .../netty/request/NettyRequestFactory.java | 4 +-- 15 files changed, 61 insertions(+), 94 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/Request.java b/api/src/main/java/org/asynchttpclient/Request.java index 7b419385e8..597931456f 100644 --- a/api/src/main/java/org/asynchttpclient/Request.java +++ b/api/src/main/java/org/asynchttpclient/Request.java @@ -46,13 +46,6 @@ public interface Request { */ String getMethod(); - /** - * Return the decoded url - * - * @return the decoded url - */ - String getUrl(); - UriComponents getURI(); /** diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java index f00056c7a6..8629599db9 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -114,20 +114,6 @@ public InetAddress getLocalAddress() { return localAddress; } - private String removeTrailingSlash(UriComponents uri) { - String uriString = uri.toUrl(); - if (uriString.endsWith("/")) { - return uriString.substring(0, uriString.length() - 1); - } else { - return uriString; - } - } - - @Override - public String getUrl() { - return removeTrailingSlash(getURI()); - } - @Override public UriComponents getURI() { return uri; @@ -302,8 +288,6 @@ public T setUrl(String url) { } public T setURI(UriComponents uri) { - if (uri.getPath() == null) - throw new NullPointerException("uri.path"); request.uri = uri; return derived.cast(this); } @@ -602,14 +586,12 @@ private void computeRequestLength() { private void computeFinalUri() { if (request.uri == null) { - logger.debug("setUrl hasn't been invoked. Using http://localhost"); + logger.debug("setUrl hasn't been invoked. Using {}", DEFAULT_REQUEST_URL); request.uri = DEFAULT_REQUEST_URL; } AsyncHttpProviderUtils.validateSupportedScheme(request.uri); - // FIXME is that right? - String newPath = isNonEmpty(request.uri.getPath()) ? request.uri.getPath() : "/"; String newQuery = queryComputer.computeFullQueryString(request.uri.getQuery(), queryParams); request.uri = new UriComponents(// @@ -617,7 +599,7 @@ private void computeFinalUri() { request.uri.getUserInfo(),// request.uri.getHost(),// request.uri.getPort(),// - newPath,// + request.uri.getPath(),// newQuery); } diff --git a/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java b/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java index c7fd13d75a..f91cdeb313 100644 --- a/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java +++ b/api/src/main/java/org/asynchttpclient/resumable/ResumableAsyncHandler.java @@ -197,7 +197,7 @@ public AsyncHandler.STATE onHeadersReceived(HttpResponseHeaders headers) throws */ public Request adjustRequestRange(Request request) { - Long ri = resumableIndex.get(request.getUrl()); + Long ri = resumableIndex.get(request.getURI().toUrl()); if (ri != null) { byteTransferred.set(ri); } diff --git a/api/src/main/java/org/asynchttpclient/uri/UriComponents.java b/api/src/main/java/org/asynchttpclient/uri/UriComponents.java index 898557d6a5..5343071e61 100644 --- a/api/src/main/java/org/asynchttpclient/uri/UriComponents.java +++ b/api/src/main/java/org/asynchttpclient/uri/UriComponents.java @@ -12,6 +12,8 @@ */ package org.asynchttpclient.uri; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; + import java.net.URI; import java.net.URISyntaxException; @@ -64,6 +66,15 @@ public String getQuery() { return query; } + /** + * Convenient for HTTP layer when targeting server root + * + * @return the raw path or "/" if it's null + */ + public String getNonEmptyPath() { + return isNonEmpty(path) ? path : "/"; + } + public String getPath() { return path; } diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java index b753ec9c6a..dcd6164c9a 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java @@ -12,8 +12,6 @@ */ package org.asynchttpclient.util; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.AsyncHttpProvider; import org.asynchttpclient.HttpResponseBodyPart; @@ -49,22 +47,6 @@ public static final void validateSupportedScheme(UriComponents uri) { } } - public final static UriComponents createNonEmptyPathURI(String u) { - UriComponents uri = UriComponents.create(u); - validateSupportedScheme(uri); - - String path = uri.getPath(); - if (path == null) { - throw new IllegalArgumentException("The URI path, of the URI " + uri + ", must be non-null"); - } else if (isNonEmpty(path) && path.charAt(0) != '/') { - throw new IllegalArgumentException("The URI path, of the URI " + uri + ". must start with a '/'"); - } else if (!isNonEmpty(path)) { - return UriComponents.create(u + "/"); - } - - return uri; - } - /** * @param bodyParts NON EMPTY body part * @param maxLen diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java index 2b84c806e7..d8f8936d33 100755 --- a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java @@ -64,12 +64,16 @@ public abstract class AsyncProvidersBasicTest extends AbstractBasicTest { + protected abstract String acceptEncodingHeader(); + + protected abstract AsyncHttpProviderConfig getProviderConfig(); + @Test(groups = { "standalone", "default_provider", "async" }) public void asyncProviderEncodingTest() throws Exception { AsyncHttpClient client = getAsyncHttpClient(null); try { Request request = new RequestBuilder("GET").setUrl(getTargetUrl() + "?q=+%20x").build(); - assertEquals(request.getUrl(), getTargetUrl() + "?q=%20%20x"); + assertEquals(request.getURI().toUrl(), getTargetUrl() + "?q=%20%20x"); String url = client.executeRequest(request, new AsyncCompletionHandler() { @Override @@ -680,8 +684,6 @@ public Response onCompleted(Response response) throws Exception { client.close(); } } - - protected abstract String acceptEncodingHeader(); @Test(groups = { "standalone", "default_provider", "async" }) public void asyncDoPostBasicGZIPTest() throws Exception { @@ -1595,6 +1597,4 @@ public void mirrorByteTest() throws Exception { client.close(); } } - - protected abstract AsyncHttpProviderConfig getProviderConfig(); } diff --git a/api/src/test/java/org/asynchttpclient/async/RequestBuilderTest.java b/api/src/test/java/org/asynchttpclient/async/RequestBuilderTest.java index fe560de3ac..9086595c99 100644 --- a/api/src/test/java/org/asynchttpclient/async/RequestBuilderTest.java +++ b/api/src/test/java/org/asynchttpclient/async/RequestBuilderTest.java @@ -70,7 +70,7 @@ public void testEncodesQueryParameters() throws UnsupportedEncodingException { } String expValue = sb.toString(); Request request = builder.build(); - assertEquals(request.getUrl(), "http://example.com/?name=" + expValue); + assertEquals(request.getURI().toUrl(), "http://example.com/?name=" + expValue); } } @@ -83,7 +83,7 @@ public void testChaining() throws IOException, ExecutionException, InterruptedEx Request request2 = new RequestBuilder(request).build(); - assertEquals(request2.getUrl(), request.getUrl()); + assertEquals(request2.getURI(), request.getURI()); } @Test(groups = {"standalone", "default_provider"}) @@ -93,7 +93,7 @@ public void testParsesQueryParams() throws IOException, ExecutionException, Inte .addQueryParam("param2", "value2") .build(); - assertEquals(request.getUrl(), "http://foo.com/?param1=value1¶m2=value2"); + assertEquals(request.getURI().toUrl(), "http://foo.com/?param1=value1¶m2=value2"); List params = request.getQueryParams(); assertEquals(params.size(), 2); assertEquals(params.get(0), new Param("param1", "value1")); @@ -104,14 +104,14 @@ public void testParsesQueryParams() throws IOException, ExecutionException, Inte public void testUserProvidedRequestMethod() { Request req = new RequestBuilder("ABC").setUrl("http://foo.com/").build(); assertEquals(req.getMethod(), "ABC"); - assertEquals(req.getUrl(), "http://foo.com/"); + assertEquals(req.getURI().toUrl(), "http://foo.com/"); } @Test(groups = {"standalone", "default_provider"}) public void testPercentageEncodedUserInfo() { final Request req = new RequestBuilder("GET").setUrl("http://hello:wor%20ld@foo.com/").build(); assertEquals(req.getMethod(), "GET"); - assertEquals(req.getUrl(), "http://hello:wor%20ld@foo.com/"); + assertEquals(req.getURI().toUrl(), "http://hello:wor%20ld@foo.com/"); } @Test(groups = {"standalone", "default_provider"}) diff --git a/api/src/test/java/org/asynchttpclient/resumable/ResumableAsyncHandlerTest.java b/api/src/test/java/org/asynchttpclient/resumable/ResumableAsyncHandlerTest.java index 5e3cc094aa..196b2e4df6 100644 --- a/api/src/test/java/org/asynchttpclient/resumable/ResumableAsyncHandlerTest.java +++ b/api/src/test/java/org/asynchttpclient/resumable/ResumableAsyncHandlerTest.java @@ -31,13 +31,13 @@ public void testAdjustRange() { ResumableAsyncHandler h = new ResumableAsyncHandler(proc); Request request = new RequestBuilder("GET").setUrl("http://test/url").build(); Request newRequest = h.adjustRequestRange(request); - assertEquals(newRequest.getUrl(), request.getUrl()); + assertEquals(newRequest.getURI(), request.getURI()); String rangeHeader = newRequest.getHeaders().getFirstValue("Range"); assertNull(rangeHeader); proc.put("http://test/url", 5000); newRequest = h.adjustRequestRange(request); - assertEquals(newRequest.getUrl(), request.getUrl()); + assertEquals(newRequest.getURI(), request.getURI()); rangeHeader = newRequest.getHeaders().getFirstValue("Range"); assertEquals(rangeHeader, "bytes=5000-"); } 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 308558b228..a69dad744b 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,6 +18,7 @@ import org.asynchttpclient.providers.grizzly.bodyhandler.BodyHandler; import org.asynchttpclient.providers.grizzly.statushandler.StatusHandler; import org.asynchttpclient.providers.grizzly.statushandler.StatusHandler.InvocationStatus; +import org.asynchttpclient.uri.UriComponents; import org.asynchttpclient.util.AsyncHttpProviderUtils; import org.asynchttpclient.websocket.WebSocket; import org.glassfish.grizzly.CloseListener; @@ -33,6 +34,7 @@ import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; + import org.asynchttpclient.providers.grizzly.filters.events.GracefulCloseEvent; import org.glassfish.grizzly.Connection; import org.glassfish.grizzly.filterchain.FilterChain; @@ -50,7 +52,7 @@ public final class HttpTxContext { private final GrizzlyAsyncHttpProvider provider; private Request request; - private String requestUrl; + private UriComponents requestUri; private final AsyncHandler handler; private BodyHandler bodyHandler; private StatusHandler statusHandler; @@ -61,7 +63,7 @@ public final class HttpTxContext { private final AtomicLong totalBodyWritten = new AtomicLong(); private AsyncHandler.STATE currentState; - private String wsRequestURI; + private UriComponents wsRequestURI; private boolean isWSRequest; private HandShake handshake; private ProtocolHandler protocolHandler; @@ -97,7 +99,7 @@ private HttpTxContext(final GrizzlyAsyncHttpProvider provider, final GrizzlyResp this.handler = handler; redirectsAllowed = this.provider.getClientConfig().isFollowRedirect(); maxRedirectCount = this.provider.getClientConfig().getMaxRedirects(); - this.requestUrl = request.getUrl(); + this.requestUri = request.getURI(); } // ---------------------------------------------------------- Public Methods @@ -160,12 +162,12 @@ public void setRequest(Request request) { this.request = request; } - public String getRequestUrl() { - return requestUrl; + public UriComponents getRequestUri() { + return requestUri; } - public void setRequestUrl(String requestUrl) { - this.requestUrl = requestUrl; + public void setRequestUri(UriComponents requestUri) { + this.requestUri = requestUri; } public AsyncHandler getHandler() { @@ -232,11 +234,11 @@ public void setCurrentState(AsyncHandler.STATE currentState) { this.currentState = currentState; } - public String getWsRequestURI() { + public UriComponents getWsRequestURI() { return wsRequestURI; } - public void setWsRequestURI(String wsRequestURI) { + public void setWsRequestURI(UriComponents wsRequestURI) { this.wsRequestURI = wsRequestURI; } 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 4b9d7fdf19..5d7a3009ba 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 @@ -218,7 +218,7 @@ private boolean sendAsGrizzlyRequest( return true; } - if (isUpgradeRequest(httpTxContext.getHandler()) && isWSRequest(httpTxContext.getRequestUrl())) { + if (isUpgradeRequest(httpTxContext.getHandler()) && isWSRequest(httpTxContext.getRequestUri())) { httpTxContext.setWSRequest(true); convertToUpgradeRequest(httpTxContext); } @@ -238,7 +238,7 @@ private boolean sendAsGrizzlyRequest( final int port = uri.getPort(); requestPacket.setRequestURI(uri.getHost() + ':' + (port == -1 ? 443 : port)); } else { - requestPacket.setRequestURI(uri.getPath()); + requestPacket.setRequestURI(uri.getNonEmptyPath()); } final BodyHandler bodyHandler = isPayloadAllowed(method) ? @@ -257,7 +257,7 @@ private boolean sendAsGrizzlyRequest( if (httpTxContext.isWSRequest()) { try { - final URI wsURI = new URI(httpTxContext.getWsRequestURI()); + final URI wsURI = httpTxContext.getWsRequestURI().toURI(); httpTxContext.setProtocolHandler(Version.RFC6455.createHandler(true)); httpTxContext.setHandshake(httpTxContext.getProtocolHandler().createHandShake(wsURI)); requestPacket = (HttpRequestPacket) httpTxContext.getHandshake().composeHeaders().getHttpHeader(); @@ -462,21 +462,18 @@ private static boolean isUpgradeRequest(final AsyncHandler handler) { return (handler instanceof UpgradeHandler); } - private static boolean isWSRequest(final String requestUri) { - return (requestUri.charAt(0) == 'w' && requestUri.charAt(1) == 's'); + private static boolean isWSRequest(final UriComponents requestUri) { + return requestUri.getScheme().startsWith("ws"); } private static void convertToUpgradeRequest(final HttpTxContext ctx) { - final int colonIdx = ctx.getRequestUrl().indexOf(':'); - - if (colonIdx < 2 || colonIdx > 3) { - throw new IllegalArgumentException("Invalid websocket URL: " + ctx.getRequestUrl()); - } - - final StringBuilder sb = new StringBuilder(ctx.getRequestUrl()); - sb.replace(0, colonIdx, ((colonIdx == 2) ? "http" : "https")); - ctx.setWsRequestURI(ctx.getRequestUrl()); - ctx.setRequestUrl(sb.toString()); + + UriComponents originalUri = ctx.getRequestUri(); + String newScheme = originalUri.getScheme().equalsIgnoreCase("https")? "wss" : "ws"; + ctx.setRequestUri(originalUri.withNewScheme(newScheme)); + + ctx.setWsRequestURI(ctx.getRequestUri()); + ctx.setRequestUri(originalUri.withNewScheme(newScheme)); } private void addGeneralHeaders(final Request request, final HttpRequestPacket requestPacket) { 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 5178d7dd4c..f233d40e1f 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 @@ -62,7 +62,7 @@ public NextAction handleWrite(FilterChainContext ctx) throws IOException { assert (context != null); Request req = context.getRequest(); if (!secure) { - request.setRequestURI(req.getURI().toString()); + request.setRequestURI(req.getURI().toUrl()); } addProxyHeaders(getRealm(req), request); return ctx.getInvokeAction(); 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 8bebb92d5d..191be251d8 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 @@ -67,7 +67,7 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpT responsePacket.setSkipRemainder(true); // ignore the remainder of the response final Request req = httpTransactionContext.getRequest(); - realm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(req.getURI().getPath()) + realm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(req.getURI().getNonEmptyPath()) .setMethodName(req.getMethod()).setUsePreemptiveAuth(true).parseWWWAuthenticateHeader(auth).build(); String lowerCaseAuth = auth.toLowerCase(Locale.ENGLISH); if (lowerCaseAuth.startsWith("basic")) { 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 a59d23876b..d7572e2ace 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 @@ -75,7 +75,7 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpT httpTransactionContext.setFuture(null); newContext.setInvocationStatus(CONTINUE); newContext.setRequest(requestToSend); - newContext.setRequestUrl(requestToSend.getUrl()); + newContext.setRequestUri(requestToSend.getURI()); HttpTxContext.set(ctx, newContext); httpTransactionContext.getProvider().execute(c, requestToSend, newContext.getHandler(), newContext.getFuture(), newContext); return 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 3326fde184..b7caaa25cb 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 @@ -87,7 +87,7 @@ private Realm kerberosChallenge(List proxyAuth, Request request, ProxySe headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader); return newRealmBuilder(realm)// - .setUri(uri.getPath())// + .setUri(uri.getNonEmptyPath())// .setMethodName(request.getMethod())// .setScheme(Realm.AuthScheme.KERBEROS)// .build(); @@ -127,7 +127,7 @@ private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer p future.getAndSetAuth(false); return newRealmBuilder(realm)// .setScheme(realm.getAuthScheme())// - .setUri(uri.getPath())// + .setUri(uri.getNonEmptyPath())// .setMethodName(request.getMethod())// .setNtlmMessageType2Received(true)// .build(); @@ -137,7 +137,7 @@ private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer p Realm.AuthScheme authScheme = realm != null ? realm.getAuthScheme() : Realm.AuthScheme.NTLM; return newRealmBuilder(realm)// .setScheme(authScheme)// - .setUri(request.getURI().getPath())// + .setUri(request.getURI().getNonEmptyPath())// .setMethodName(request.getMethod())// .build(); } @@ -153,7 +153,7 @@ private Realm ntlmProxyChallenge(List wwwAuth, Request request, ProxySer return newRealmBuilder(realm)// // .setScheme(realm.getAuthScheme()) - .setUri(request.getURI().getPath())// + .setUri(request.getURI().getNonEmptyPath())// .setMethodName(request.getMethod()).build(); } @@ -214,9 +214,9 @@ private final String computeRealmURI(Realm realm, UriComponents requestURI) { } } else { if (realm.isOmitQuery() || !isNonEmpty(requestURI.getQuery())) { - return requestURI.getPath(); + return requestURI.getNonEmptyPath(); } else { - return requestURI.getPath() + "?" + requestURI.getQuery(); + return requestURI.getNonEmptyPath() + "?" + requestURI.getQuery(); } } } @@ -241,7 +241,7 @@ private boolean handleUnauthorizedAndExit(int statusCode, Realm realm, final Req return true; } } else { - newRealm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(request.getURI().getPath()) + newRealm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(request.getURI().getNonEmptyPath()) .setMethodName(request.getMethod()).setUsePreemptiveAuth(true) .parseWWWAuthenticateHeader(authenticateHeaders.get(0)).build(); } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java index c62cbce5eb..eee4f6e6e3 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java @@ -79,10 +79,10 @@ else if (proxyServer != null && !(isSecure(uri) && config.isUseRelativeURIsWithS return uri.toString(); else if (uri.getQuery() != null) - return uri.getPath() + "?" + uri.getQuery(); + return uri.getNonEmptyPath() + "?" + uri.getQuery(); else - return uri.getPath(); + return uri.getNonEmptyPath(); } private String hostHeader(Request request, UriComponents uri, Realm realm) { From 6fcbe4220b89773e8b95ee3d3895496dcb430ffa Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 9 Jul 2014 20:54:25 +0200 Subject: [PATCH 0079/2020] Move UriComponents.getNonEmptyPath to AsyncHttpProviderUtils --- .../asynchttpclient/uri/UriComponents.java | 9 ------ .../util/AsyncHttpProviderUtils.java | 11 +++++++ .../filters/AsyncHttpClientFilter.java | 3 +- .../statushandler/AuthorizationHandler.java | 3 +- .../providers/netty/handler/HttpProtocol.java | 29 ++++++++++++------- .../netty/request/NettyRequestFactory.java | 13 +++++---- 6 files changed, 41 insertions(+), 27 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/uri/UriComponents.java b/api/src/main/java/org/asynchttpclient/uri/UriComponents.java index 5343071e61..cc9b6f08e8 100644 --- a/api/src/main/java/org/asynchttpclient/uri/UriComponents.java +++ b/api/src/main/java/org/asynchttpclient/uri/UriComponents.java @@ -66,15 +66,6 @@ public String getQuery() { return query; } - /** - * Convenient for HTTP layer when targeting server root - * - * @return the raw path or "/" if it's null - */ - public String getNonEmptyPath() { - return isNonEmpty(path) ? path : "/"; - } - public String getPath() { return path; } diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java index dcd6164c9a..49ed3a4f45 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java @@ -12,6 +12,8 @@ */ package org.asynchttpclient.util; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; + import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.AsyncHttpProvider; import org.asynchttpclient.HttpResponseBodyPart; @@ -102,6 +104,15 @@ public final static int getDefaultPort(UriComponents uri) { return port; } + /** + * Convenient for HTTP layer when targeting server root + * + * @return the raw path or "/" if it's null + */ + public final static String getNonEmptyPath(UriComponents uri) { + return isNonEmpty(uri.getPath()) ? uri.getPath() : "/"; + } + public static String constructUserAgent(Class httpProvider, AsyncHttpClientConfig config) { return new StringBuilder("AHC (").append(httpProvider.getSimpleName()).append(" - ").append(System.getProperty("os.name")) .append(" - ").append(System.getProperty("os.version")).append(" - ").append(System.getProperty("java.version")) 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 5d7a3009ba..dfca7a3805 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 @@ -16,6 +16,7 @@ import static org.asynchttpclient.providers.grizzly.filters.SwitchingSSLFilter.getHandshakeError; import static org.asynchttpclient.providers.grizzly.GrizzlyAsyncHttpProvider.NTLM_ENGINE; import static org.asynchttpclient.util.AsyncHttpProviderUtils.getAuthority; +import static org.asynchttpclient.util.AsyncHttpProviderUtils.getNonEmptyPath; import static org.asynchttpclient.util.AuthenticatorUtils.computeBasicAuthentication; import static org.asynchttpclient.util.AuthenticatorUtils.computeDigestAuthentication; import static org.asynchttpclient.util.MiscUtils.isNonEmpty; @@ -238,7 +239,7 @@ private boolean sendAsGrizzlyRequest( final int port = uri.getPort(); requestPacket.setRequestURI(uri.getHost() + ':' + (port == -1 ? 443 : port)); } else { - requestPacket.setRequestURI(uri.getNonEmptyPath()); + requestPacket.setRequestURI(getNonEmptyPath(uri)); } final BodyHandler bodyHandler = isPayloadAllowed(method) ? 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 191be251d8..16416b73dc 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 @@ -13,6 +13,7 @@ package org.asynchttpclient.providers.grizzly.statushandler; +import static org.asynchttpclient.util.AsyncHttpProviderUtils.getNonEmptyPath; import static org.asynchttpclient.providers.grizzly.statushandler.StatusHandler.InvocationStatus.STOP; import org.asynchttpclient.Realm; @@ -67,7 +68,7 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpT responsePacket.setSkipRemainder(true); // ignore the remainder of the response final Request req = httpTransactionContext.getRequest(); - realm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(req.getURI().getNonEmptyPath()) + realm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(getNonEmptyPath(req.getURI())) .setMethodName(req.getMethod()).setUsePreemptiveAuth(true).parseWWWAuthenticateHeader(auth).build(); String lowerCaseAuth = auth.toLowerCase(Locale.ENGLISH); if (lowerCaseAuth.startsWith("basic")) { 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 b7caaa25cb..0694b5a510 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 @@ -20,6 +20,7 @@ import static io.netty.handler.codec.http.HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED; import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED; import static org.asynchttpclient.providers.netty.util.HttpUtil.isNTLM; +import static org.asynchttpclient.util.AsyncHttpProviderUtils.getNonEmptyPath; import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.AsyncHandler; @@ -87,7 +88,7 @@ private Realm kerberosChallenge(List proxyAuth, Request request, ProxySe headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader); return newRealmBuilder(realm)// - .setUri(uri.getNonEmptyPath())// + .setUri(getNonEmptyPath(uri))// .setMethodName(request.getMethod())// .setScheme(Realm.AuthScheme.KERBEROS)// .build(); @@ -127,7 +128,7 @@ private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer p future.getAndSetAuth(false); return newRealmBuilder(realm)// .setScheme(realm.getAuthScheme())// - .setUri(uri.getNonEmptyPath())// + .setUri(getNonEmptyPath(uri))// .setMethodName(request.getMethod())// .setNtlmMessageType2Received(true)// .build(); @@ -137,7 +138,7 @@ private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer p Realm.AuthScheme authScheme = realm != null ? realm.getAuthScheme() : Realm.AuthScheme.NTLM; return newRealmBuilder(realm)// .setScheme(authScheme)// - .setUri(request.getURI().getNonEmptyPath())// + .setUri(getNonEmptyPath(request.getURI()))// .setMethodName(request.getMethod())// .build(); } @@ -153,7 +154,7 @@ private Realm ntlmProxyChallenge(List wwwAuth, Request request, ProxySer return newRealmBuilder(realm)// // .setScheme(realm.getAuthScheme()) - .setUri(request.getURI().getNonEmptyPath())// + .setUri(getNonEmptyPath(request.getURI()))// .setMethodName(request.getMethod()).build(); } @@ -208,15 +209,16 @@ private void markAsDone(NettyResponseFuture future, final Channel channel) { private final String computeRealmURI(Realm realm, UriComponents requestURI) { if (realm.isUseAbsoluteURI()) { if (realm.isOmitQuery() && isNonEmpty(requestURI.getQuery())) { - return requestURI.withNewQuery(null).toString(); + return requestURI.withNewQuery(null).toUrl(); } else { - return requestURI.toString(); + return requestURI.toUrl(); } } else { + String path = getNonEmptyPath(requestURI); if (realm.isOmitQuery() || !isNonEmpty(requestURI.getQuery())) { - return requestURI.getNonEmptyPath(); + return path; } else { - return requestURI.getNonEmptyPath() + "?" + requestURI.getQuery(); + return path + "?" + requestURI.getQuery(); } } } @@ -241,9 +243,14 @@ private boolean handleUnauthorizedAndExit(int statusCode, Realm realm, final Req return true; } } else { - newRealm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(request.getURI().getNonEmptyPath()) - .setMethodName(request.getMethod()).setUsePreemptiveAuth(true) - .parseWWWAuthenticateHeader(authenticateHeaders.get(0)).build(); + newRealm = new Realm.RealmBuilder()// + .clone(realm)// + .setScheme(realm.getAuthScheme())// + .setUri(getNonEmptyPath(request.getURI()))// + .setMethodName(request.getMethod())// + .setUsePreemptiveAuth(true)// + .parseWWWAuthenticateHeader(authenticateHeaders.get(0))// + .build(); } String realmURI = computeRealmURI(newRealm, request.getURI()); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java index eee4f6e6e3..d777c9d1c6 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java @@ -19,6 +19,7 @@ import static org.asynchttpclient.providers.netty.util.HttpUtil.isSecure; import static org.asynchttpclient.providers.netty.util.HttpUtil.isWebSocket; import static org.asynchttpclient.util.AsyncHttpProviderUtils.DEFAULT_CHARSET; +import static org.asynchttpclient.util.AsyncHttpProviderUtils.getNonEmptyPath; import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.DefaultFullHttpRequest; @@ -78,11 +79,13 @@ private String requestUri(UriComponents uri, ProxyServer proxyServer, HttpMethod else if (proxyServer != null && !(isSecure(uri) && config.isUseRelativeURIsWithSSLProxies())) return uri.toString(); - else if (uri.getQuery() != null) - return uri.getNonEmptyPath() + "?" + uri.getQuery(); - - else - return uri.getNonEmptyPath(); + else { + String path = getNonEmptyPath(uri); + if (isNonEmpty(uri.getQuery())) + return path + "?" + uri.getQuery(); + else + return path; + } } private String hostHeader(Request request, UriComponents uri, Realm realm) { From d756d2606f2033690b69d5c024b042c90c520cf3 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 9 Jul 2014 23:11:11 +0200 Subject: [PATCH 0080/2020] Minor fix --- .../providers/grizzly/filters/AsyncHttpClientFilter.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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 dfca7a3805..f1be2da91d 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 @@ -470,10 +470,8 @@ private static boolean isWSRequest(final UriComponents requestUri) { private static void convertToUpgradeRequest(final HttpTxContext ctx) { UriComponents originalUri = ctx.getRequestUri(); - String newScheme = originalUri.getScheme().equalsIgnoreCase("https")? "wss" : "ws"; - ctx.setRequestUri(originalUri.withNewScheme(newScheme)); - - ctx.setWsRequestURI(ctx.getRequestUri()); + String newScheme = originalUri.getScheme().equalsIgnoreCase("https") ? "wss" : "ws"; + ctx.setWsRequestURI(originalUri); ctx.setRequestUri(originalUri.withNewScheme(newScheme)); } From b65a7a3adfdb0a59db779b0ec6b3dbbf3a7bf7ad Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 9 Jul 2014 23:32:33 +0200 Subject: [PATCH 0081/2020] Make all providers honor Realm.isOmitQuery and isUseAbsoluteURI, close #607 --- .../main/java/org/asynchttpclient/Realm.java | 13 +++---- .../asynchttpclient/uri/UriComponents.java | 9 +++++ .../util/AuthenticatorUtils.java | 14 +++++++- .../java/org/asynchttpclient/RealmTest.java | 5 +-- .../statushandler/AuthorizationHandler.java | 3 +- .../ProxyAuthorizationHandler.java | 2 +- .../providers/netty/handler/HttpProtocol.java | 34 +++++-------------- 7 files changed, 42 insertions(+), 38 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/Realm.java b/api/src/main/java/org/asynchttpclient/Realm.java index 45594ad4ec..bc71d0d3d3 100644 --- a/api/src/main/java/org/asynchttpclient/Realm.java +++ b/api/src/main/java/org/asynchttpclient/Realm.java @@ -18,6 +18,7 @@ import static org.asynchttpclient.util.MiscUtils.isNonEmpty; +import org.asynchttpclient.uri.UriComponents; import org.asynchttpclient.util.StandardCharsets; import java.nio.charset.Charset; @@ -42,7 +43,7 @@ public class Realm { private final String qop; private final String nc; private final String cnonce; - private final String uri; + private final UriComponents uri; private final String methodName; private final boolean usePreemptiveAuth; private final String enc; @@ -58,7 +59,7 @@ public enum AuthScheme { } private Realm(AuthScheme scheme, String principal, String password, String realmName, String nonce, String algorithm, String response, - String qop, String nc, String cnonce, String uri, String method, boolean usePreemptiveAuth, String ntlmDomain, String enc, + String qop, String nc, String cnonce, UriComponents uri, String method, boolean usePreemptiveAuth, String ntlmDomain, String enc, String host, boolean messageType2Received, String opaque, boolean useAbsoluteURI, boolean omitQuery) { this.principal = principal; @@ -133,7 +134,7 @@ public String getCnonce() { return cnonce; } - public String getUri() { + public UriComponents getUri() { return uri; } @@ -268,7 +269,7 @@ public static class RealmBuilder { private String qop = "auth"; private String nc = "00000001"; private String cnonce = ""; - private String uri = ""; + private UriComponents uri; private String methodName = "GET"; private boolean usePreemptive = false; private String domain = System.getProperty("http.auth.ntlm.domain", ""); @@ -386,11 +387,11 @@ public RealmBuilder setNc(String nc) { return this; } - public String getUri() { + public UriComponents getUri() { return uri; } - public RealmBuilder setUri(String uri) { + public RealmBuilder setUri(UriComponents uri) { this.uri = uri; return this; } diff --git a/api/src/main/java/org/asynchttpclient/uri/UriComponents.java b/api/src/main/java/org/asynchttpclient/uri/UriComponents.java index cc9b6f08e8..3fe3c224c6 100644 --- a/api/src/main/java/org/asynchttpclient/uri/UriComponents.java +++ b/api/src/main/java/org/asynchttpclient/uri/UriComponents.java @@ -121,6 +121,15 @@ public UriComponents withNewScheme(String newScheme) { query); } + public UriComponents withNewPath(String newPath) { + return new UriComponents(scheme,// + userInfo,// + host,// + port,// + newPath,// + query); + } + public UriComponents withNewQuery(String newQuery) { return new UriComponents(scheme,// userInfo,// diff --git a/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java b/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java index 349b50cf14..c147355612 100644 --- a/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java @@ -16,6 +16,7 @@ import org.asynchttpclient.ProxyServer; import org.asynchttpclient.Realm; +import org.asynchttpclient.uri.UriComponents; import java.security.NoSuchAlgorithmException; @@ -31,13 +32,24 @@ public static String computeBasicAuthentication(ProxyServer proxyServer) { return "Basic " + Base64.encode(s.getBytes(proxyServer.getCharset())); } + private static String computeRealmURI(Realm realm) { + UriComponents uri = realm.getUri(); + boolean omitQuery = realm.isOmitQuery() && MiscUtils.isNonEmpty(uri.getQuery()); + if (realm.isUseAbsoluteURI()) { + return omitQuery ? uri.withNewQuery(null).toUrl() : uri.toUrl(); + } else { + String path = uri.getPath(); + return omitQuery ? path : path + "?" + uri.getQuery(); + } + } + public static String computeDigestAuthentication(Realm realm) throws NoSuchAlgorithmException { StringBuilder builder = new StringBuilder().append("Digest "); construct(builder, "username", realm.getPrincipal()); construct(builder, "realm", realm.getRealmName()); construct(builder, "nonce", realm.getNonce()); - construct(builder, "uri", realm.getUri()); + construct(builder, "uri", computeRealmURI(realm)); builder.append("algorithm").append('=').append(realm.getAlgorithm()).append(", "); construct(builder, "response", realm.getResponse()); diff --git a/api/src/test/java/org/asynchttpclient/RealmTest.java b/api/src/test/java/org/asynchttpclient/RealmTest.java index 11873f0126..23315a92e6 100644 --- a/api/src/test/java/org/asynchttpclient/RealmTest.java +++ b/api/src/test/java/org/asynchttpclient/RealmTest.java @@ -16,6 +16,7 @@ import org.asynchttpclient.Realm.AuthScheme; import org.asynchttpclient.Realm.RealmBuilder; +import org.asynchttpclient.uri.UriComponents; import org.asynchttpclient.util.StandardCharsets; import org.testng.annotations.Test; @@ -60,7 +61,7 @@ private void testOldDigest(String qop) { String realm = "realm"; String nonce = "nonce"; String method = "GET"; - String uri = "/foo"; + UriComponents uri = UriComponents.create("http://ahc.io/foo"); RealmBuilder builder = new RealmBuilder(); builder.setPrincipal(user).setPassword(pass); builder.setNonce(nonce); @@ -85,7 +86,7 @@ public void testStrongDigest() { String realm = "realm"; String nonce = "nonce"; String method = "GET"; - String uri = "/foo"; + UriComponents uri = UriComponents.create("http://ahc.io/foo"); String qop = "auth"; RealmBuilder builder = new RealmBuilder(); builder.setPrincipal(user).setPassword(pass); 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 16416b73dc..fb12218ba3 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 @@ -13,7 +13,6 @@ package org.asynchttpclient.providers.grizzly.statushandler; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getNonEmptyPath; import static org.asynchttpclient.providers.grizzly.statushandler.StatusHandler.InvocationStatus.STOP; import org.asynchttpclient.Realm; @@ -68,7 +67,7 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpT responsePacket.setSkipRemainder(true); // ignore the remainder of the response final Request req = httpTransactionContext.getRequest(); - realm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(getNonEmptyPath(req.getURI())) + realm = new Realm.RealmBuilder().clone(realm).setScheme(realm.getAuthScheme()).setUri(req.getURI()) .setMethodName(req.getMethod()).setUsePreemptiveAuth(true).parseWWWAuthenticateHeader(auth).build(); String lowerCaseAuth = auth.toLowerCase(Locale.ENGLISH); if (lowerCaseAuth.startsWith("basic")) { 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 1e090e69ea..0189796e64 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 @@ -60,7 +60,7 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpT .select(req.getURI()); String principal = proxyServer.getPrincipal(); String password = proxyServer.getPassword(); - Realm realm = new Realm.RealmBuilder().setPrincipal(principal).setPassword(password).setUri("/") + Realm realm = new Realm.RealmBuilder().setPrincipal(principal).setPassword(password).setUri(req.getURI().withNewPath("/").withNewQuery(null)) .setMethodName(Method.CONNECT.getMethodString()).setUsePreemptiveAuth(true).parseProxyAuthenticateHeader(proxyAuth).build(); String proxyAuthLowerCase = proxyAuth.toLowerCase(Locale.ENGLISH); if (proxyAuthLowerCase.startsWith("basic")) { 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 0694b5a510..0a58c32937 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 @@ -88,7 +88,7 @@ private Realm kerberosChallenge(List proxyAuth, Request request, ProxySe headers.add(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challengeHeader); return newRealmBuilder(realm)// - .setUri(getNonEmptyPath(uri))// + .setUri(uri)// .setMethodName(request.getMethod())// .setScheme(Realm.AuthScheme.KERBEROS)// .build(); @@ -119,16 +119,16 @@ private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer p String ntlmHost = useRealm ? realm.getNtlmHost() : proxyServer.getHost(); String principal = useRealm ? realm.getPrincipal() : proxyServer.getPrincipal(); String password = useRealm ? realm.getPassword() : proxyServer.getPassword(); + UriComponents uri = request.getURI(); if (realm != null && !realm.isNtlmMessageType2Received()) { String challengeHeader = NTLMEngine.INSTANCE.generateType1Msg(ntlmDomain, ntlmHost); - UriComponents uri = request.getURI(); addNTLMAuthorizationHeader(headers, challengeHeader, proxyInd); future.getAndSetAuth(false); return newRealmBuilder(realm)// .setScheme(realm.getAuthScheme())// - .setUri(getNonEmptyPath(uri))// + .setUri(uri)// .setMethodName(request.getMethod())// .setNtlmMessageType2Received(true)// .build(); @@ -138,7 +138,7 @@ private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer p Realm.AuthScheme authScheme = realm != null ? realm.getAuthScheme() : Realm.AuthScheme.NTLM; return newRealmBuilder(realm)// .setScheme(authScheme)// - .setUri(getNonEmptyPath(request.getURI()))// + .setUri(uri)// .setMethodName(request.getMethod())// .build(); } @@ -154,7 +154,7 @@ private Realm ntlmProxyChallenge(List wwwAuth, Request request, ProxySer return newRealmBuilder(realm)// // .setScheme(realm.getAuthScheme()) - .setUri(getNonEmptyPath(request.getURI()))// + .setUri(request.getURI())// .setMethodName(request.getMethod()).build(); } @@ -206,23 +206,6 @@ private void markAsDone(NettyResponseFuture future, final Channel channel) { } } - private final String computeRealmURI(Realm realm, UriComponents requestURI) { - if (realm.isUseAbsoluteURI()) { - if (realm.isOmitQuery() && isNonEmpty(requestURI.getQuery())) { - return requestURI.withNewQuery(null).toUrl(); - } else { - return requestURI.toUrl(); - } - } else { - String path = getNonEmptyPath(requestURI); - if (realm.isOmitQuery() || !isNonEmpty(requestURI.getQuery())) { - return path; - } else { - return path + "?" + requestURI.getQuery(); - } - } - } - private boolean handleUnauthorizedAndExit(int statusCode, Realm realm, final Request request, HttpResponse response, final NettyResponseFuture future, ProxyServer proxyServer, final Channel channel) throws Exception { if (statusCode == UNAUTHORIZED.code() && realm != null) { @@ -246,15 +229,14 @@ private boolean handleUnauthorizedAndExit(int statusCode, Realm realm, final Req newRealm = new Realm.RealmBuilder()// .clone(realm)// .setScheme(realm.getAuthScheme())// - .setUri(getNonEmptyPath(request.getURI()))// + .setUri(request.getURI())// .setMethodName(request.getMethod())// .setUsePreemptiveAuth(true)// .parseWWWAuthenticateHeader(authenticateHeaders.get(0))// .build(); } - String realmURI = computeRealmURI(newRealm, request.getURI()); - Realm nr = new Realm.RealmBuilder().clone(newRealm).setUri(realmURI).build(); + Realm nr = newRealm; final Request nextRequest = new RequestBuilder(future.getRequest()).setHeaders(request.getHeaders()).setRealm(nr).build(); LOGGER.debug("Sending authentication to {}", request.getURI()); @@ -318,7 +300,7 @@ private boolean handleProxyAuthenticationRequiredAndExit(int statusCode,// } else { newRealm = new Realm.RealmBuilder().clone(realm)// .setScheme(realm.getAuthScheme())// - .setUri("/")// + .setUri(request.getURI().withNewPath("/").withNewQuery(null))// .setMethodName(HttpMethod.CONNECT.name())// .setUsePreemptiveAuth(true)// .parseProxyAuthenticateHeader(proxyAuthenticateHeaders.get(0))// From c809a504e8ce7007c427aaa0f54bef7e3bf1e701 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 9 Jul 2014 23:35:13 +0200 Subject: [PATCH 0082/2020] minor clean up --- .../main/java/org/asynchttpclient/RequestBuilderBase.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java index 8629599db9..1b1a94bad2 100644 --- a/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java +++ b/api/src/main/java/org/asynchttpclient/RequestBuilderBase.java @@ -594,13 +594,7 @@ private void computeFinalUri() { String newQuery = queryComputer.computeFullQueryString(request.uri.getQuery(), queryParams); - request.uri = new UriComponents(// - request.uri.getScheme(),// - request.uri.getUserInfo(),// - request.uri.getHost(),// - request.uri.getPort(),// - request.uri.getPath(),// - newQuery); + request.uri = request.uri.withNewQuery(newQuery); } public Request build() { From 4a87ef77a41ee2bdb000175ab57732b35a9d3e8d Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 9 Jul 2014 23:37:20 +0200 Subject: [PATCH 0083/2020] Minor clean up --- .../grizzly/statushandler/ProxyAuthorizationHandler.java | 2 +- .../asynchttpclient/providers/netty/handler/HttpProtocol.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) 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 0189796e64..172806b2fa 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 @@ -60,7 +60,7 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpT .select(req.getURI()); String principal = proxyServer.getPrincipal(); String password = proxyServer.getPassword(); - Realm realm = new Realm.RealmBuilder().setPrincipal(principal).setPassword(password).setUri(req.getURI().withNewPath("/").withNewQuery(null)) + Realm realm = new Realm.RealmBuilder().setPrincipal(principal).setPassword(password).setUri(req.getURI().withNewPath("/")).setOmitQuery(true) .setMethodName(Method.CONNECT.getMethodString()).setUsePreemptiveAuth(true).parseProxyAuthenticateHeader(proxyAuth).build(); String proxyAuthLowerCase = proxyAuth.toLowerCase(Locale.ENGLISH); if (proxyAuthLowerCase.startsWith("basic")) { 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 0a58c32937..91f237118f 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 @@ -300,7 +300,8 @@ private boolean handleProxyAuthenticationRequiredAndExit(int statusCode,// } else { newRealm = new Realm.RealmBuilder().clone(realm)// .setScheme(realm.getAuthScheme())// - .setUri(request.getURI().withNewPath("/").withNewQuery(null))// + .setUri(request.getURI().withNewPath("/"))// + .setOmitQuery(true)// .setMethodName(HttpMethod.CONNECT.name())// .setUsePreemptiveAuth(true)// .parseProxyAuthenticateHeader(proxyAuthenticateHeaders.get(0))// From 683e24c86d7f8b03886a2a5a080938f9a2a39ade Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 10 Jul 2014 05:53:34 +0200 Subject: [PATCH 0084/2020] Better proxy auth --- .../main/java/org/asynchttpclient/Realm.java | 46 +++++++++++++------ .../asynchttpclient/uri/UriComponents.java | 11 ----- .../util/AuthenticatorUtils.java | 16 ++++--- .../ProxyAuthorizationHandler.java | 2 +- .../providers/netty/handler/HttpProtocol.java | 3 +- 5 files changed, 45 insertions(+), 33 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/Realm.java b/api/src/main/java/org/asynchttpclient/Realm.java index bc71d0d3d3..1f5434f62a 100644 --- a/api/src/main/java/org/asynchttpclient/Realm.java +++ b/api/src/main/java/org/asynchttpclient/Realm.java @@ -53,6 +53,7 @@ public class Realm { private final Charset charset; private final boolean useAbsoluteURI; private final boolean omitQuery; + private final boolean targetProxy; public enum AuthScheme { DIGEST, BASIC, NTLM, SPNEGO, KERBEROS, NONE @@ -60,7 +61,7 @@ public enum AuthScheme { private Realm(AuthScheme scheme, String principal, String password, String realmName, String nonce, String algorithm, String response, String qop, String nc, String cnonce, UriComponents uri, String method, boolean usePreemptiveAuth, String ntlmDomain, String enc, - String host, boolean messageType2Received, String opaque, boolean useAbsoluteURI, boolean omitQuery) { + String host, boolean messageType2Received, String opaque, boolean useAbsoluteURI, boolean omitQuery, boolean targetProxy) { this.principal = principal; this.password = password; @@ -83,6 +84,7 @@ private Realm(AuthScheme scheme, String principal, String password, String realm this.charset = enc != null ? Charset.forName(enc) : null; this.useAbsoluteURI = useAbsoluteURI; this.omitQuery = omitQuery; + this.targetProxy = targetProxy; } public String getPrincipal() { @@ -188,7 +190,11 @@ public boolean isUseAbsoluteURI() { public boolean isOmitQuery() { return omitQuery; } - + + public boolean isTargetProxy() { + return targetProxy; + } + @Override public boolean equals(Object o) { if (this == o) @@ -271,20 +277,21 @@ public static class RealmBuilder { private String cnonce = ""; private UriComponents uri; private String methodName = "GET"; - private boolean usePreemptive = false; - private String domain = System.getProperty("http.auth.ntlm.domain", ""); + private boolean usePreemptive; + private String ntlmDomain = System.getProperty("http.auth.ntlm.domain", ""); private String enc = StandardCharsets.UTF_8.name(); private String host = "localhost"; - private boolean messageType2Received = false; + private boolean messageType2Received; private boolean useAbsoluteURI = true; - private boolean omitQuery = false; + private boolean omitQuery; + private boolean targetProxy; public String getNtlmDomain() { - return domain; + return ntlmDomain; } - public RealmBuilder setNtlmDomain(String domain) { - this.domain = domain; + public RealmBuilder setNtlmDomain(String ntlmDomain) { + this.ntlmDomain = ntlmDomain; return this; } @@ -436,7 +443,16 @@ public RealmBuilder setOmitQuery(boolean omitQuery) { this.omitQuery = omitQuery; return this; } - + + public boolean isTargetProxy() { + return targetProxy; + } + + public RealmBuilder setTargetProxy(boolean targetProxy) { + this.targetProxy = targetProxy; + return this; + } + public RealmBuilder parseWWWAuthenticateHeader(String headerLine) { setRealmName(match(headerLine, "realm")); setNonce(match(headerLine, "nonce")); @@ -464,6 +480,7 @@ public RealmBuilder parseProxyAuthenticateHeader(String headerLine) { } else { setScheme(AuthScheme.BASIC); } + setTargetProxy(true); return this; } @@ -484,6 +501,9 @@ public RealmBuilder clone(Realm clone) { setNtlmDomain(clone.getNtlmDomain()); setNtlmHost(clone.getNtlmHost()); setNtlmMessageType2Received(clone.isNtlmMessageType2Received()); + setUseAbsoluteURI(clone.isUseAbsoluteURI()); + setOmitQuery(clone.isOmitQuery()); + setTargetProxy(clone.isTargetProxy()); return this; } @@ -511,8 +531,8 @@ private String match(String headerLine, String token) { // = to skip match += token.length() + 1; - int traillingComa = headerLine.indexOf(",", match); - String value = headerLine.substring(match, traillingComa > 0 ? traillingComa : headerLine.length()); + int trailingComa = headerLine.indexOf(",", match); + String value = headerLine.substring(match, trailingComa > 0 ? trailingComa : headerLine.length()); value = value.length() > 0 && value.charAt(value.length() - 1) == '"' ? value.substring(0, value.length() - 1) : value; return value.charAt(0) == '"' ? value.substring(1) : value; } @@ -609,7 +629,7 @@ public Realm build() { } return new Realm(scheme, principal, password, realmName, nonce, algorithm, response, qop, nc, cnonce, uri, methodName, - usePreemptive, domain, enc, host, messageType2Received, opaque, useAbsoluteURI, omitQuery); + usePreemptive, ntlmDomain, enc, host, messageType2Received, opaque, useAbsoluteURI, omitQuery, targetProxy); } } } diff --git a/api/src/main/java/org/asynchttpclient/uri/UriComponents.java b/api/src/main/java/org/asynchttpclient/uri/UriComponents.java index 3fe3c224c6..898557d6a5 100644 --- a/api/src/main/java/org/asynchttpclient/uri/UriComponents.java +++ b/api/src/main/java/org/asynchttpclient/uri/UriComponents.java @@ -12,8 +12,6 @@ */ package org.asynchttpclient.uri; -import static org.asynchttpclient.util.MiscUtils.isNonEmpty; - import java.net.URI; import java.net.URISyntaxException; @@ -121,15 +119,6 @@ public UriComponents withNewScheme(String newScheme) { query); } - public UriComponents withNewPath(String newPath) { - return new UriComponents(scheme,// - userInfo,// - host,// - port,// - newPath,// - query); - } - public UriComponents withNewQuery(String newQuery) { return new UriComponents(scheme,// userInfo,// diff --git a/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java b/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java index c147355612..a74d71a659 100644 --- a/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java @@ -33,13 +33,17 @@ public static String computeBasicAuthentication(ProxyServer proxyServer) { } private static String computeRealmURI(Realm realm) { - UriComponents uri = realm.getUri(); - boolean omitQuery = realm.isOmitQuery() && MiscUtils.isNonEmpty(uri.getQuery()); - if (realm.isUseAbsoluteURI()) { - return omitQuery ? uri.withNewQuery(null).toUrl() : uri.toUrl(); + if (realm.isTargetProxy()) { + return "/"; } else { - String path = uri.getPath(); - return omitQuery ? path : path + "?" + uri.getQuery(); + UriComponents uri = realm.getUri(); + boolean omitQuery = realm.isOmitQuery() && MiscUtils.isNonEmpty(uri.getQuery()); + if (realm.isUseAbsoluteURI()) { + return omitQuery ? uri.withNewQuery(null).toUrl() : uri.toUrl(); + } else { + String path = uri.getPath(); + return omitQuery ? path : path + "?" + uri.getQuery(); + } } } 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 172806b2fa..40564b2d2e 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 @@ -60,7 +60,7 @@ public boolean handleStatus(final HttpResponsePacket responsePacket, final HttpT .select(req.getURI()); String principal = proxyServer.getPrincipal(); String password = proxyServer.getPassword(); - Realm realm = new Realm.RealmBuilder().setPrincipal(principal).setPassword(password).setUri(req.getURI().withNewPath("/")).setOmitQuery(true) + Realm realm = new Realm.RealmBuilder().setPrincipal(principal).setPassword(password).setUri(req.getURI()).setOmitQuery(true) .setMethodName(Method.CONNECT.getMethodString()).setUsePreemptiveAuth(true).parseProxyAuthenticateHeader(proxyAuth).build(); String proxyAuthLowerCase = proxyAuth.toLowerCase(Locale.ENGLISH); if (proxyAuthLowerCase.startsWith("basic")) { 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 91f237118f..9bd2b36ee9 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 @@ -20,7 +20,6 @@ import static io.netty.handler.codec.http.HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED; import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED; import static org.asynchttpclient.providers.netty.util.HttpUtil.isNTLM; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.getNonEmptyPath; import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.AsyncHandler; @@ -300,7 +299,7 @@ private boolean handleProxyAuthenticationRequiredAndExit(int statusCode,// } else { newRealm = new Realm.RealmBuilder().clone(realm)// .setScheme(realm.getAuthScheme())// - .setUri(request.getURI().withNewPath("/"))// + .setUri(request.getURI())// .setOmitQuery(true)// .setMethodName(HttpMethod.CONNECT.name())// .setUsePreemptiveAuth(true)// From 8b6fcee02d2134f465231496cd9eeccc5ad68245 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 10 Jul 2014 13:54:31 +0200 Subject: [PATCH 0085/2020] Add #355 test on master: both Netty and Grizzly are OK --- .../asynchttpclient/async/BasicHttpsTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/api/src/test/java/org/asynchttpclient/async/BasicHttpsTest.java b/api/src/test/java/org/asynchttpclient/async/BasicHttpsTest.java index 24675057e7..513786fc91 100644 --- a/api/src/test/java/org/asynchttpclient/async/BasicHttpsTest.java +++ b/api/src/test/java/org/asynchttpclient/async/BasicHttpsTest.java @@ -25,9 +25,12 @@ import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig.Builder; import org.asynchttpclient.Response; +import org.testng.Assert; import org.testng.annotations.Test; +import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLSession; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -125,4 +128,33 @@ public void reconnectsAfterFailedCertificationPath() throws Exception { c.close(); } } + + @Test(timeOut = 5000) + public void failInstantlyIfHostNamesDiffer() throws Exception { + AsyncHttpClient client = null; + + try { + final Builder builder = new Builder().setHostnameVerifier(new HostnameVerifier() { + + public boolean verify(String arg0, SSLSession arg1) { + return false; + } + }).setRequestTimeoutInMs(20000); + + client = getAsyncHttpClient(builder.build()); + + try { + client.prepareGet("https://github.com/AsyncHttpClient/async-http-client/issues/355").execute().get(TIMEOUT, TimeUnit.SECONDS); + + Assert.assertTrue(false, "Shouldn't be here: should get an Exception"); + } catch (ExecutionException e) { + Assert.assertTrue(e.getCause() instanceof ConnectException, "Cause should be a ConnectException"); + } catch (Exception e) { + Assert.assertTrue(false, "Shouldn't be here: should get a ConnectException wrapping a ConnectException"); + } + + } finally { + client.close(); + } + } } From 60830ebc76fc83097966702dd974c333859340ec Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 10 Jul 2014 15:37:51 +0200 Subject: [PATCH 0086/2020] Missing acceptAnyCertificate propagation --- api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java index 8c8a4ba980..422f560af9 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -1133,6 +1133,7 @@ public Builder(AsyncHttpClientConfig prototype) { strict302Handling = prototype.isStrict302Handling(); useRelativeURIsWithSSLProxies = prototype.isUseRelativeURIsWithSSLProxies(); timeConverter = prototype.getTimeConverter(); + acceptAnyCertificate = prototype.isAcceptAnyCertificate(); } /** From e12a45b188283d166fa397f18e3cf7300bf8fe51 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Sat, 12 Jul 2014 09:34:20 +0200 Subject: [PATCH 0087/2020] Fix extras/registry test --- extras/pom.xml | 7 ++++ .../AbstractAsyncHttpClientFactoryTest.java | 31 ++++++++++-------- .../extras/registry}/TestAsyncHttpClient.java | 12 ++++++- extras/registry/src/test/resources/300k.png | Bin 0 -> 265495 bytes .../src/test/resources/SimpleTextFile.txt | 1 + 5 files changed, 36 insertions(+), 15 deletions(-) rename {api/src/test/java/org/asynchttpclient => extras/registry/src/test/java/org/asynchttpclient/extras/registry}/TestAsyncHttpClient.java (87%) create mode 100644 extras/registry/src/test/resources/300k.png create mode 100644 extras/registry/src/test/resources/SimpleTextFile.txt diff --git a/extras/pom.xml b/extras/pom.xml index c220594e75..ab0b2dc70d 100644 --- a/extras/pom.xml +++ b/extras/pom.xml @@ -55,5 +55,12 @@ async-http-client-api ${project.version} + + org.asynchttpclient + async-http-client-api + ${project.version} + test + tests + \ No newline at end of file diff --git a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AbstractAsyncHttpClientFactoryTest.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AbstractAsyncHttpClientFactoryTest.java index 4561a751ee..b4c05cc8be 100644 --- a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AbstractAsyncHttpClientFactoryTest.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AbstractAsyncHttpClientFactoryTest.java @@ -17,7 +17,6 @@ import org.asynchttpclient.AsyncHttpProvider; import org.asynchttpclient.DefaultAsyncHttpClient; import org.asynchttpclient.Response; -import org.asynchttpclient.TestAsyncHttpClient; import org.asynchttpclient.async.util.EchoHandler; import org.asynchttpclient.async.util.TestUtils; import org.asynchttpclient.extras.registry.AsyncHttpClientFactory; @@ -36,6 +35,10 @@ public abstract class AbstractAsyncHttpClientFactoryTest { + public static final String TEST_CLIENT_CLASS_NAME = "org.asynchttpclient.extras.registry.TestAsyncHttpClient"; + public static final String BAD_CLIENT_CLASS_NAME = "org.asynchttpclient.extras.registry.BadAsyncHttpClient"; + public static final String NON_EXISTENT_CLIENT_CLASS_NAME = "org.asynchttpclient.extras.registry.NonExistentAsyncHttpClient"; + private Server server; private int port; @@ -114,27 +117,27 @@ public void testGetAsyncHttpClientStringConfig() { // =================================================================================================================================== @Test(groups = "fast") public void testFactoryWithSystemProperty() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, "org.asynchttpclient.TestAsyncHttpClient"); + System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, TEST_CLIENT_CLASS_NAME); Assert.assertTrue(AsyncHttpClientFactory.getAsyncHttpClient().getClass().equals(TestAsyncHttpClient.class)); } @Test(groups = "fast") public void testGetAsyncHttpClientConfigWithSystemProperty() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, "org.asynchttpclient.TestAsyncHttpClient"); + System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, TEST_CLIENT_CLASS_NAME); AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(new AsyncHttpClientConfig.Builder().build()); Assert.assertTrue(asyncHttpClient.getClass().equals(TestAsyncHttpClient.class)); } @Test(groups = "fast") public void testGetAsyncHttpClientProviderWithSystemProperty() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, "org.asynchttpclient.TestAsyncHttpClient"); + System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, TEST_CLIENT_CLASS_NAME); AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null)); Assert.assertTrue(asyncHttpClient.getClass().equals(TestAsyncHttpClient.class)); } @Test(groups = "fast") public void testGetAsyncHttpClientConfigAndProviderWithSystemProperty() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, "org.asynchttpclient.TestAsyncHttpClient"); + System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, TEST_CLIENT_CLASS_NAME); AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null), new AsyncHttpClientConfig.Builder().build()); Assert.assertTrue(asyncHttpClient.getClass().equals(TestAsyncHttpClient.class)); @@ -142,7 +145,7 @@ public void testGetAsyncHttpClientConfigAndProviderWithSystemProperty() { @Test(groups = "fast") public void testGetAsyncHttpClientStringConfigWithSystemProperty() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, "org.asynchttpclient.TestAsyncHttpClient"); + System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, TEST_CLIENT_CLASS_NAME); AsyncHttpClient asyncHttpClient = AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null).getClass().getName(), new AsyncHttpClientConfig.Builder().build()); Assert.assertTrue(asyncHttpClient.getClass().equals(TestAsyncHttpClient.class)); @@ -157,14 +160,14 @@ public void testGetAsyncHttpClientStringConfigWithSystemProperty() { // =================================================================================================================================== @Test(groups = "fast", expectedExceptions = BadAsyncHttpClientException.class) public void testFactoryWithBadAsyncHttpClient() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, "org.asynchttpclient.BadAsyncHttpClient"); + System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, BAD_CLIENT_CLASS_NAME); AsyncHttpClientFactory.getAsyncHttpClient(); Assert.fail("BadAsyncHttpClientException should have been thrown before this point"); } @Test(groups = "fast") public void testGetAsyncHttpClientConfigWithBadAsyncHttpClient() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, "org.asynchttpclient.BadAsyncHttpClient"); + System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, BAD_CLIENT_CLASS_NAME); try { AsyncHttpClientFactory.getAsyncHttpClient(new AsyncHttpClientConfig.Builder().build()); } catch (AsyncHttpClientImplException e) { @@ -175,7 +178,7 @@ public void testGetAsyncHttpClientConfigWithBadAsyncHttpClient() { @Test(groups = "fast") public void testGetAsyncHttpClientProviderWithBadAsyncHttpClient() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, "org.asynchttpclient.BadAsyncHttpClient"); + System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, BAD_CLIENT_CLASS_NAME); try { AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null)); } catch (AsyncHttpClientImplException e) { @@ -186,7 +189,7 @@ public void testGetAsyncHttpClientProviderWithBadAsyncHttpClient() { @Test(groups = "fast") public void testGetAsyncHttpClientConfigAndProviderWithBadAsyncHttpClient() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, "org.asynchttpclient.BadAsyncHttpClient"); + System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, BAD_CLIENT_CLASS_NAME); try { AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null), new AsyncHttpClientConfig.Builder().build()); } catch (AsyncHttpClientImplException e) { @@ -197,7 +200,7 @@ public void testGetAsyncHttpClientConfigAndProviderWithBadAsyncHttpClient() { @Test(groups = "fast") public void testGetAsyncHttpClientStringConfigWithBadAsyncHttpClient() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, "org.asynchttpclient.BadAsyncHttpClient"); + System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, BAD_CLIENT_CLASS_NAME); try { AsyncHttpClientFactory.getAsyncHttpClient(getAsyncHttpProvider(null).getClass().getName(), new AsyncHttpClientConfig.Builder().build()); @@ -215,7 +218,7 @@ public void testGetAsyncHttpClientStringConfigWithBadAsyncHttpClient() { */ @Test(groups = "fast", expectedExceptions = AsyncHttpClientImplException.class) public void testFactoryWithNonExistentAsyncHttpClient() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, "org.asynchttpclient.NonExistentAsyncHttpClient"); + System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, NON_EXISTENT_CLIENT_CLASS_NAME); AsyncHttpClientFactory.getAsyncHttpClient(); Assert.fail("AsyncHttpClientImplException should have been thrown before this point"); } @@ -227,7 +230,7 @@ public void testFactoryWithNonExistentAsyncHttpClient() { @Test(groups = "fast", expectedExceptions = AsyncHttpClientImplException.class) public void testRepeatedCallsToBadAsyncHttpClient() { boolean exceptionCaught = false; - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, "org.asynchttpclient.NonExistentAsyncHttpClient"); + System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, NON_EXISTENT_CLIENT_CLASS_NAME); try { AsyncHttpClientFactory.getAsyncHttpClient(); } catch (AsyncHttpClientImplException e) { @@ -260,7 +263,7 @@ private void assertClientWorks(AsyncHttpClient asyncHttpClient) { private void assertException(AsyncHttpClientImplException e) { InvocationTargetException t = (InvocationTargetException) e.getCause(); - Assert.assertTrue(t.getCause().getClass().equals(BadAsyncHttpClientException.class)); + Assert.assertTrue(t.getCause() instanceof BadAsyncHttpClientException); } } diff --git a/api/src/test/java/org/asynchttpclient/TestAsyncHttpClient.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/TestAsyncHttpClient.java similarity index 87% rename from api/src/test/java/org/asynchttpclient/TestAsyncHttpClient.java rename to extras/registry/src/test/java/org/asynchttpclient/extras/registry/TestAsyncHttpClient.java index 1d9ab99702..9d927287fc 100644 --- a/api/src/test/java/org/asynchttpclient/TestAsyncHttpClient.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/TestAsyncHttpClient.java @@ -10,7 +10,17 @@ * "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; +package org.asynchttpclient.extras.registry; + +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.AsyncHttpProvider; +import org.asynchttpclient.BoundRequestBuilder; +import org.asynchttpclient.ListenableFuture; +import org.asynchttpclient.Request; +import org.asynchttpclient.Response; +import org.asynchttpclient.SignatureCalculator; import java.io.IOException; diff --git a/extras/registry/src/test/resources/300k.png b/extras/registry/src/test/resources/300k.png new file mode 100644 index 0000000000000000000000000000000000000000..bff4a8598918ed4945bc27db894230d8b5b7cc80 GIT binary patch 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 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 J Date: Mon, 14 Jul 2014 14:19:54 +0200 Subject: [PATCH 0088/2020] Port refactored Netty Channel pool on master --- .../netty/channel/DefaultChannelPool.java | 320 ++++++++++-------- 1 file changed, 183 insertions(+), 137 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java index 54308e010a..a1c1c9f4ed 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java @@ -27,144 +27,207 @@ import io.netty.util.internal.chmv8.ConcurrentHashMapV8; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; /** * A simple implementation of {@link ChannelPool} based on a {@link java.util.concurrent.ConcurrentHashMap} */ public class DefaultChannelPool implements ChannelPool { - private final static Logger LOGGER = LoggerFactory.getLogger(DefaultChannelPool.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultChannelPool.class); + private final ConcurrentHashMapV8> connectionsPool = new ConcurrentHashMapV8>(); - private final ConcurrentHashMapV8 channel2IdleChannel = new ConcurrentHashMapV8(); - private final ConcurrentHashMapV8 channel2CreationDate = new ConcurrentHashMapV8(); - private final AtomicBoolean closed = new AtomicBoolean(false); + private final ConcurrentHashMapV8 channel2Creation = new ConcurrentHashMapV8(); + private final AtomicInteger size = new AtomicInteger(); + private final AtomicBoolean isClosed = new AtomicBoolean(false); private final Timer nettyTimer; private final boolean sslConnectionPoolEnabled; private final int maxTotalConnections; + private final boolean maxTotalConnectionsDisabled; private final int maxConnectionPerHost; - private final int maxConnectionLifeTimeInMs; + private final boolean maxConnectionPerHostDisabled; + private final int maxConnectionTTL; + private final boolean maxConnectionTTLDisabled; private final long maxIdleTime; + private final boolean maxIdleTimeDisabled; + private final long cleanerPeriod; - public DefaultChannelPool(AsyncHttpClientConfig config, Timer nettyTimer) { + public DefaultChannelPool(AsyncHttpClientConfig config, Timer hashedWheelTimer) { this(config.getMaxTotalConnections(),// config.getMaxConnectionPerHost(),// config.getIdleConnectionInPoolTimeoutInMs(),// - config.isSslConnectionPoolEnabled(),// config.getMaxConnectionLifeTimeInMs(),// - nettyTimer); + config.isSslConnectionPoolEnabled(),// + hashedWheelTimer); } public DefaultChannelPool(// int maxTotalConnections,// int maxConnectionPerHost,// long maxIdleTime,// + int maxConnectionTTL,// boolean sslConnectionPoolEnabled,// - int maxConnectionLifeTimeInMs,// Timer nettyTimer) { this.maxTotalConnections = maxTotalConnections; + maxTotalConnectionsDisabled = maxTotalConnections <= 0; this.maxConnectionPerHost = maxConnectionPerHost; + maxConnectionPerHostDisabled = maxConnectionPerHost <= 0; this.sslConnectionPoolEnabled = sslConnectionPoolEnabled; this.maxIdleTime = maxIdleTime; - this.maxConnectionLifeTimeInMs = maxConnectionLifeTimeInMs; + this.maxConnectionTTL = maxConnectionTTL; + maxConnectionTTLDisabled = maxConnectionTTL <= 0; this.nettyTimer = nettyTimer; - if (maxIdleTime > 0L) { + maxIdleTimeDisabled = maxIdleTime <= 0; + + cleanerPeriod = Math.min(maxConnectionTTLDisabled ? Long.MAX_VALUE : maxConnectionTTL, maxIdleTimeDisabled ? Long.MAX_VALUE + : maxIdleTime); + + if (!maxConnectionTTLDisabled || !maxIdleTimeDisabled) scheduleNewIdleChannelDetector(new IdleChannelDetector()); - } } private void scheduleNewIdleChannelDetector(TimerTask task) { - nettyTimer.newTimeout(task, maxIdleTime, TimeUnit.MILLISECONDS); + nettyTimer.newTimeout(task, cleanerPeriod, TimeUnit.MILLISECONDS); } - private static final class IdleChannel { + private static final class ChannelCreation { + final long creationTime; final String key; + + ChannelCreation(long creationTime, String key) { + this.creationTime = creationTime; + this.key = key; + } + } + + private static final class IdleChannel { final Channel channel; final long start; - IdleChannel(String key, Channel channel) { - if (key == null) - throw new NullPointerException("key"); + IdleChannel(Channel channel, long start) { if (channel == null) throw new NullPointerException("channel"); - this.key = key; this.channel = channel; - this.start = millisTime(); + this.start = start; } @Override + // only depends on channel public boolean equals(Object o) { return this == o || (o instanceof IdleChannel && channel.equals(IdleChannel.class.cast(o).channel)); } @Override public int hashCode() { - return channel != null ? channel.hashCode() : 0; + return channel.hashCode(); } } - private class IdleChannelDetector implements TimerTask { + private boolean isTTLExpired(Channel channel, long now) { + if (maxConnectionTTLDisabled) + return false; - @Override - public void run(Timeout timeout) throws Exception { - try { - if (closed.get()) - return; + ChannelCreation creation = channel2Creation.get(channel); + return creation == null || now - creation.creationTime >= maxConnectionTTL; + } - if (LOGGER.isDebugEnabled()) { - Set keys = connectionsPool.keySet(); + private boolean isRemotelyClosed(Channel channel) { + return !channel.isOpen(); + } - for (String s : keys) { - LOGGER.debug("Entry count for : {} : {}", s, connectionsPool.get(s).size()); - } - } + private final class IdleChannelDetector implements TimerTask { - List channelsInTimeout = new ArrayList(); - long currentTime = millisTime(); + private boolean isIdleTimeoutExpired(IdleChannel idleChannel, long now) { + return !maxIdleTimeDisabled && now - idleChannel.start >= maxIdleTime; + } - for (IdleChannel idleChannel : channel2IdleChannel.values()) { - long age = currentTime - idleChannel.start; - if (age > maxIdleTime) { + private List expiredChannels(ConcurrentLinkedQueue pool, long now) { + // lazy create + List idleTimeoutChannels = null; + for (IdleChannel idleChannel : pool) { + if (isTTLExpired(idleChannel.channel, now) || isIdleTimeoutExpired(idleChannel, now) + || isRemotelyClosed(idleChannel.channel)) { + LOGGER.debug("Adding Candidate expired Channel {}", idleChannel.channel); + if (idleTimeoutChannels == null) + idleTimeoutChannels = new ArrayList(); + idleTimeoutChannels.add(idleChannel); + } + } - LOGGER.debug("Adding Candidate Idle Channel {}", idleChannel.channel); + return idleTimeoutChannels != null ? idleTimeoutChannels : Collections. emptyList(); + } - // store in an unsynchronized list to minimize the impact on the ConcurrentHashMap. - channelsInTimeout.add(idleChannel); - } - } - long endConcurrentLoop = millisTime(); - - for (IdleChannel idleChannel : channelsInTimeout) { - Object attachment = Channels.getDefaultAttribute(idleChannel.channel); - if (attachment != null) { - if (attachment instanceof NettyResponseFuture) { - NettyResponseFuture future = (NettyResponseFuture) attachment; - - if (!future.isDone() && !future.isCancelled()) { - LOGGER.debug("Future not in appropriate state %s\n", future); - continue; - } - } - } + private boolean isChannelCloseable(Channel channel) { + boolean closeable = true; + Object attachment = Channels.getDefaultAttribute(channel); + if (attachment instanceof NettyResponseFuture) { + NettyResponseFuture future = (NettyResponseFuture) attachment; + closeable = !future.isDone() || !future.isCancelled(); + if (!closeable) + LOGGER.error("Future not in appropriate state %s, not closing", future); + } + return true; + } - if (remove(idleChannel)) { + private final List closeChannels(List candidates) { + + // lazy create, only if we have a non-closeable channel + List closedChannels = null; + for (int i = 0; i < candidates.size(); i++) { + IdleChannel idleChannel = candidates.get(i); + if (!isChannelCloseable(idleChannel.channel)) + if (closedChannels == null) { + // first non closeable to be skipped, copy all previously skipped closeable channels + closedChannels = new ArrayList(candidates.size()); + for (int j = 0; j < i; j++) + closedChannels.add(candidates.get(j)); + } else { LOGGER.debug("Closing Idle Channel {}", idleChannel.channel); close(idleChannel.channel); + if (closedChannels != null) { + closedChannels.add(idleChannel); + } } - } + } + + return closedChannels != null ? closedChannels : candidates; + } + + public void run(Timeout timeout) throws Exception { - if (LOGGER.isTraceEnabled()) { - int openChannels = 0; - for (ConcurrentLinkedQueue hostChannels : connectionsPool.values()) { - openChannels += hostChannels.size(); + if (isClosed.get()) + return; + + try { + if (LOGGER.isDebugEnabled()) { + for (String key : connectionsPool.keySet()) { + LOGGER.debug("Entry count for : {} : {}", key, connectionsPool.get(key).size()); } - LOGGER.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)); } + + long start = millisTime(); + int totalCount = size.get(); + int closedCount = 0; + + for (ConcurrentLinkedQueue pool : connectionsPool.values()) { + // store in intermediate unsynchronized lists to minimize the impact on the ConcurrentLinkedQueue + List candidateExpiredChannels = expiredChannels(pool, start); + List closedChannels = closeChannels(candidateExpiredChannels); + pool.removeAll(closedChannels); + int poolClosedCount = closedChannels.size(); + size.addAndGet(-poolClosedCount); + closedCount += poolClosedCount; + } + + long duration = millisTime() - start; + + LOGGER.debug("Closed {} connections out of {} in {}ms", closedCount, totalCount, duration); + } catch (Throwable t) { LOGGER.error("uncaught exception!", t); } @@ -173,137 +236,120 @@ public void run(Timeout timeout) throws Exception { } } + private boolean addIdleChannel(ConcurrentLinkedQueue idleConnectionForKey, String key, Channel channel, long now) { + + // FIXME computing CLQ.size is not efficient + if (maxConnectionPerHostDisabled || idleConnectionForKey.size() < maxConnectionPerHost) { + IdleChannel idleChannel = new IdleChannel(channel, now); + return idleConnectionForKey.add(idleChannel); + } + LOGGER.debug("Maximum number of requests per key reached {} for {}", maxConnectionPerHost, key); + return false; + } + /** * {@inheritDoc} */ - public boolean offer(String uri, Channel channel) { - if (closed.get() || (!sslConnectionPoolEnabled && uri.startsWith("https"))) + public boolean offer(String key, Channel channel) { + if (isClosed.get() || (!sslConnectionPoolEnabled && key.startsWith("https"))) return false; - Long createTime = channel2CreationDate.get(channel); - if (createTime == null) { - channel2CreationDate.putIfAbsent(channel, millisTime()); + long now = millisTime(); - } else if (maxConnectionLifeTimeInMs != -1 && (createTime + maxConnectionLifeTimeInMs) < millisTime()) { - LOGGER.debug("Channel {} expired", channel); + if (isTTLExpired(channel, now)) return false; - } - - LOGGER.debug("Adding uri: {} for channel {}", uri, channel); - Channels.setDefaultAttribute(channel, DiscardEvent.INSTANCE); - ConcurrentLinkedQueue pooledConnectionForKey = connectionsPool.get(uri); - if (pooledConnectionForKey == null) { + ConcurrentLinkedQueue idleConnectionForKey = connectionsPool.get(key); + if (idleConnectionForKey == null) { ConcurrentLinkedQueue newPool = new ConcurrentLinkedQueue(); - pooledConnectionForKey = connectionsPool.putIfAbsent(uri, newPool); - if (pooledConnectionForKey == null) - pooledConnectionForKey = newPool; + idleConnectionForKey = connectionsPool.putIfAbsent(key, newPool); + if (idleConnectionForKey == null) + idleConnectionForKey = newPool; } - boolean added; - int size = pooledConnectionForKey.size(); - if (maxConnectionPerHost == -1 || size < maxConnectionPerHost) { - IdleChannel idleChannel = new IdleChannel(uri, channel); - synchronized (pooledConnectionForKey) { - added = pooledConnectionForKey.add(idleChannel); - - if (channel2IdleChannel.put(channel, idleChannel) != null) { - LOGGER.error("Channel {} already exists in the connections pool!", channel); - } - } - } else { - LOGGER.debug("Maximum number of requests per host reached {} for {}", maxConnectionPerHost, uri); - added = false; + boolean added = addIdleChannel(idleConnectionForKey, key, channel, now); + if (added) { + size.incrementAndGet(); + channel2Creation.putIfAbsent(channel, new ChannelCreation(now, key)); } + return added; } /** * {@inheritDoc} */ - public Channel poll(String uri) { - if (!sslConnectionPoolEnabled && uri.startsWith("https")) { + public Channel poll(String key) { + if (!sslConnectionPoolEnabled && key.startsWith("https")) return null; - } IdleChannel idleChannel = null; - ConcurrentLinkedQueue pooledConnectionForKey = connectionsPool.get(uri); + ConcurrentLinkedQueue pooledConnectionForKey = connectionsPool.get(key); if (pooledConnectionForKey != null) { - boolean poolEmpty = false; - while (!poolEmpty && idleChannel == null) { - if (pooledConnectionForKey.size() > 0) { - synchronized (pooledConnectionForKey) { - idleChannel = pooledConnectionForKey.poll(); - if (idleChannel != null) { - channel2IdleChannel.remove(idleChannel.channel); - } - } - } + while (idleChannel == null) { + idleChannel = pooledConnectionForKey.poll(); - if (idleChannel == null) { - poolEmpty = true; - } else if (!idleChannel.channel.isActive() || !idleChannel.channel.isOpen()) { + if (idleChannel == null) + // pool is empty + break; + else if (isRemotelyClosed(idleChannel.channel)) { idleChannel = null; - LOGGER.trace("Channel not connected or not opened!"); + LOGGER.trace("Channel not connected or not opened, probably remotely closed!"); } } } - return idleChannel != null ? idleChannel.channel : null; - } - - private boolean remove(IdleChannel pooledChannel) { - if (pooledChannel == null || closed.get()) - return false; - - boolean isRemoved = false; - ConcurrentLinkedQueue pooledConnectionForHost = connectionsPool.get(pooledChannel.key); - if (pooledConnectionForHost != null) { - isRemoved = pooledConnectionForHost.remove(pooledChannel); - } - return isRemoved |= channel2IdleChannel.remove(pooledChannel.channel) != null; + if (idleChannel != null) { + size.decrementAndGet(); + return idleChannel.channel; + } else + return null; } /** * {@inheritDoc} */ public boolean removeAll(Channel channel) { - channel2CreationDate.remove(channel); - return !closed.get() && remove(channel2IdleChannel.get(channel)); + ChannelCreation creation = channel2Creation.remove(channel); + return !isClosed.get() && creation != null && connectionsPool.get(creation.key).remove(channel); } /** * {@inheritDoc} */ public boolean canCacheConnection() { - return !closed.get() && (maxTotalConnections == -1 || channel2IdleChannel.size() < maxTotalConnections); + // FIXME: doesn't honor per host limit + // FIXME: doesn't account for borrowed channels + return !isClosed.get() && (maxTotalConnectionsDisabled || size.get() < maxTotalConnections); } /** * {@inheritDoc} */ public void destroy() { - if (closed.getAndSet(true)) + if (isClosed.getAndSet(true)) return; - for (Channel channel : channel2IdleChannel.keySet()) { - close(channel); + for (ConcurrentLinkedQueue pool : connectionsPool.values()) { + for (IdleChannel idleChannel : pool) + close(idleChannel.channel); } + connectionsPool.clear(); - channel2IdleChannel.clear(); - channel2CreationDate.clear(); + channel2Creation.clear(); + size.set(0); } private void close(Channel channel) { try { Channels.setDefaultAttribute(channel, DiscardEvent.INSTANCE); - channel2CreationDate.remove(channel); + channel2Creation.remove(channel); channel.close(); } catch (Throwable t) { // noop } } - public final String toString() { - return String.format("NettyConnectionPool: {pool-size: %d}", channel2IdleChannel.size()); + public String toString() { + return String.format("NettyConnectionPool: {pool-size: %d}", size.get()); } } From cc92c540c5f704b686f0c6cecf5800686e868259 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 14 Jul 2014 14:34:53 +0200 Subject: [PATCH 0089/2020] Refactor Netty provider config --- .../netty/NettyAsyncHttpProviderConfig.java | 213 ++++++++---------- .../providers/netty/channel/Channels.java | 35 +-- .../netty/NettyAsyncProviderBasicTest.java | 6 +- 3 files changed, 104 insertions(+), 150 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 ac75499993..b03b6ef31b 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 @@ -22,8 +22,6 @@ import org.asynchttpclient.providers.netty.response.EagerResponseBodyPart; import org.asynchttpclient.providers.netty.response.LazyResponseBodyPart; import org.asynchttpclient.providers.netty.response.NettyResponseBodyPart; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; @@ -38,67 +36,9 @@ /** * This class can be used to pass Netty's internal configuration options. See Netty documentation for more information. */ -public class NettyAsyncHttpProviderConfig implements AsyncHttpProviderConfig { +public class NettyAsyncHttpProviderConfig implements AsyncHttpProviderConfig, Object> { - private final static Logger LOGGER = LoggerFactory.getLogger(NettyAsyncHttpProviderConfig.class); - - /** - * Allow configuring the Netty's event loop. - */ - private EventLoopGroup eventLoopGroup; - - private AdditionalChannelInitializer httpAdditionalChannelInitializer; - private AdditionalChannelInitializer wsAdditionalChannelInitializer; - private AdditionalChannelInitializer httpsAdditionalChannelInitializer; - private AdditionalChannelInitializer wssAdditionalChannelInitializer; - - /** - * 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(); - - private ResponseBodyPartFactory bodyPartFactory = new EagerResponseBodyPartFactory(); - - private ChannelPool channelPool; - - private boolean disableZeroCopy; - - private Timer nettyTimer; - - private long handshakeTimeoutInMillis; - - private SSLEngineFactory sslEngineFactory; - - public NettyAsyncHttpProviderConfig() { - properties.put(REUSE_ADDRESS, Boolean.FALSE); - } + private final Map, Object> properties = new HashMap, Object>(); /** * Add a property that will be used when the AsyncHttpClient initialize its @@ -108,16 +48,14 @@ public NettyAsyncHttpProviderConfig() { * @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); - } + public NettyAsyncHttpProviderConfig addProperty(ChannelOption name, Object value) { + properties.put(name, value); + return this; + } + @SuppressWarnings("unchecked") + public NettyAsyncHttpProviderConfig addChannelOption(ChannelOption name, T value) { + properties.put((ChannelOption) name, value); return this; } @@ -127,7 +65,7 @@ public NettyAsyncHttpProviderConfig addProperty(String name, Object value) { * @param name * @return this instance of AsyncHttpProviderConfig */ - public Object getProperty(String name) { + public Object getProperty(ChannelOption name) { return properties.get(name); } @@ -137,7 +75,7 @@ public Object getProperty(String name) { * @param name * @return true if removed */ - public Object removeProperty(String name) { + public Object removeProperty(ChannelOption name) { return properties.remove(name); } @@ -146,40 +84,79 @@ public Object removeProperty(String name) { * * @return a the curent entry set. */ - public Set> propertiesSet() { + public Set, Object>> propertiesSet() { return properties.entrySet(); } - public EventLoopGroup getEventLoopGroup() { - return eventLoopGroup; - } + public static interface AdditionalChannelInitializer { - public void setEventLoopGroup(EventLoopGroup eventLoopGroup) { - this.eventLoopGroup = eventLoopGroup; + void initChannel(Channel ch) throws Exception; } - public int getMaxInitialLineLength() { - return maxInitialLineLength; - } + public static interface ResponseBodyPartFactory { - public void setMaxInitialLineLength(int maxInitialLineLength) { - this.maxInitialLineLength = maxInitialLineLength; + NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last); } - public int getMaxHeaderSize() { - return maxHeaderSize; + public static class EagerResponseBodyPartFactory implements ResponseBodyPartFactory { + + @Override + public NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { + return new EagerResponseBodyPart(buf, last); + } } - public void setMaxHeaderSize(int maxHeaderSize) { - this.maxHeaderSize = maxHeaderSize; + public static class LazyResponseBodyPartFactory implements ResponseBodyPartFactory { + + @Override + public NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { + return new LazyResponseBodyPart(buf, last); + } } - public int getMaxChunkSize() { - return maxChunkSize; + /** + * Allow configuring the Netty's event loop. + */ + private EventLoopGroup eventLoopGroup; + + private AdditionalChannelInitializer httpAdditionalChannelInitializer; + private AdditionalChannelInitializer wsAdditionalChannelInitializer; + private AdditionalChannelInitializer httpsAdditionalChannelInitializer; + private AdditionalChannelInitializer wssAdditionalChannelInitializer; + + /** + * HttpClientCodec's maxInitialLineLength + */ + private int maxInitialLineLength = 4096; + + /** + * HttpClientCodec's maxHeaderSize + */ + private int maxHeaderSize = 8192; + + /** + * HttpClientCodec's maxChunkSize + */ + private int maxChunkSize = 8192; + + private ResponseBodyPartFactory bodyPartFactory = new EagerResponseBodyPartFactory(); + + private ChannelPool channelPool; + + private boolean disableZeroCopy; + + private Timer nettyTimer; + + private long handshakeTimeoutInMillis; + + private SSLEngineFactory sslEngineFactory; + + public EventLoopGroup getEventLoopGroup() { + return eventLoopGroup; } - public void setMaxChunkSize(int maxChunkSize) { - this.maxChunkSize = maxChunkSize; + public void setEventLoopGroup(EventLoopGroup eventLoopGroup) { + this.eventLoopGroup = eventLoopGroup; } public AdditionalChannelInitializer getHttpAdditionalChannelInitializer() { @@ -214,6 +191,30 @@ public void setWssAdditionalChannelInitializer(AdditionalChannelInitializer wssA this.wssAdditionalChannelInitializer = wssAdditionalChannelInitializer; } + 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 ResponseBodyPartFactory getBodyPartFactory() { return bodyPartFactory; } @@ -253,7 +254,7 @@ public long getHandshakeTimeoutInMillis() { public void setHandshakeTimeoutInMillis(long handshakeTimeoutInMillis) { this.handshakeTimeoutInMillis = handshakeTimeoutInMillis; } - + public SSLEngineFactory getSslEngineFactory() { return sslEngineFactory; } @@ -261,30 +262,4 @@ public SSLEngineFactory getSslEngineFactory() { public void setSslEngineFactory(SSLEngineFactory sslEngineFactory) { this.sslEngineFactory = sslEngineFactory; } - - public static interface AdditionalChannelInitializer { - - void initChannel(Channel ch) throws Exception; - } - - public static interface ResponseBodyPartFactory { - - NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last); - } - - public static class EagerResponseBodyPartFactory implements ResponseBodyPartFactory { - - @Override - public NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { - return new EagerResponseBodyPart(buf, last); - } - } - - public static class LazyResponseBodyPartFactory implements ResponseBodyPartFactory { - - @Override - public NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { - return new LazyResponseBodyPart(buf, last); - } - } } 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 34f9338bf7..1c1eb6ed04 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 @@ -63,10 +63,7 @@ import javax.net.ssl.SSLEngine; import java.io.IOException; -import java.lang.reflect.Field; import java.security.GeneralSecurityException; -import java.util.HashMap; -import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @@ -162,31 +159,13 @@ public Channels(final AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig freeConnections = null; } - Map> optionMap = new HashMap>(); - for (Field field : ChannelOption.class.getDeclaredFields()) { - if (field.getType().isAssignableFrom(ChannelOption.class)) { - field.setAccessible(true); - try { - optionMap.put(field.getName(), (ChannelOption) field.get(null)); - } catch (IllegalAccessException ex) { - throw new Error(ex); - } - } - } - - if (nettyProviderConfig != null) { - for (Entry entry : nettyProviderConfig.propertiesSet()) { - ChannelOption key = optionMap.get(entry.getKey()); - if (key != null) { - Object value = entry.getValue(); - plainBootstrap.option(key, value); - webSocketBootstrap.option(key, value); - secureBootstrap.option(key, value); - secureWebSocketBootstrap.option(key, value); - } else { - throw new IllegalArgumentException("Unknown config property " + entry.getKey()); - } - } + for (Entry, Object> entry : nettyProviderConfig.propertiesSet()) { + ChannelOption key = entry.getKey(); + Object value = entry.getValue(); + plainBootstrap.option(key, value); + webSocketBootstrap.option(key, value); + secureBootstrap.option(key, value); + secureWebSocketBootstrap.option(key, value); } int timeOut = config.getConnectionTimeoutInMs() > 0 ? config.getConnectionTimeoutInMs() : Integer.MAX_VALUE; 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 eeb0008d6f..79c5112869 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 @@ -17,6 +17,8 @@ import org.asynchttpclient.AsyncHttpProviderConfig; import org.asynchttpclient.async.AsyncProvidersBasicTest; +import io.netty.channel.ChannelOption; + public class NettyAsyncProviderBasicTest extends AsyncProvidersBasicTest { @Override @@ -26,9 +28,7 @@ public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { @Override protected AsyncHttpProviderConfig getProviderConfig() { - final NettyAsyncHttpProviderConfig config = new NettyAsyncHttpProviderConfig(); - config.addProperty("TCP_NODELAY", true); - return config; + return new NettyAsyncHttpProviderConfig().addChannelOption(ChannelOption.TCP_NODELAY, Boolean.TRUE); } @Override From 226995db6e973ce7c4dd916f9b7bf97f94732e09 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 14 Jul 2014 15:02:08 +0200 Subject: [PATCH 0090/2020] fix build --- .../extras/registry/AsyncHttpClientRegistryTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryTest.java b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryTest.java index ffd8afad0f..c4e94ed128 100644 --- a/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryTest.java +++ b/extras/registry/src/test/java/org/asynchttpclient/extras/registry/AsyncHttpClientRegistryTest.java @@ -38,7 +38,7 @@ public void setUp() { @BeforeClass public void setUpBeforeTest() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, "org.asynchttpclient.TestAsyncHttpClient"); + System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_IMPL_SYSTEM_PROPERTY, AbstractAsyncHttpClientFactoryTest.TEST_CLIENT_CLASS_NAME); } @AfterClass @@ -100,14 +100,14 @@ public void testCustomAsyncHttpClientRegistry() { @Test(groups = "fast", expectedExceptions = AsyncHttpClientImplException.class) public void testNonExistentAsyncHttpClientRegistry() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_REGISTRY_SYSTEM_PROPERTY, "org.asynchttpclient.NonExistentAsyncRegistry"); + System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_REGISTRY_SYSTEM_PROPERTY, AbstractAsyncHttpClientFactoryTest.NON_EXISTENT_CLIENT_CLASS_NAME); AsyncHttpClientRegistryImpl.getInstance(); Assert.fail("Should never have reached here"); } @Test(groups = "fast", expectedExceptions = AsyncHttpClientImplException.class) public void testBadAsyncHttpClientRegistry() { - System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_REGISTRY_SYSTEM_PROPERTY, "org.asynchttpclient.BadAsyncHttpClientRegistry"); + System.setProperty(AsyncImplHelper.ASYNC_HTTP_CLIENT_REGISTRY_SYSTEM_PROPERTY, AbstractAsyncHttpClientFactoryTest.BAD_CLIENT_CLASS_NAME); AsyncHttpClientRegistryImpl.getInstance(); Assert.fail("Should never have reached here"); } From 442c1469709dff25016444bb2b74493b12d94fcf Mon Sep 17 00:00:00 2001 From: oleksiys Date: Tue, 15 Jul 2014 11:49:17 -0700 Subject: [PATCH 0091/2020] [master] + rework the fix for the issue #525 https://github.com/AsyncHttpClient/async-http-client/pull/525 "Move the hostname verification to after the SSL handshake has completed" --- .../providers/grizzly/ConnectionManager.java | 68 ++++++------------- .../grizzly/GrizzlyAsyncHttpProvider.java | 7 +- .../grizzly/filters/SwitchingSSLFilter.java | 67 ++++++++++++++++-- 3 files changed, 84 insertions(+), 58 deletions(-) diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java index 3130f83f51..b83618df63 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -21,7 +21,6 @@ import org.asynchttpclient.ProxyServer; import org.asynchttpclient.Request; import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.util.Base64; import org.glassfish.grizzly.CompletionHandler; import org.glassfish.grizzly.Connection; import org.glassfish.grizzly.Grizzly; @@ -30,19 +29,14 @@ import org.glassfish.grizzly.connectionpool.EndpointKey; import org.glassfish.grizzly.filterchain.FilterChainBuilder; import org.glassfish.grizzly.impl.FutureImpl; -import org.glassfish.grizzly.ssl.SSLBaseFilter; -import org.glassfish.grizzly.ssl.SSLFilter; -import org.glassfish.grizzly.ssl.SSLUtils; import org.glassfish.grizzly.utils.Futures; import org.glassfish.grizzly.utils.IdleTimeoutFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSession; import java.io.IOException; -import java.net.ConnectException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -52,6 +46,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; +import org.asynchttpclient.providers.grizzly.filters.SwitchingSSLFilter; public class ConnectionManager { @@ -66,15 +61,13 @@ public class ConnectionManager { private final FilterChainBuilder secureBuilder; private final FilterChainBuilder nonSecureBuilder; private final boolean asyncConnect; - private final SSLFilter sslFilter; // ------------------------------------------------------------ Constructors ConnectionManager(final GrizzlyAsyncHttpProvider provider,// final ConnectionPool connectionPool,// final FilterChainBuilder secureBuilder,// - final FilterChainBuilder nonSecureBuilder,// - final SSLFilter sslFilter) { + final FilterChainBuilder nonSecureBuilder) { this.provider = provider; final AsyncHttpClientConfig config = provider.getClientConfig(); @@ -95,18 +88,26 @@ public class ConnectionManager { AsyncHttpProviderConfig providerConfig = config.getAsyncHttpProviderConfig(); asyncConnect = providerConfig instanceof GrizzlyAsyncHttpProviderConfig ? GrizzlyAsyncHttpProviderConfig.class.cast(providerConfig) .isAsyncConnectMode() : false; - this.sslFilter = sslFilter; } // ---------------------------------------------------------- Public Methods public void doTrackedConnection(final Request request,// final GrizzlyResponseFuture requestFuture,// - final CompletionHandler connectHandler) throws IOException { + CompletionHandler completionHandler) throws IOException { final EndpointKey key = getEndPointKey(request, requestFuture.getProxyServer()); - CompletionHandler handler = wrapHandler(request, getVerifier(), connectHandler, sslFilter); + + final HostnameVerifier verifier = getVerifier(); + final UriComponents uri = request.getURI(); + + if (Utils.isSecure(uri) && verifier != null) { + completionHandler = + SwitchingSSLFilter.wrapWithHostnameVerifierHandler( + completionHandler, verifier, uri.getHost()); + } + if (asyncConnect) { - connectionPool.take(key, handler); + connectionPool.take(key, completionHandler); } else { IOException ioe = null; GrizzlyFuture future = connectionPool.take(key); @@ -114,18 +115,18 @@ public void doTrackedConnection(final Request request,// // No explicit timeout when calling get() here as the Grizzly // endpoint pool will time it out based on the connect timeout // setting. - handler.completed(future.get()); + completionHandler.completed(future.get()); } catch (CancellationException e) { - handler.cancelled(); + completionHandler.cancelled(); } catch (ExecutionException ee) { final Throwable cause = ee.getCause(); if (cause instanceof ConnectionPool.MaxCapacityException) { ioe = (IOException) cause; } else { - handler.failed(ee.getCause()); + completionHandler.failed(ee.getCause()); } } catch (Exception ie) { - handler.failed(ie); + completionHandler.failed(ie); } if (ioe != null) { throw ioe; @@ -144,37 +145,6 @@ public Connection obtainConnection(final Request request, final GrizzlyResponseF // --------------------------------------------------Package Private Methods - static CompletionHandler wrapHandler(final Request request, final HostnameVerifier verifier, - final CompletionHandler delegate, final SSLFilter sslFilter) { - final UriComponents uri = request.getURI(); - if (Utils.isSecure(uri) && verifier != null) { - SSLBaseFilter.HandshakeListener handshakeListener = new SSLBaseFilter.HandshakeListener() { - @Override - public void onStart(Connection connection) { - // do nothing - LOGGER.debug("SSL Handshake onStart: "); - } - - @Override - public void onComplete(Connection connection) { - sslFilter.removeHandshakeListener(this); - - final String host = uri.getHost(); - final SSLSession session = SSLUtils.getSSLEngine(connection).getSession(); - LOGGER.debug("SSL Handshake onComplete: session = {}, id = {}, isValid = {}, host = {}", session.toString(), Base64.encode(session.getId()), session.isValid(), host); - - if (!verifier.verify(host, session)) { - connection.close(); // XXX what's the correct way to kill a connection? - IOException e = new ConnectException("Host name verification failed for host " + host); - delegate.failed(e); - } - } - }; - sslFilter.addHandshakeListener(handshakeListener); - } - return delegate; - } - static void markConnectionAsDoNotCache(final Connection c) { DO_NOT_CACHE.set(c, Boolean.TRUE); } 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 2e1d77ee37..9d3151957a 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 @@ -259,8 +259,9 @@ public void onTimeout(Connection connection) { } } final SSLEngineConfigurator configurator = new SSLEngineConfigurator(context, true, false, false); - final SwitchingSSLFilter filter = new SwitchingSSLFilter(configurator); - secure.add(filter); + final SwitchingSSLFilter sslFilter = new SwitchingSSLFilter(configurator); + secure.add(sslFilter); + GrizzlyAsyncHttpProviderConfig providerConfig = (GrizzlyAsyncHttpProviderConfig) clientConfig.getAsyncHttpProviderConfig(); boolean npnEnabled = NextProtoNegSupport.isEnabled(); @@ -335,7 +336,7 @@ public void onTimeout(Connection connection) { } else { pool = null; } - connectionManager = new ConnectionManager(this, pool, secure, nonSecure, filter); + connectionManager = new ConnectionManager(this, pool, secure, nonSecure); } diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/SwitchingSSLFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/SwitchingSSLFilter.java index 3085c950e8..c8075ca630 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/SwitchingSSLFilter.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/SwitchingSSLFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -13,7 +13,15 @@ package org.asynchttpclient.providers.grizzly.filters; +import java.io.IOException; +import java.net.ConnectException; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLSession; import org.asynchttpclient.providers.grizzly.filters.events.SSLSwitchingEvent; +import org.asynchttpclient.util.Base64; +import org.glassfish.grizzly.CompletionHandler; import org.glassfish.grizzly.Connection; import org.glassfish.grizzly.EmptyCompletionHandler; import org.glassfish.grizzly.Grizzly; @@ -25,11 +33,9 @@ import org.glassfish.grizzly.filterchain.NextAction; import org.glassfish.grizzly.ssl.SSLEngineConfigurator; import org.glassfish.grizzly.ssl.SSLFilter; - -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLHandshakeException; - -import java.io.IOException; +import org.glassfish.grizzly.ssl.SSLUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * SSL Filter that may be present within the FilterChain and may be @@ -45,6 +51,8 @@ public final class SwitchingSSLFilter extends SSLFilter { private static final Attribute HANDSHAKE_ERROR = Grizzly.DEFAULT_ATTRIBUTE_BUILDER.createAttribute(SwitchingSSLFilter.class .getName() + "-HANDSHAKE-ERROR"); + private final static Logger LOGGER = LoggerFactory.getLogger(SwitchingSSLFilter.class); + // ------------------------------------------------------------ Constructors public SwitchingSSLFilter(final SSLEngineConfigurator clientConfig) { @@ -161,4 +169,51 @@ private static void setError(final Connection c, Throwable t) { private static void enableRead(final Connection c) throws IOException { c.enableIOEvent(IOEvent.READ); } + + // ================= HostnameVerifier section ======================== + + public static CompletionHandler wrapWithHostnameVerifierHandler( + final CompletionHandler delegateCompletionHandler, + final HostnameVerifier verifier, final String host) { + + return new CompletionHandler() { + + public void cancelled() { + if (delegateCompletionHandler != null) { + delegateCompletionHandler.cancelled(); + } + } + + public void failed(final Throwable throwable) { + if (delegateCompletionHandler != null) { + delegateCompletionHandler.failed(throwable); + } + } + + public void completed(final Connection connection) { + final SSLSession session = SSLUtils.getSSLEngine(connection).getSession(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("SSL Handshake onComplete: session = {}, id = {}, isValid = {}, host = {}", + session.toString(), Base64.encode(session.getId()), session.isValid(), host); + } + + if (!verifier.verify(host, session)) { + connection.terminateSilently(); + + if (delegateCompletionHandler != null) { + IOException e = new ConnectException("Host name verification failed for host " + host); + delegateCompletionHandler.failed(e); + } + } else if (delegateCompletionHandler != null) { + delegateCompletionHandler.completed(connection); + } + } + + public void updated(final Connection connection) { + if (delegateCompletionHandler != null) { + delegateCompletionHandler.updated(connection); + } + } + }; + } } From f171c9a13a150824f0b4d898dcbc03ddd2af6e4a Mon Sep 17 00:00:00 2001 From: oleksiys Date: Tue, 15 Jul 2014 12:08:15 -0700 Subject: [PATCH 0092/2020] [master] + rework (#2) the fix for the issue #525 https://github.com/AsyncHttpClient/async-http-client/pull/525 "Move the hostname verification to after the SSL handshake has completed" --- .../grizzly/filters/SwitchingSSLFilter.java | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/SwitchingSSLFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/SwitchingSSLFilter.java index c8075ca630..b0449e82d2 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/SwitchingSSLFilter.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/SwitchingSSLFilter.java @@ -191,20 +191,26 @@ public void failed(final Throwable throwable) { } public void completed(final Connection connection) { - final SSLSession session = SSLUtils.getSSLEngine(connection).getSession(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("SSL Handshake onComplete: session = {}, id = {}, isValid = {}, host = {}", - session.toString(), Base64.encode(session.getId()), session.isValid(), host); - } + if (getHandshakeError(connection) == null) { + final SSLSession session = SSLUtils.getSSLEngine(connection).getSession(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("SSL Handshake onComplete: session = {}, id = {}, isValid = {}, host = {}", + session.toString(), Base64.encode(session.getId()), session.isValid(), host); + } + + if (!verifier.verify(host, session)) { + connection.terminateSilently(); - if (!verifier.verify(host, session)) { - connection.terminateSilently(); - - if (delegateCompletionHandler != null) { - IOException e = new ConnectException("Host name verification failed for host " + host); - delegateCompletionHandler.failed(e); + if (delegateCompletionHandler != null) { + IOException e = new ConnectException("Host name verification failed for host " + host); + delegateCompletionHandler.failed(e); + } + + return; } - } else if (delegateCompletionHandler != null) { + } + + if (delegateCompletionHandler != null) { delegateCompletionHandler.completed(connection); } } From 4393106266c50f98d7b88b1092d4512d4dec60f1 Mon Sep 17 00:00:00 2001 From: oleksiys Date: Tue, 15 Jul 2014 23:40:43 -0700 Subject: [PATCH 0093/2020] [master] fix issue #608 https://github.com/AsyncHttpClient/async-http-client/issues/608 "Grizzly provider can't deal with wss + proxy" --- providers/grizzly/pom.xml | 2 +- .../filters/AsyncHttpClientFilter.java | 11 +++++--- .../grizzly/filters/ProxyFilter.java | 25 ++++++++++++------- .../websocket/GrizzlyProxyTunnellingTest.java | 5 ---- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/providers/grizzly/pom.xml b/providers/grizzly/pom.xml index b55f00865e..269aea00ba 100644 --- a/providers/grizzly/pom.xml +++ b/providers/grizzly/pom.xml @@ -14,7 +14,7 @@ - 2.3.14 + 2.3.16 1.1 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 f1be2da91d..21dc88c516 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 @@ -469,10 +469,13 @@ private static boolean isWSRequest(final UriComponents requestUri) { private static void convertToUpgradeRequest(final HttpTxContext ctx) { - UriComponents originalUri = ctx.getRequestUri(); - String newScheme = originalUri.getScheme().equalsIgnoreCase("https") ? "wss" : "ws"; - ctx.setWsRequestURI(originalUri); - ctx.setRequestUri(originalUri.withNewScheme(newScheme)); + final UriComponents requestUri = ctx.getRequestUri(); + + ctx.setWsRequestURI(requestUri); + ctx.setRequestUri(requestUri.withNewScheme( + "ws".equals(requestUri.getScheme()) + ? "http" + : "https")); } private void addGeneralHeaders(final Request request, final HttpRequestPacket requestPacket) { 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 f233d40e1f..ded8031daf 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -29,6 +29,7 @@ import org.glassfish.grizzly.http.util.Header; import java.io.IOException; +import org.glassfish.grizzly.http.HttpPacket; /** * This Filter will be placed in the FilterChain when a request is being @@ -56,15 +57,21 @@ public ProxyFilter(final ProxyServer proxyServer, final AsyncHttpClientConfig co @Override 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); - assert (context != null); - Request req = context.getRequest(); - if (!secure) { - request.setRequestURI(req.getURI().toUrl()); + final Object msg = ctx.getMessage(); + if (HttpPacket.isHttp(msg)) { + HttpPacket httpPacket = (HttpPacket) msg; + final HttpRequestPacket request = (HttpRequestPacket) httpPacket.getHttpHeader(); + if (!request.isCommitted()) { + HttpTxContext context = HttpTxContext.get(ctx); + assert (context != null); + Request req = context.getRequest(); + if (!secure) { + request.setRequestURI(req.getURI().toUrl()); + } + addProxyHeaders(getRealm(req), request); + } } - addProxyHeaders(getRealm(req), request); + return ctx.getInvokeAction(); } diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyProxyTunnellingTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyProxyTunnellingTest.java index 6d69758e71..0d864e1fc2 100644 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyProxyTunnellingTest.java +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/websocket/GrizzlyProxyTunnellingTest.java @@ -25,9 +25,4 @@ public class GrizzlyProxyTunnellingTest extends ProxyTunnellingTest { public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { return GrizzlyProviderUtil.grizzlyProvider(config); } - - @Test(timeOut = 60000, enabled = false) - public void echoText() throws Exception { - // FIXME - } } From ee3f56dad3c15d40cc8df7a0c96b0daab45073d6 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 16 Jul 2014 22:55:44 +0200 Subject: [PATCH 0094/2020] Refactor Netty channel pool, port #624 on master, close #201, close #623 --- .../netty/NettyAsyncHttpProvider.java | 13 +- .../netty/NettyAsyncHttpProviderConfig.java | 2 +- .../netty/channel/ChannelManager.java | 168 ++++++++++++++++++ .../providers/netty/channel/Channels.java | 156 ++++++---------- .../netty/channel/{ => pool}/ChannelPool.java | 30 ++-- .../{ => pool}/DefaultChannelPool.java | 136 ++++++-------- .../NoopChannelPool.java} | 12 +- .../providers/netty/handler/HttpProtocol.java | 20 +-- .../providers/netty/handler/Processor.java | 2 +- .../providers/netty/handler/Protocol.java | 12 +- .../netty/request/NettyConnectListener.java | 48 ++++- .../netty/request/NettyRequestSender.java | 93 +++++----- .../netty/NettyConnectionPoolTest.java | 18 +- 13 files changed, 401 insertions(+), 309 deletions(-) create mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java rename providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/{ => pool}/ChannelPool.java (55%) rename providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/{ => pool}/DefaultChannelPool.java (68%) rename providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/{NonChannelPool.java => pool/NoopChannelPool.java} (73%) 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 01d71e1baf..5fb2104212 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 @@ -32,7 +32,6 @@ public class NettyAsyncHttpProvider implements AsyncHttpProvider { private static final Logger LOGGER = LoggerFactory.getLogger(NettyAsyncHttpProvider.class); - private final AsyncHttpClientConfig config; private final NettyAsyncHttpProviderConfig nettyConfig; private final AtomicBoolean closed = new AtomicBoolean(false); private final Channels channels; @@ -40,9 +39,8 @@ public class NettyAsyncHttpProvider implements AsyncHttpProvider { public NettyAsyncHttpProvider(AsyncHttpClientConfig config) { - this.config = config; nettyConfig = config.getAsyncHttpProviderConfig() instanceof NettyAsyncHttpProviderConfig ? // - NettyAsyncHttpProviderConfig.class.cast(config.getAsyncHttpProviderConfig()) + (NettyAsyncHttpProviderConfig) config.getAsyncHttpProviderConfig() : new NettyAsyncHttpProviderConfig(); channels = new Channels(config, nettyConfig); @@ -50,15 +48,6 @@ public NettyAsyncHttpProvider(AsyncHttpClientConfig config) { channels.configureProcessor(requestSender, closed); } - @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() - availablePermits,// - channels.openChannels.toString(),// - channels.channelPool.toString()); - } - @Override public void close() { if (closed.compareAndSet(false, true)) { 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 b03b6ef31b..554e1643c1 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 @@ -18,7 +18,7 @@ import org.asynchttpclient.AsyncHttpProviderConfig; import org.asynchttpclient.SSLEngineFactory; -import org.asynchttpclient.providers.netty.channel.ChannelPool; +import org.asynchttpclient.providers.netty.channel.pool.ChannelPool; import org.asynchttpclient.providers.netty.response.EagerResponseBodyPart; import org.asynchttpclient.providers.netty.response.LazyResponseBodyPart; import org.asynchttpclient.providers.netty.response.NettyResponseBodyPart; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java new file mode 100644 index 0000000000..637a619c6f --- /dev/null +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2014 AsyncHttpClient Project. 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.channel; + +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.providers.netty.channel.pool.ChannelPool; +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; +import org.asynchttpclient.providers.netty.util.CleanupChannelGroup; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.netty.channel.Channel; +import io.netty.channel.group.ChannelGroup; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; + +public class ChannelManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(ChannelManager.class); + + private final ChannelPool channelPool; + private final boolean maxTotalConnectionsEnabled; + private final Semaphore freeChannels; + private final ChannelGroup openChannels; + private final int maxConnectionsPerHost; + private final boolean maxConnectionsPerHostEnabled; + private final ConcurrentHashMap freeChannelsPerHost; + private final ConcurrentHashMap channel2KeyPool; + + public ChannelManager(AsyncHttpClientConfig config, ChannelPool channelPool) { + this.channelPool = channelPool; + + maxTotalConnectionsEnabled = config.getMaxTotalConnections() > 0; + + if (maxTotalConnectionsEnabled) { + openChannels = new CleanupChannelGroup("asyncHttpClient") { + @Override + public boolean remove(Object o) { + boolean removed = super.remove(o); + if (removed) { + freeChannels.release(); + if (maxConnectionsPerHostEnabled) { + String poolKey = channel2KeyPool.remove(Channel.class.cast(o)); + if (poolKey != null) { + Semaphore freeChannelsForHost = freeChannelsPerHost.get(poolKey); + if (freeChannelsForHost != null) + freeChannelsForHost.release(); + } + } + } + return removed; + } + }; + freeChannels = new Semaphore(config.getMaxTotalConnections()); + } else { + openChannels = new CleanupChannelGroup("asyncHttpClient"); + freeChannels = null; + } + + maxConnectionsPerHost = config.getMaxConnectionPerHost(); + maxConnectionsPerHostEnabled = config.getMaxConnectionPerHost() > 0; + + if (maxConnectionsPerHostEnabled) { + freeChannelsPerHost = new ConcurrentHashMap(); + channel2KeyPool = new ConcurrentHashMap(); + } else { + freeChannelsPerHost = null; + channel2KeyPool = null; + } + } + + public final void tryToOfferChannelToPool(Channel channel, boolean keepAlive, String poolKey) { + if (keepAlive && channel.isActive()) { + LOGGER.debug("Adding key: {} for channel {}", poolKey, channel); + channelPool.offer(channel, poolKey); + if (maxConnectionsPerHostEnabled) + channel2KeyPool.putIfAbsent(channel, poolKey); + Channels.setDiscard(channel); + } else { + // not offered + closeChannel(channel); + } + } + + public Channel poll(String uri) { + return channelPool.poll(uri); + } + + public boolean removeAll(Channel connection) { + return channelPool.removeAll(connection); + } + + private boolean tryAcquireGlobal() { + return !maxTotalConnectionsEnabled || freeChannels.tryAcquire(); + } + + private Semaphore getFreeConnectionsForHost(String poolKey) { + Semaphore freeConnections = freeChannelsPerHost.get(poolKey); + if (freeConnections == null) { + // lazy create the semaphore + Semaphore newFreeConnections = new Semaphore(maxConnectionsPerHost); + freeConnections = freeChannelsPerHost.putIfAbsent(poolKey, newFreeConnections); + if (freeConnections == null) + freeConnections = newFreeConnections; + } + return freeConnections; + } + + private boolean tryAcquirePerHost(String poolKey) { + return !maxConnectionsPerHostEnabled || getFreeConnectionsForHost(poolKey).tryAcquire(); + } + + public boolean preemptChannel(String poolKey) { + return channelPool.isOpen() && tryAcquireGlobal() && tryAcquirePerHost(poolKey); + } + + public void destroy() { + channelPool.destroy(); + openChannels.close(); + + for (Channel channel : openChannels) { + Object attachment = Channels.getDefaultAttribute(channel); + if (attachment instanceof NettyResponseFuture) { + NettyResponseFuture future = (NettyResponseFuture) attachment; + future.cancelTimeouts(); + } + } + } + + public void closeChannel(Channel channel) { + removeAll(channel); + Channels.setDiscard(channel); + + // The channel may have already been removed if a timeout occurred, and this method may be called just after. + if (channel != null) { + LOGGER.debug("Closing Channel {} ", channel); + try { + channel.close(); + } catch (Throwable t) { + LOGGER.debug("Error closing a connection", t); + } + openChannels.remove(channel); + } + } + + public void abortChannelPreemption(String poolKey) { + if (maxTotalConnectionsEnabled) + freeChannels.release(); + if (maxConnectionsPerHostEnabled) + getFreeConnectionsForHost(poolKey).release(); + } + + public void registerOpenChannel(Channel channel) { + openChannels.add(channel); + } +} \ No newline at end of file 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 1c1eb6ed04..3dfc57f9be 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,13 +25,14 @@ import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.ConnectionPoolKeyStrategy; import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.providers.netty.Callback; import org.asynchttpclient.providers.netty.DiscardEvent; import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; +import org.asynchttpclient.providers.netty.channel.pool.ChannelPool; +import org.asynchttpclient.providers.netty.channel.pool.DefaultChannelPool; +import org.asynchttpclient.providers.netty.channel.pool.NoopChannelPool; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.handler.Processor; import org.asynchttpclient.providers.netty.request.NettyRequestSender; -import org.asynchttpclient.providers.netty.util.CleanupChannelGroup; import org.asynchttpclient.uri.UriComponents; import org.asynchttpclient.util.SslUtils; import org.slf4j.Logger; @@ -43,7 +44,6 @@ import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; -import io.netty.channel.group.ChannelGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.HttpClientCodec; @@ -65,7 +65,7 @@ import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Map.Entry; -import java.util.concurrent.Semaphore; +import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -95,20 +95,7 @@ public class Channels { private final Bootstrap webSocketBootstrap; private final Bootstrap secureWebSocketBootstrap; - public final ChannelPool channelPool; - public final Semaphore freeConnections; - public final boolean trackConnections; - public final ChannelGroup openChannels = new CleanupChannelGroup("asyncHttpClient") { - @Override - public boolean remove(Object o) { - boolean removed = super.remove(o); - if (removed && trackConnections) { - freeConnections.release(); - } - return removed; - } - }; - + public final ChannelManager channelManager; private final boolean allowStopNettyTimer; private final Timer nettyTimer; private final long handshakeTimeoutInMillis; @@ -147,17 +134,10 @@ public Channels(final AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig if (config.isAllowPoolingConnection()) { cp = new DefaultChannelPool(config, nettyTimer); } else { - cp = new NonChannelPool(); + cp = new NoopChannelPool(); } } - this.channelPool = cp; - if (config.getMaxTotalConnections() != -1) { - trackConnections = true; - freeConnections = new Semaphore(config.getMaxTotalConnections()); - } else { - trackConnections = false; - freeConnections = null; - } + this.channelManager = new ChannelManager(config, cp); for (Entry, Object> entry : nettyProviderConfig.propertiesSet()) { ChannelOption key = entry.getKey(); @@ -182,11 +162,11 @@ private Timer newNettyTimer() { } public SslHandler createSslHandler(String peerHost, int peerPort) throws IOException, GeneralSecurityException { - + SSLEngine sslEngine = null; if (nettyProviderConfig.getSslEngineFactory() != null) { sslEngine = nettyProviderConfig.getSslEngineFactory().newSSLEngine(); - + } else { SSLContext sslContext = config.getSSLContext(); if (sslContext == null) @@ -195,11 +175,11 @@ public SslHandler createSslHandler(String peerHost, int peerPort) throws IOExcep sslEngine = sslContext.createSSLEngine(peerHost, peerPort); sslEngine.setUseClientMode(true); } - + SslHandler sslHandler = new SslHandler(sslEngine); if (handshakeTimeoutInMillis > 0) sslHandler.setHandshakeTimeoutMillis(handshakeTimeoutInMillis); - + return sslHandler; } @@ -244,8 +224,7 @@ protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline()// - .addLast(SSL_HANDLER, new SslInitializer(Channels.this)) - .addLast(HTTP_HANDLER, newHttpClientCodec()); + .addLast(SSL_HANDLER, new SslInitializer(Channels.this)).addLast(HTTP_HANDLER, newHttpClientCodec()); if (config.isCompressionEnabled()) { pipeline.addLast(INFLATER_HANDLER, new HttpContentDecompressor()); @@ -281,15 +260,7 @@ public Bootstrap getBootstrap(UriComponents uri, boolean useSSl, boolean useProx } public void close() { - channelPool.destroy(); - for (Channel channel : openChannels) { - Object attribute = getDefaultAttribute(channel); - if (attribute instanceof NettyResponseFuture) { - NettyResponseFuture future = (NettyResponseFuture) attribute; - future.cancelTimeouts(); - } - } - openChannels.close(); + channelManager.destroy(); if (allowReleaseEventLoopGroup) eventLoopGroup.shutdownGracefully(); @@ -354,7 +325,7 @@ public static void upgradePipelineForWebSockets(Channel channel) { } public Channel pollAndVerifyCachedChannel(UriComponents uri, ProxyServer proxy, ConnectionPoolKeyStrategy connectionPoolKeyStrategy) { - final Channel channel = channelPool.poll(connectionPoolKeyStrategy.getKey(uri, proxy)); + final Channel channel = channelManager.poll(connectionPoolKeyStrategy.getKey(uri, proxy)); if (channel != null) { LOGGER.debug("Using cached Channel {}\n for uri {}\n", channel, uri); @@ -368,82 +339,48 @@ public Channel pollAndVerifyCachedChannel(UriComponents uri, ProxyServer proxy, return channel; } - public boolean acquireConnection(AsyncHandler asyncHandler) throws IOException { + public boolean preemptChannel(AsyncHandler asyncHandler, String poolKey) throws IOException { - if (!channelPool.canCacheConnection()) { - IOException ex = new IOException("Too many connections " + config.getMaxTotalConnections()); + boolean channelPreempted = false; + if (channelManager.preemptChannel(poolKey)) { + channelPreempted = true; + } else { + IOException ex = new IOException(String.format("Too many connections %s", config.getMaxTotalConnections())); try { asyncHandler.onThrowable(ex); - } catch (Throwable t) { - LOGGER.warn("!connectionsPool.canCacheConnection()", t); + } catch (Exception e) { + LOGGER.warn("asyncHandler.onThrowable crashed", e); } throw ex; } - - if (trackConnections) { - if (freeConnections.tryAcquire()) { - return true; - } else { - IOException ex = new IOException("Too many connections " + config.getMaxTotalConnections()); - try { - asyncHandler.onThrowable(ex); - } catch (Throwable t) { - LOGGER.warn("!connectionsPool.canCacheConnection()", t); - } - throw ex; - } - } - - return false; - } - - public void registerChannel(Channel channel) { - openChannels.add(channel); - } - - public boolean offerToPool(String key, Channel channel) { - return channelPool.offer(key, channel); + return channelPreempted; } - public void releaseFreeConnections() { - freeConnections.release(); + public void tryToOfferChannelToPool(Channel channel, boolean keepAlive, String poolKey) { + channelManager.tryToOfferChannelToPool(channel, keepAlive, poolKey); } - public void removeFromPool(Channel channel) { - channelPool.removeAll(channel); + public void abortChannelPreemption(String poolKey) { + channelManager.abortChannelPreemption(poolKey); } public void closeChannel(Channel channel) { - removeFromPool(channel); - finishChannel(channel); + channelManager.closeChannel(channel); } - public void finishChannel(Channel channel) { - setDefaultAttribute(channel, DiscardEvent.INSTANCE); - - // The channel may have already been removed if a timeout occurred, and - // this method may be called just after. - if (channel == null) - return; - - LOGGER.debug("Closing Channel {} ", channel); - try { - channel.close(); - } catch (Throwable t) { - LOGGER.debug("Error closing a connection", t); - } + public final Callable> newDrainCallable(final NettyResponseFuture future, final Channel channel, + final boolean keepAlive, final String poolKey) { - openChannels.remove(channel); + return new Callable>() { + public NettyResponseFuture call() throws Exception { + channelManager.tryToOfferChannelToPool(channel, keepAlive, poolKey); + return null; + } + }; } public void drainChannel(final Channel channel, final NettyResponseFuture future) { - setDefaultAttribute(channel, new Callback(future) { - public void call() throws Exception { - if (!(future.isKeepAlive() && channel.isActive() && channelPool.offer(getPoolKey(future), channel))) { - finishChannel(channel); - } - } - }); + setDefaultAttribute(channel, newDrainCallable(future, channel, future.isKeepAlive(), getPoolKey(future))); } public String getPoolKey(NettyResponseFuture future) { @@ -451,17 +388,16 @@ public String getPoolKey(NettyResponseFuture future) { } public void removeAll(Channel channel) { - channelPool.removeAll(channel); + channelManager.removeAll(channel); } public void abort(NettyResponseFuture future, Throwable t) { + Channel channel = future.channel(); - if (channel != null && openChannels.contains(channel)) { - closeChannel(channel); - openChannels.remove(channel); - } + if (channel != null) + channelManager.closeChannel(channel); - if (!future.isCancelled() && !future.isDone()) { + if (!future.isDone()) { LOGGER.debug("Aborting Future {}\n", future); LOGGER.debug(t.getMessage(), t); } @@ -485,4 +421,12 @@ public static Object getDefaultAttribute(Channel channel) { public static void setDefaultAttribute(Channel channel, Object o) { channel.attr(DEFAULT_ATTRIBUTE).set(o); } + + public static void setDiscard(Channel channel) { + setDefaultAttribute(channel, DiscardEvent.INSTANCE); + } + + public void registerOpenChannel(Channel channel) { + channelManager.registerOpenChannel(channel); + } } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/ChannelPool.java similarity index 55% rename from providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelPool.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/ChannelPool.java index 39f5c7a5af..4f1d96ba16 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelPool.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/ChannelPool.java @@ -14,47 +14,47 @@ * under the License. * */ -package org.asynchttpclient.providers.netty.channel; +package org.asynchttpclient.providers.netty.channel.pool; import io.netty.channel.Channel; public interface ChannelPool { /** - * Add a connection to the pool + * Add a channel to the pool * - * @param uri a uri used to retrieve the cached connection - * @param connection an I/O connection + * @param poolKey a key used to retrieve the cached channel + * @param channel an I/O channel * @return true if added. */ - boolean offer(String uri, Channel connection); + boolean offer(Channel channel, String poolKey); /** - * Remove the connection associated with the uri. + * Remove the channel associated with the uri. * * @param uri the uri used when invoking addConnection - * @return the connection associated with the uri + * @return the channel associated with the uri */ Channel poll(String uri); /** - * Remove all connections from the cache. A connection might have been associated with several uri. + * Remove all channels from the cache. A channel might have been associated with several uri. * - * @param connection a connection - * @return the true if the connection has been removed + * @param channel a channel + * @return the true if the channel has been removed */ - boolean removeAll(Channel connection); + boolean removeAll(Channel channel); /** - * Return true if a connection can be cached. A implementation can decide based on some rules to allow caching + * Return true if a channel can be cached. A implementation can decide based on some rules to allow caching * Calling this method is equivalent of checking the returned value of {@link ChannelPool#offer(Object, Object)} * - * @return true if a connection can be cached. + * @return true if a channel can be cached. */ - boolean canCacheConnection(); + boolean isOpen(); /** - * Destroy all connections that has been cached by this instance. + * Destroy all channels that has been cached by this instance. */ void destroy(); } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java similarity index 68% rename from providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java index a1c1c9f4ed..f46a63d310 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/DefaultChannelPool.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java @@ -1,21 +1,22 @@ /* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. + * Copyright (c) 2014 AsyncHttpClient Project. 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. + * 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.channel; +package org.asynchttpclient.providers.netty.channel.pool; import static org.asynchttpclient.util.DateUtils.millisTime; import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty.DiscardEvent; +import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,33 +25,27 @@ import io.netty.util.Timeout; import io.netty.util.Timer; import io.netty.util.TimerTask; -import io.netty.util.internal.chmv8.ConcurrentHashMapV8; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; /** - * A simple implementation of {@link ChannelPool} based on a {@link java.util.concurrent.ConcurrentHashMap} + * A simple implementation of {@link com.ning.http.client.providers.netty.pool.ChannelPool} based on a {@link java.util.concurrent.ConcurrentHashMap} */ -public class DefaultChannelPool implements ChannelPool { +public final class DefaultChannelPool implements ChannelPool { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultChannelPool.class); - private final ConcurrentHashMapV8> connectionsPool = new ConcurrentHashMapV8>(); - private final ConcurrentHashMapV8 channel2Creation = new ConcurrentHashMapV8(); - private final AtomicInteger size = new AtomicInteger(); + private final ConcurrentHashMap> poolsPerKey = new ConcurrentHashMap>(); + private final ConcurrentHashMap channel2Creation = new ConcurrentHashMap(); private final AtomicBoolean isClosed = new AtomicBoolean(false); private final Timer nettyTimer; private final boolean sslConnectionPoolEnabled; - private final int maxTotalConnections; - private final boolean maxTotalConnectionsDisabled; - private final int maxConnectionPerHost; - private final boolean maxConnectionPerHostDisabled; private final int maxConnectionTTL; private final boolean maxConnectionTTLDisabled; private final long maxIdleTime; @@ -58,8 +53,7 @@ public class DefaultChannelPool implements ChannelPool { private final long cleanerPeriod; public DefaultChannelPool(AsyncHttpClientConfig config, Timer hashedWheelTimer) { - this(config.getMaxTotalConnections(),// - config.getMaxConnectionPerHost(),// + this(config.getMaxConnectionPerHost(),// config.getIdleConnectionInPoolTimeoutInMs(),// config.getMaxConnectionLifeTimeInMs(),// config.isSslConnectionPoolEnabled(),// @@ -67,16 +61,11 @@ public DefaultChannelPool(AsyncHttpClientConfig config, Timer hashedWheelTimer) } public DefaultChannelPool(// - int maxTotalConnections,// int maxConnectionPerHost,// long maxIdleTime,// int maxConnectionTTL,// boolean sslConnectionPoolEnabled,// Timer nettyTimer) { - this.maxTotalConnections = maxTotalConnections; - maxTotalConnectionsDisabled = maxTotalConnections <= 0; - this.maxConnectionPerHost = maxConnectionPerHost; - maxConnectionPerHostDisabled = maxConnectionPerHost <= 0; this.sslConnectionPoolEnabled = sslConnectionPoolEnabled; this.maxIdleTime = maxIdleTime; this.maxConnectionTTL = maxConnectionTTL; @@ -97,11 +86,11 @@ private void scheduleNewIdleChannelDetector(TimerTask task) { private static final class ChannelCreation { final long creationTime; - final String key; + final String poolKey; - ChannelCreation(long creationTime, String key) { + ChannelCreation(long creationTime, String poolKey) { this.creationTime = creationTime; - this.key = key; + this.poolKey = poolKey; } } @@ -137,7 +126,7 @@ private boolean isTTLExpired(Channel channel, long now) { } private boolean isRemotelyClosed(Channel channel) { - return !channel.isOpen(); + return !channel.isActive(); } private final class IdleChannelDetector implements TimerTask { @@ -163,12 +152,10 @@ private List expiredChannels(ConcurrentLinkedQueue poo } private boolean isChannelCloseable(Channel channel) { - boolean closeable = true; Object attachment = Channels.getDefaultAttribute(channel); if (attachment instanceof NettyResponseFuture) { NettyResponseFuture future = (NettyResponseFuture) attachment; - closeable = !future.isDone() || !future.isCancelled(); - if (!closeable) + if (!future.isDone()) LOGGER.error("Future not in appropriate state %s, not closing", future); } return true; @@ -204,23 +191,23 @@ public void run(Timeout timeout) throws Exception { return; try { - if (LOGGER.isDebugEnabled()) { - for (String key : connectionsPool.keySet()) { - LOGGER.debug("Entry count for : {} : {}", key, connectionsPool.get(key).size()); + if (LOGGER.isDebugEnabled()) + for (String key : poolsPerKey.keySet()) { + LOGGER.debug("Entry count for : {} : {}", key, poolsPerKey.get(key).size()); } - } long start = millisTime(); - int totalCount = size.get(); int closedCount = 0; + int totalCount = 0; - for (ConcurrentLinkedQueue pool : connectionsPool.values()) { + for (ConcurrentLinkedQueue pool : poolsPerKey.values()) { // store in intermediate unsynchronized lists to minimize the impact on the ConcurrentLinkedQueue - List candidateExpiredChannels = expiredChannels(pool, start); - List closedChannels = closeChannels(candidateExpiredChannels); + if (LOGGER.isDebugEnabled()) + totalCount += pool.size(); + + List closedChannels = closeChannels(expiredChannels(pool, start)); pool.removeAll(closedChannels); int poolClosedCount = closedChannels.size(); - size.addAndGet(-poolClosedCount); closedCount += poolClosedCount; } @@ -236,22 +223,23 @@ public void run(Timeout timeout) throws Exception { } } - private boolean addIdleChannel(ConcurrentLinkedQueue idleConnectionForKey, String key, Channel channel, long now) { - - // FIXME computing CLQ.size is not efficient - if (maxConnectionPerHostDisabled || idleConnectionForKey.size() < maxConnectionPerHost) { - IdleChannel idleChannel = new IdleChannel(channel, now); - return idleConnectionForKey.add(idleChannel); + private ConcurrentLinkedQueue getPoolForKey(String key) { + ConcurrentLinkedQueue pool = poolsPerKey.get(key); + if (pool == null) { + // lazy init pool + ConcurrentLinkedQueue newPool = new ConcurrentLinkedQueue(); + pool = poolsPerKey.putIfAbsent(key, newPool); + if (pool == null) + pool = newPool; } - LOGGER.debug("Maximum number of requests per key reached {} for {}", maxConnectionPerHost, key); - return false; + return pool; } - + /** * {@inheritDoc} */ - public boolean offer(String key, Channel channel) { - if (isClosed.get() || (!sslConnectionPoolEnabled && key.startsWith("https"))) + public boolean offer(Channel channel, String poolKey) { + if (isClosed.get() || (!sslConnectionPoolEnabled && poolKey.startsWith("https"))) return false; long now = millisTime(); @@ -259,19 +247,9 @@ public boolean offer(String key, Channel channel) { if (isTTLExpired(channel, now)) return false; - ConcurrentLinkedQueue idleConnectionForKey = connectionsPool.get(key); - if (idleConnectionForKey == null) { - ConcurrentLinkedQueue newPool = new ConcurrentLinkedQueue(); - idleConnectionForKey = connectionsPool.putIfAbsent(key, newPool); - if (idleConnectionForKey == null) - idleConnectionForKey = newPool; - } - - boolean added = addIdleChannel(idleConnectionForKey, key, channel, now); - if (added) { - size.incrementAndGet(); - channel2Creation.putIfAbsent(channel, new ChannelCreation(now, key)); - } + boolean added = getPoolForKey(poolKey).add(new IdleChannel(channel, now)); + if (added) + channel2Creation.putIfAbsent(channel, new ChannelCreation(now, poolKey)); return added; } @@ -279,12 +257,12 @@ public boolean offer(String key, Channel channel) { /** * {@inheritDoc} */ - public Channel poll(String key) { - if (!sslConnectionPoolEnabled && key.startsWith("https")) + public Channel poll(String poolKey) { + if (!sslConnectionPoolEnabled && poolKey.startsWith("https")) return null; IdleChannel idleChannel = null; - ConcurrentLinkedQueue pooledConnectionForKey = connectionsPool.get(key); + ConcurrentLinkedQueue pooledConnectionForKey = poolsPerKey.get(poolKey); if (pooledConnectionForKey != null) { while (idleChannel == null) { idleChannel = pooledConnectionForKey.poll(); @@ -298,11 +276,7 @@ else if (isRemotelyClosed(idleChannel.channel)) { } } } - if (idleChannel != null) { - size.decrementAndGet(); - return idleChannel.channel; - } else - return null; + return idleChannel != null ? idleChannel.channel : null; } /** @@ -310,16 +284,14 @@ else if (isRemotelyClosed(idleChannel.channel)) { */ public boolean removeAll(Channel channel) { ChannelCreation creation = channel2Creation.remove(channel); - return !isClosed.get() && creation != null && connectionsPool.get(creation.key).remove(channel); + return !isClosed.get() && creation != null && poolsPerKey.get(creation.poolKey).remove(channel); } /** * {@inheritDoc} */ - public boolean canCacheConnection() { - // FIXME: doesn't honor per host limit - // FIXME: doesn't account for borrowed channels - return !isClosed.get() && (maxTotalConnectionsDisabled || size.get() < maxTotalConnections); + public boolean isOpen() { + return !isClosed.get(); } /** @@ -329,27 +301,23 @@ public void destroy() { if (isClosed.getAndSet(true)) return; - for (ConcurrentLinkedQueue pool : connectionsPool.values()) { + for (ConcurrentLinkedQueue pool : poolsPerKey.values()) { for (IdleChannel idleChannel : pool) close(idleChannel.channel); } - connectionsPool.clear(); + poolsPerKey.clear(); channel2Creation.clear(); - size.set(0); } private void close(Channel channel) { try { - Channels.setDefaultAttribute(channel, DiscardEvent.INSTANCE); + // FIXME pity to have to do this here + Channels.setDiscard(channel); channel2Creation.remove(channel); channel.close(); } catch (Throwable t) { // noop } } - - public String toString() { - return String.format("NettyConnectionPool: {pool-size: %d}", size.get()); - } -} +} \ No newline at end of file diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/NonChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/NoopChannelPool.java similarity index 73% rename from providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/NonChannelPool.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/NoopChannelPool.java index c3a929ba24..f8d684ad84 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/NonChannelPool.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/NoopChannelPool.java @@ -13,25 +13,25 @@ * License for the specific language governing permissions and limitations * under the License. */ -package org.asynchttpclient.providers.netty.channel; +package org.asynchttpclient.providers.netty.channel.pool; import io.netty.channel.Channel; -public class NonChannelPool implements ChannelPool { +public class NoopChannelPool implements ChannelPool { - public boolean offer(String uri, Channel connection) { + public boolean offer(Channel channel, String poolKey) { return false; } - public Channel poll(String uri) { + public Channel poll(String poolKey) { return null; } - public boolean removeAll(Channel connection) { + public boolean removeAll(Channel channel) { return false; } - public boolean canCacheConnection() { + public boolean isOpen() { return true; } 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 9bd2b36ee9..377dd130cd 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 @@ -168,16 +168,13 @@ private void addType3NTLMAuthorizationHeader(List auth, FluentCaseInsens } } - private void finishUpdate(final NettyResponseFuture future, Channel channel, boolean lastValidChunk) throws IOException { - if (lastValidChunk && future.isKeepAlive()) { + private void finishUpdate(final NettyResponseFuture future, Channel channel, boolean expectOtherChunks) throws IOException { + + boolean keepAlive = future.isKeepAlive(); + if (expectOtherChunks && keepAlive) channels.drainChannel(channel, future); - } else { - if (future.isKeepAlive() && channel.isActive() && channels.offerToPool(channels.getPoolKey(future), channel)) { - markAsDone(future, channel); - return; - } - channels.finishChannel(channel); - } + else + channels.tryToOfferChannelToPool(channel, keepAlive, channels.getPoolKey(future)); markAsDone(future, channel); } @@ -383,11 +380,12 @@ private boolean handleResponseAndExit(final Channel channel, final NettyResponse @Override public void handle(final Channel channel, final NettyResponseFuture future, final Object e) throws Exception { + future.touch(); // The connect timeout occurred. - if (future.isCancelled() || future.isDone()) { - channels.finishChannel(channel); + if (future.isDone()) { + channels.closeChannel(channel); return; } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java index 9d31b94d36..7c69daeba7 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java @@ -115,7 +115,7 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception { } Channel channel = ctx.channel(); - channels.removeFromPool(channel); + channels.removeAll(channel); Object attachment = Channels.getDefaultAttribute(channel); LOGGER.debug("Channel Closed: {} with attachment {}", channel, attachment); 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 0c482212df..59ff36c90e 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 @@ -32,7 +32,6 @@ import org.asynchttpclient.filter.FilterContext; import org.asynchttpclient.filter.FilterException; import org.asynchttpclient.filter.ResponseFilter; -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; @@ -50,6 +49,7 @@ import java.io.IOException; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.Callable; public abstract class Protocol { @@ -91,7 +91,7 @@ public Protocol(Channels channels, AsyncHttpClientConfig config, NettyAsyncHttpP public abstract void onClose(Channel channel); - protected boolean handleRedirectAndExit(Request request, NettyResponseFuture future, HttpResponse response, final Channel channel) + protected boolean handleRedirectAndExit(Request request, final NettyResponseFuture future, HttpResponse response, final Channel channel) throws Exception { io.netty.handler.codec.http.HttpResponseStatus status = response.getStatus(); @@ -147,13 +147,7 @@ protected boolean handleRedirectAndExit(Request request, NettyResponseFuture } } - Callback callback = new Callback(future) { - public void call() throws Exception { - if (!(initialConnectionKeepAlive && channel.isActive() && channels.offerToPool(initialPoolKey, channel))) { - channels.finishChannel(channel); - } - } - }; + Callable> callback = channels.newDrainCallable(future, channel, initialConnectionKeepAlive, initialPoolKey); if (HttpHeaders.isTransferEncodingChunked(response)) { // We must make sure there is no bytes left before diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java index 45f5ef4bfc..05b36fd230 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java @@ -47,15 +47,41 @@ final class NettyConnectListener implements ChannelFutureListener { private final AsyncHttpClientConfig config; private final NettyRequestSender requestSender; private final NettyResponseFuture future; - - public NettyConnectListener(AsyncHttpClientConfig config, NettyRequestSender requestSender, NettyResponseFuture future) { + private final Channels channels; + private final boolean channelPreempted; + private final String poolKey; + + public NettyConnectListener(AsyncHttpClientConfig config,// + NettyRequestSender requestSender,// + NettyResponseFuture future,// + Channels channels,// + boolean channelPreempted,// + String poolKey) { this.requestSender = requestSender; this.config = config; this.future = future; + this.channels = channels; + this.channelPreempted = channelPreempted; + this.poolKey = poolKey; + } + + private void abortChannelPreemption(String poolKey) { + if (channelPreempted) + channels.abortChannelPreemption(poolKey); } - public NettyResponseFuture future() { - return future; + private void writeRequest(Channel channel) { + + LOGGER.debug("\nNon cached request \n{}\n\nusing Channel \n{}\n", future.getNettyRequest(), channel); + + if (future.isDone()) { + abortChannelPreemption(poolKey); + return; + } + + channels.registerOpenChannel(channel); + future.attachChannel(channel, false); + requestSender.writeRequest(future, channel); } public void onFutureSuccess(final Channel channel) throws ConnectException { @@ -72,24 +98,28 @@ public void operationComplete(Future handshakeFuture) throws Ex SSLEngine engine = sslHandler.engine(); SSLSession session = engine.getSession(); - LOGGER.debug("onFutureSuccess: session = {}, id = {}, isValid = {}, host = {}", session.toString(), Base64.encode(session.getId()), session.isValid(), host); - if (!hostnameVerifier.verify(host, session)) { + LOGGER.debug("onFutureSuccess: session = {}, id = {}, isValid = {}, host = {}", session.toString(), + Base64.encode(session.getId()), session.isValid(), host); + if (hostnameVerifier.verify(host, session)) { + writeRequest(channel); + } else { + abortChannelPreemption(poolKey); ConnectException exception = new ConnectException("HostnameVerifier exception"); future.abort(exception); throw exception; - } else { - requestSender.writeRequest(future, channel); } } } }); } else { - requestSender.writeRequest(future, channel); + writeRequest(channel); } } public void onFutureFailure(Channel channel, Throwable cause) { + abortChannelPreemption(poolKey); + boolean canRetry = future.canRetry(); LOGGER.debug("Trying to recover a dead cached channel {} with a retry value of {} ", channel, canRetry); if (canRetry// 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 2fdaab8d50..71bb2d1b1c 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 @@ -78,39 +78,40 @@ public NettyRequestSender(AtomicBoolean closed,// public boolean retry(NettyResponseFuture future, Channel channel) { - boolean success = false; + if (closed.get()) + return false; - if (!closed.get()) { - channels.removeAll(channel); + channels.removeAll(channel); - if (future == null) { - Object attachment = Channels.getDefaultAttribute(channel); - if (attachment instanceof NettyResponseFuture) - future = (NettyResponseFuture) attachment; - } + if (future == null) { + Object attachment = Channels.getDefaultAttribute(channel); + if (attachment instanceof NettyResponseFuture) + future = (NettyResponseFuture) attachment; + } - if (future != null && future.canBeReplayed()) { - future.setState(NettyResponseFuture.STATE.RECONNECTED); - future.getAndSetStatusReceived(false); + if (future != null && future.canBeReplayed()) { + future.setState(NettyResponseFuture.STATE.RECONNECTED); + future.getAndSetStatusReceived(false); - LOGGER.debug("Trying to recover request {}\n", future.getNettyRequest()); - if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) { - AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onRetry(); - } + LOGGER.debug("Trying to recover request {}\n", future.getNettyRequest()); + if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) { + AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onRetry(); + } - try { - sendNextRequest(future.getRequest(), future); - success = true; - } catch (IOException iox) { - future.setState(NettyResponseFuture.STATE.CLOSED); - future.abort(iox); - LOGGER.error("Remotely Closed, unable to recover", iox); - } - } else { - LOGGER.debug("Unable to recover future {}\n", future); + try { + sendNextRequest(future.getRequest(), future); + return true; + + } catch (IOException iox) { + future.setState(NettyResponseFuture.STATE.CLOSED); + future.abort(iox); + LOGGER.error("Remotely Closed, unable to recover", iox); + return false; } + } else { + LOGGER.debug("Unable to recover future {}\n", future); + return false; } - return success; } public boolean applyIoExceptionFiltersAndReplayRequest(NettyResponseFuture future, IOException e, Channel channel) @@ -218,35 +219,35 @@ private ListenableFuture sendRequestWithNewChannel(// // 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.getURI(), useSSl, useProxy); - NettyConnectListener connectListener = new NettyConnectListener(config, this, future); - ChannelFuture channelFuture; - try { - channelFuture = connect(request, uri, proxy, useProxy, bootstrap); + boolean channelPreempted = false; + String poolKey = null; + + // Do not throw an exception when we need an extra connection for a redirect. + if (!reclaimCache) { - } catch (Throwable t) { - if (acquiredConnection) { - channels.releaseFreeConnections(); - } - channels.abort(connectListener.future(), t.getCause() == null ? t : t.getCause()); - return connectListener.future(); + // only compute when maxConnectionPerHost is enabled + // FIXME clean up + if (config.getMaxConnectionPerHost() > 0) + poolKey = channels.getPoolKey(future); + + channelPreempted = channels.preemptChannel(asyncHandler, poolKey); } - channelFuture.addListener(connectListener); + try { + ChannelFuture channelFuture = connect(request, uri, proxy, useProxy, bootstrap); + channelFuture.addListener(new NettyConnectListener(config, this, future, channels, channelPreempted, poolKey)); - LOGGER.debug("\nNon cached request \n{}\n\nusing Channel \n{}\n", connectListener.future().getNettyRequest().getHttpRequest(), - channelFuture.channel()); + } catch (Throwable t) { + if (channelPreempted) + channels.abortChannelPreemption(poolKey); - if (!connectListener.future().isCancelled() || !connectListener.future().isDone()) { - channels.registerChannel(channelFuture.channel()); - connectListener.future().attachChannel(channelFuture.channel(), false); - } else if (acquiredConnection) { - channels.releaseFreeConnections(); + channels.abort(future, t.getCause() == null ? t : t.getCause()); } - return connectListener.future(); + + return future; } private NettyResponseFuture newNettyResponseFuture(UriComponents uri, Request request, AsyncHandler asyncHandler, 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 156aeb9699..ea3f0d573c 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 @@ -19,7 +19,7 @@ import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.Response; import org.asynchttpclient.async.ConnectionPoolTest; -import org.asynchttpclient.providers.netty.channel.ChannelPool; +import org.asynchttpclient.providers.netty.channel.pool.ChannelPool; import org.testng.annotations.Test; import io.netty.channel.Channel; @@ -38,19 +38,19 @@ public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) { public void testInvalidConnectionsPool() { ChannelPool cp = new ChannelPool() { - public boolean offer(String key, Channel connection) { + public boolean offer(Channel channel, String poolKey) { return false; } - public Channel poll(String connection) { + public Channel poll(String poolKey) { return null; } - public boolean removeAll(Channel connection) { + public boolean removeAll(Channel channel) { return false; } - public boolean canCacheConnection() { + public boolean isOpen() { return false; } @@ -82,19 +82,19 @@ public void destroy() { public void testValidConnectionsPool() { ChannelPool cp = new ChannelPool() { - public boolean offer(String key, Channel connection) { + public boolean offer(Channel channel, String poolKey) { return true; } - public Channel poll(String connection) { + public Channel poll(String poolKey) { return null; } - public boolean removeAll(Channel connection) { + public boolean removeAll(Channel channel) { return false; } - public boolean canCacheConnection() { + public boolean isOpen() { return true; } From 6be39c58d044680bad36e2619e82ca9eb266be84 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 17 Jul 2014 01:35:36 +0200 Subject: [PATCH 0095/2020] Properly compute Realm URI, close #626 --- .../java/org/asynchttpclient/util/AuthenticatorUtils.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java b/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java index a74d71a659..594eafa1a3 100644 --- a/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java @@ -12,6 +12,7 @@ */ package org.asynchttpclient.util; +import static org.asynchttpclient.util.AsyncHttpProviderUtils.getNonEmptyPath; import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import org.asynchttpclient.ProxyServer; @@ -37,12 +38,11 @@ private static String computeRealmURI(Realm realm) { return "/"; } else { UriComponents uri = realm.getUri(); - boolean omitQuery = realm.isOmitQuery() && MiscUtils.isNonEmpty(uri.getQuery()); if (realm.isUseAbsoluteURI()) { - return omitQuery ? uri.withNewQuery(null).toUrl() : uri.toUrl(); + return realm.isOmitQuery() && MiscUtils.isNonEmpty(uri.getQuery()) ? uri.withNewQuery(null).toUrl() : uri.toUrl(); } else { - String path = uri.getPath(); - return omitQuery ? path : path + "?" + uri.getQuery(); + String path = getNonEmptyPath(uri); + return realm.isOmitQuery() || !MiscUtils.isNonEmpty(uri.getQuery()) ? path : path + "?" + uri.getQuery(); } } } From e9452c3b3ccbb27e6450093801d08f22123dc304 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 17 Jul 2014 02:08:19 +0200 Subject: [PATCH 0096/2020] Make ThrottleRequestFilter properly release Semaphore, close #567 --- .../extra/AsyncHandlerWrapper.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/extra/AsyncHandlerWrapper.java b/api/src/main/java/org/asynchttpclient/extra/AsyncHandlerWrapper.java index c9b2f0ae7b..3ce9527270 100644 --- a/api/src/main/java/org/asynchttpclient/extra/AsyncHandlerWrapper.java +++ b/api/src/main/java/org/asynchttpclient/extra/AsyncHandlerWrapper.java @@ -8,18 +8,27 @@ import org.slf4j.LoggerFactory; import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; public class AsyncHandlerWrapper implements AsyncHandler { - private final static Logger logger = LoggerFactory.getLogger(AsyncHandlerWrapper.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AsyncHandlerWrapper.class); private final AsyncHandler asyncHandler; private final Semaphore available; + private final AtomicBoolean complete = new AtomicBoolean(false); public AsyncHandlerWrapper(AsyncHandler asyncHandler, Semaphore available) { this.asyncHandler = asyncHandler; this.available = available; } + private void complete() { + if (complete.compareAndSet(false, true)) + available.release(); + if (LOGGER.isDebugEnabled()) + LOGGER.debug("Current Throttling Status after onThrowable {}", available.availablePermits()); + } + /** * {@inheritDoc} */ @@ -28,10 +37,7 @@ public void onThrowable(Throwable t) { try { asyncHandler.onThrowable(t); } finally { - available.release(); - if (logger.isDebugEnabled()) { - logger.debug("Current Throttling Status after onThrowable {}", available.availablePermits()); - } + complete(); } } @@ -64,10 +70,10 @@ public STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception { */ @Override public T onCompleted() throws Exception { - available.release(); - if (logger.isDebugEnabled()) { - logger.debug("Current Throttling Status {}", available.availablePermits()); + try { + return asyncHandler.onCompleted(); + } finally { + complete(); } - return asyncHandler.onCompleted(); } -} \ No newline at end of file +} From baeb431b22ba1ccc83ee21218c46810d7633cad4 Mon Sep 17 00:00:00 2001 From: oleksiys Date: Wed, 16 Jul 2014 21:44:31 -0700 Subject: [PATCH 0097/2020] [master] + minor threads cleanup, use ioThreads config value, fix tests --- .../grizzly/GrizzlyAsyncHttpProvider.java | 19 +++++++++ .../providers/grizzly/Utils.java | 16 ++++++++ .../GrizzlyFeedableBodyGeneratorTest.java | 40 +++++++++---------- .../GrizzlyNoTransferEncodingTest.java | 14 +++---- 4 files changed, 61 insertions(+), 28 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 9d3151957a..fa6a759e53 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 @@ -73,7 +73,9 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.glassfish.grizzly.nio.RoundRobinConnectionDistributor; import org.glassfish.grizzly.spdy.SpdyVersion; +import org.glassfish.grizzly.threadpool.ThreadPoolConfig; /** * A Grizzly 2.0-based implementation of {@link AsyncHttpProvider}. @@ -305,6 +307,23 @@ public void onTimeout(Connection connection) { secure.add(new WebSocketClientFilter()); clientTransport.getAsyncQueueIO().getWriter().setMaxPendingBytesPerConnection(AUTO_SIZE); + + clientTransport.setNIOChannelDistributor( + new RoundRobinConnectionDistributor(clientTransport, false, false)); + + final int kernelThreadsCount = + clientConfig.getIoThreadMultiplier() * + Runtime.getRuntime().availableProcessors(); + + clientTransport.setSelectorRunnersCount(kernelThreadsCount); + clientTransport.setKernelThreadPoolConfig( + ThreadPoolConfig.defaultConfig() + .setCorePoolSize(kernelThreadsCount) + .setMaxPoolSize(kernelThreadsCount) + .setPoolName("grizzly-ahc-kernel") +// .setPoolName(Utils.discoverTestName("grizzly-ahc-kernel")) // uncomment for tests to track down the leaked threads + ); + if (providerConfig != null) { final TransportCustomizer customizer = (TransportCustomizer) providerConfig.getProperty(Property.TRANSPORT_CUSTOMIZER); if (customizer != null) { 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 4a4a434cca..8b7ae72a1a 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 @@ -84,4 +84,20 @@ public static boolean isSpdyConnection(final Connection c) { Boolean result = SPDY.get(c); return result != null ? result : false; } + + static String discoverTestName(final String defaultName) { + final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + final int strackTraceLen = stackTrace.length; + + if (stackTrace[strackTraceLen - 1].getClassName().contains("surefire")) { + for (int i = strackTraceLen - 2; i >= 0; i--) { + if (stackTrace[i].getClassName().contains("org.asynchttpclient.async")) { + return "grizzly-kernel-" + + stackTrace[i].getClassName() + "." + stackTrace[i].getMethodName(); + } + } + } + + return defaultName; + } } diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java index 9e6a500c63..1aac4fcfce 100644 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java @@ -13,24 +13,6 @@ package org.asynchttpclient.providers.grizzly; -import org.asynchttpclient.AsyncCompletionHandler; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.RequestBuilder; -import org.glassfish.grizzly.Buffer; -import org.glassfish.grizzly.http.server.HttpHandler; -import org.glassfish.grizzly.http.server.HttpServer; -import org.glassfish.grizzly.http.server.NetworkListener; -import org.glassfish.grizzly.http.server.Request; -import org.glassfish.grizzly.http.server.Response; -import org.glassfish.grizzly.memory.Buffers; -import org.glassfish.grizzly.ssl.SSLContextConfigurator; -import org.glassfish.grizzly.ssl.SSLEngineConfigurator; -import org.glassfish.grizzly.utils.Charsets; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; - import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -42,14 +24,30 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import org.asynchttpclient.AsyncCompletionHandler; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.DefaultAsyncHttpClient; +import org.asynchttpclient.RequestBuilder; import org.asynchttpclient.providers.grizzly.FeedableBodyGenerator.NonBlockingFeeder; - +import org.glassfish.grizzly.Buffer; +import org.glassfish.grizzly.http.server.HttpHandler; +import org.glassfish.grizzly.http.server.HttpServer; +import org.glassfish.grizzly.http.server.NetworkListener; import static org.glassfish.grizzly.http.server.NetworkListener.DEFAULT_NETWORK_HOST; +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.grizzly.http.server.Response; +import org.glassfish.grizzly.memory.Buffers; import static org.glassfish.grizzly.memory.MemoryManager.DEFAULT_MEMORY_MANAGER; +import org.glassfish.grizzly.ssl.SSLContextConfigurator; +import org.glassfish.grizzly.ssl.SSLEngineConfigurator; +import org.glassfish.grizzly.utils.Charsets; import static org.testng.Assert.assertNull; import static org.testng.Assert.fail; import static org.testng.AssertJUnit.assertEquals; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; public class GrizzlyFeedableBodyGeneratorTest { @@ -67,7 +65,7 @@ public class GrizzlyFeedableBodyGeneratorTest { // ------------------------------------------------------------------- Setup - @BeforeTest + @BeforeMethod public void setup() throws Exception { generateTempFile(); server = new HttpServer(); @@ -91,7 +89,7 @@ public void setup() throws Exception { // --------------------------------------------------------------- Tear Down - @AfterTest + @AfterMethod public void tearDown() { if (!tempFile.delete()) { tempFile.deleteOnExit(); diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java index 8655f47753..ea02d5fc57 100644 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java @@ -13,21 +13,21 @@ package org.asynchttpclient.providers.grizzly; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.AsyncHttpClientConfig; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.DefaultAsyncHttpClient; import org.glassfish.grizzly.http.server.HttpHandler; import org.glassfish.grizzly.http.server.HttpServer; import org.glassfish.grizzly.http.server.NetworkListener; +import static org.glassfish.grizzly.http.server.NetworkListener.DEFAULT_NETWORK_HOST; import org.glassfish.grizzly.http.server.Request; import org.glassfish.grizzly.http.server.Response; import org.testng.Assert; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import static org.glassfish.grizzly.http.server.NetworkListener.DEFAULT_NETWORK_HOST; public class GrizzlyNoTransferEncodingTest { private static final String TEST_MESSAGE = "Hello World!"; @@ -37,7 +37,7 @@ public class GrizzlyNoTransferEncodingTest { // ------------------------------------------------------------------- Setup - @BeforeTest + @BeforeMethod public void setup() throws Exception { server = new HttpServer(); final NetworkListener listener = @@ -70,7 +70,7 @@ public void service(final Request request, // --------------------------------------------------------------- Tear Down - @AfterTest + @AfterMethod public void tearDown() { server.shutdownNow(); server = null; From 25bf191ea9faa5ef1fa092c9fe954117b193fa28 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 17 Jul 2014 10:23:58 +0200 Subject: [PATCH 0098/2020] Drop requestCompressionLevel config parameter, close #627 --- .../AsyncHttpClientConfig.java | 35 ------------------- .../AsyncHttpClientConfigBean.java | 6 ---- .../AsyncHttpClientConfigDefaults.java | 5 --- .../SimpleAsyncHttpClient.java | 5 --- .../providers/netty/channel/Channels.java | 5 --- 5 files changed, 56 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java index 422f560af9..2668db7550 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -98,7 +98,6 @@ public class AsyncHttpClientConfig { protected List requestFilters; protected List responseFilters; protected List ioExceptionFilters; - protected int requestCompressionLevel; protected int maxRequestRetry; protected boolean allowSslConnectionPool; protected boolean disableUrlEncodingForBoundRequests; @@ -140,7 +139,6 @@ private AsyncHttpClientConfig(int maxTotalConnections, // List requestFilters, // List responseFilters, // List ioExceptionFilters, // - int requestCompressionLevel, // int maxRequestRetry, // boolean allowSslConnectionCaching, // boolean disableUrlEncodingForBoundRequests, // @@ -174,7 +172,6 @@ private AsyncHttpClientConfig(int maxTotalConnections, // this.requestFilters = requestFilters; this.responseFilters = responseFilters; this.ioExceptionFilters = ioExceptionFilters; - this.requestCompressionLevel = requestCompressionLevel; this.maxRequestRetry = maxRequestRetry; this.allowSslConnectionPool = allowSslConnectionCaching; this.removeQueryParamOnRedirect = removeQueryParamOnRedirect; @@ -394,15 +391,6 @@ public List getIOExceptionFilters() { return Collections.unmodifiableList(ioExceptionFilters); } - /** - * Return the compression level, or -1 if no compression is used. - * - * @return the compression level, or -1 if no compression is use - */ - public int getRequestCompressionLevel() { - return requestCompressionLevel; - } - /** * Return the number of time the library will retry when an {@link java.io.IOException} is throw by the remote server * @@ -560,7 +548,6 @@ public static class Builder { private boolean useProxySelector = defaultUseProxySelector(); private boolean allowPoolingConnection = defaultAllowPoolingConnection(); private boolean useRelativeURIsWithSSLProxies = defaultUseRelativeURIsWithSSLProxies(); - private int requestCompressionLevel = defaultRequestCompressionLevel(); private int maxRequestRetry = defaultMaxRequestRetry(); private int ioThreadMultiplier = defaultIoThreadMultiplier(); private boolean allowSslConnectionPool = defaultAllowSslConnectionPool(); @@ -887,26 +874,6 @@ public Builder removeIOExceptionFilter(IOExceptionFilter ioExceptionFilter) { return this; } - /** - * Return the compression level, or -1 if no compression is used. - * - * @return the compression level, or -1 if no compression is use - */ - public int getRequestCompressionLevel() { - return requestCompressionLevel; - } - - /** - * Set the compression level, or -1 if no compression is used. - * - * @param requestCompressionLevel compression level, or -1 if no compression is use - * @return this - */ - public Builder setRequestCompressionLevel(int requestCompressionLevel) { - this.requestCompressionLevel = requestCompressionLevel; - return this; - } - /** * Set the number of times a request will be retried when an {@link java.io.IOException} occurs because of a Network exception. * @@ -1123,7 +1090,6 @@ public Builder(AsyncHttpClientConfig prototype) { responseFilters.addAll(prototype.getResponseFilters()); ioExceptionFilters.addAll(prototype.getIOExceptionFilters()); - requestCompressionLevel = prototype.getRequestCompressionLevel(); disableUrlEncodingForBoundRequests = prototype.isDisableUrlEncodingForBoundRequests(); ioThreadMultiplier = prototype.getIoThreadMultiplier(); maxRequestRetry = prototype.getMaxRequestRetry(); @@ -1188,7 +1154,6 @@ public Thread newThread(Runnable r) { requestFilters, // responseFilters, // ioExceptionFilters, // - requestCompressionLevel, // maxRequestRetry, // allowSslConnectionPool, // disableUrlEncodingForBoundRequests, // diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java index 6c677b075e..254d00403d 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java @@ -59,7 +59,6 @@ void configureDefaults() { userAgent = defaultUserAgent(); allowPoolingConnection = defaultAllowPoolingConnection(); useRelativeURIsWithSSLProxies = defaultUseRelativeURIsWithSSLProxies(); - requestCompressionLevel = defaultRequestCompressionLevel(); maxRequestRetry = defaultMaxRequestRetry(); ioThreadMultiplier = defaultIoThreadMultiplier(); allowSslConnectionPool = defaultAllowSslConnectionPool(); @@ -201,11 +200,6 @@ public AsyncHttpClientConfigBean addIoExceptionFilters(IOExceptionFilter ioExcep return this; } - public AsyncHttpClientConfigBean setRequestCompressionLevel(int requestCompressionLevel) { - this.requestCompressionLevel = requestCompressionLevel; - return this; - } - public AsyncHttpClientConfigBean setMaxRequestRetry(int maxRequestRetry) { this.maxRequestRetry = maxRequestRetry; return this; diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java index fa05cec469..47af64f89b 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java @@ -97,11 +97,6 @@ public static boolean defaultUseRelativeURIsWithSSLProxies() { return getBoolean(ASYNC_CLIENT + "useRelativeURIsWithSSLProxies", true); } - // unused/broken, left there for compatibility, fixed in Netty 4 - public static int defaultRequestCompressionLevel() { - return Integer.getInteger(ASYNC_CLIENT + "requestCompressionLevel", -1); - } - public static int defaultMaxRequestRetry() { return Integer.getInteger(ASYNC_CLIENT + "maxRequestRetry", 5); } diff --git a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java index d601d4d82c..ad739b546a 100644 --- a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java +++ b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java @@ -565,11 +565,6 @@ public Builder setSSLContext(final SSLContext sslContext) { return this; } - public Builder setRequestCompressionLevel(int requestCompressionLevel) { - configBuilder.setRequestCompressionLevel(requestCompressionLevel); - return this; - } - public Builder setRealmNtlmDomain(String domain) { realm().setNtlmDomain(domain); return this; 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 3dfc57f9be..54376e29b1 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 @@ -107,11 +107,6 @@ public Channels(final AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig this.config = config; this.nettyProviderConfig = nettyProviderConfig; - // FIXME https://github.com/netty/netty/issues/2132 - if (config.getRequestCompressionLevel() > 0) { - LOGGER.warn("Request was enabled but Netty actually doesn't support this feature, see https://github.com/netty/netty/issues/2132"); - } - // check if external EventLoopGroup is defined allowReleaseEventLoopGroup = nettyProviderConfig.getEventLoopGroup() == null; eventLoopGroup = allowReleaseEventLoopGroup ? new NioEventLoopGroup() : nettyProviderConfig.getEventLoopGroup(); From 8f289c3a94009715bafc8e3c1ea0c12f0079bb2d Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 17 Jul 2014 12:39:28 +0200 Subject: [PATCH 0099/2020] Add missing Host header port when using a virtual host, close #632 --- .../netty/request/NettyRequestFactory.java | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java index d777c9d1c6..0af2bddfc6 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java @@ -88,20 +88,9 @@ else if (proxyServer != null && !(isSecure(uri) && config.isUseRelativeURIsWithS } } - private String hostHeader(Request request, UriComponents uri, Realm realm) { - - String hostHeader = null; - + private String hostHeader(Request request, UriComponents uri) { String host = request.getVirtualHost() != null ? request.getVirtualHost() : uri.getHost(); - - if (host != null) { - if (request.getVirtualHost() != null || uri.getPort() == -1) - hostHeader = host; - else - hostHeader = host + ":" + uri.getPort(); - } - - return hostHeader; + return uri.getPort() == -1 ? host : host + ":" + uri.getPort(); } private String authorizationHeader(Request request, UriComponents uri, ProxyServer proxyServer, Realm realm) throws IOException { @@ -148,9 +137,6 @@ else if (request.getVirtualHost() != null) else host = uri.getHost(); - if (host == null) - host = "127.0.0.1"; - try { authorizationHeader = "Negotiate " + SpnegoEngine.instance().generateToken(host); } catch (Throwable e) { @@ -318,12 +304,11 @@ public NettyRequest newNettyRequest(Request request, UriComponents uri, boolean httpRequest.headers().set(HttpHeaders.Names.CONNECTION, AsyncHttpProviderUtils.keepAliveHeaderValue(config)); } - Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - - String hostHeader = hostHeader(request, uri, realm); + String hostHeader = hostHeader(request, uri); if (hostHeader != null) httpRequest.headers().set(HttpHeaders.Names.HOST, hostHeader); + Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); String authorizationHeader = authorizationHeader(request, uri, proxyServer, realm); if (authorizationHeader != null) // don't override authorization but append From 2da66bf266376470e18db04e472989aea34321ed Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 17 Jul 2014 15:11:07 +0200 Subject: [PATCH 0100/2020] Revert #632 --- .../providers/netty/request/NettyRequestFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java index 0af2bddfc6..55bbc81b85 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java @@ -90,7 +90,7 @@ else if (proxyServer != null && !(isSecure(uri) && config.isUseRelativeURIsWithS private String hostHeader(Request request, UriComponents uri) { String host = request.getVirtualHost() != null ? request.getVirtualHost() : uri.getHost(); - return uri.getPort() == -1 ? host : host + ":" + uri.getPort(); + return request.getVirtualHost() != null || uri.getPort() == -1 ? host : host + ":" + uri.getPort(); } private String authorizationHeader(Request request, UriComponents uri, ProxyServer proxyServer, Realm realm) throws IOException { From 2834f03eeb3ec91464139daa96643d58de873c92 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 17 Jul 2014 15:23:01 +0200 Subject: [PATCH 0101/2020] NettyResponseFuture.get timeout shouldn't cancel http request, close #370 --- .../netty/future/NettyResponseFuture.java | 200 +++++++----------- .../netty/request/NettyRequestSender.java | 6 +- .../timeout/RequestTimeoutTimerTask.java | 8 +- 3 files changed, 90 insertions(+), 124 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 0af60186d3..e95e5bfff6 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 @@ -18,7 +18,6 @@ import static org.asynchttpclient.util.DateUtils.millisTime; import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.ConnectionPoolKeyStrategy; import org.asynchttpclient.ProxyServer; import org.asynchttpclient.Request; @@ -54,14 +53,12 @@ */ public final class NettyResponseFuture extends AbstractListenableFuture { - private static final Logger logger = LoggerFactory.getLogger(NettyResponseFuture.class); - public static final String MAX_RETRY = "org.asynchttpclient.providers.netty.maxRetry"; + private static final Logger LOGGER = LoggerFactory.getLogger(NettyResponseFuture.class); public enum STATE { NEW, POOLED, RECONNECTED, CLOSED, } - private final int requestTimeoutInMs; private volatile boolean requestTimeoutReached; private volatile boolean idleConnectionTimeoutReached; private final long start = millisTime(); @@ -105,37 +102,22 @@ public NettyResponseFuture(UriComponents uri,// Request request,// AsyncHandler asyncHandler,// NettyRequest nettyRequest,// - int requestTimeoutInMs,// - AsyncHttpClientConfig config,// + int maxRetry,// ConnectionPoolKeyStrategy connectionPoolKeyStrategy,// ProxyServer proxyServer) { this.asyncHandler = asyncHandler; - this.requestTimeoutInMs = requestTimeoutInMs; this.request = request; this.nettyRequest = nettyRequest; this.uri = uri; this.connectionPoolKeyStrategy = connectionPoolKeyStrategy; this.proxyServer = proxyServer; - - maxRetry = Integer.getInteger(MAX_RETRY, config.getMaxRequestRetry()); - } - - public UriComponents getURI() { - return uri; - } - - public void setURI(UriComponents uri) { - this.uri = uri; + this.maxRetry = maxRetry; } - public ConnectionPoolKeyStrategy getConnectionPoolKeyStrategy() { - return connectionPoolKeyStrategy; - } - - public ProxyServer getProxyServer() { - return proxyServer; - } + /*********************************************/ + /** java.util.concurrent.Future **/ + /*********************************************/ @Override public boolean isDone() { @@ -147,10 +129,6 @@ public boolean isCancelled() { return isCancelled.get(); } - public void setAsyncHandler(AsyncHandler asyncHandler) { - this.asyncHandler = asyncHandler; - } - @Override public boolean cancel(boolean force) { cancelTimeouts(); @@ -168,7 +146,7 @@ public boolean cancel(boolean force) { try { asyncHandler.onThrowable(new CancellationException()); } catch (Throwable t) { - logger.warn("cancel", t); + LOGGER.warn("cancel", t); } } latch.countDown(); @@ -176,91 +154,22 @@ public boolean cancel(boolean force) { return true; } - /** - * Is the Future still valid - * - * @return true if response has expired and should be terminated. - */ - public boolean hasExpired() { - return requestTimeoutReached || idleConnectionTimeoutReached; - } - - public void setRequestTimeoutReached() { - this.requestTimeoutReached = true; - } - - public boolean isRequestTimeoutReached() { - return requestTimeoutReached; - } - - public void setIdleConnectionTimeoutReached() { - this.idleConnectionTimeoutReached = true; - } - - public boolean isIdleConnectionTimeoutReached() { - return idleConnectionTimeoutReached; - } - @Override public V get() throws InterruptedException, ExecutionException { - try { - return get(requestTimeoutInMs, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - cancelTimeouts(); - throw new ExecutionException(e); - } - } - - public void cancelTimeouts() { - if (timeoutsHolder != null) { - timeoutsHolder.cancel(); - timeoutsHolder = null; - } + if (!isDone()) + latch.await(); + return getContent(); } @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 { - Channels.setDefaultAttribute(channel, DiscardEvent.INSTANCE); - channel.close(); - } catch (Throwable t) { - // Ignore - } - TimeoutException te = new TimeoutException(String.format("No response received after %s %s", l, tu.name().toLowerCase())); - if (!onThrowableCalled.getAndSet(true)) { - try { - try { - asyncHandler.onThrowable(te); - } catch (Throwable t) { - logger.debug("asyncHandler.onThrowable", t); - } - throw new ExecutionException(te); - } finally { - cancelTimeouts(); - } - } - } - isDone.set(true); - - ExecutionException e = exEx.getAndSet(null); - if (e != null) { - throw e; - } - } + if (!isDone() && !latch.await(l, tu)) + throw new TimeoutException(); return getContent(); } private V getContent() throws ExecutionException { + // FIXME why lose the exception??? ExecutionException e = exEx.getAndSet(null); if (e != null) { throw e; @@ -278,7 +187,7 @@ private V getContent() throws ExecutionException { try { asyncHandler.onThrowable(ex); } catch (Throwable t) { - logger.debug("asyncHandler.onThrowable", t); + LOGGER.debug("asyncHandler.onThrowable", t); } throw new RuntimeException(ex); } finally { @@ -291,6 +200,10 @@ private V getContent() throws ExecutionException { return update; } + /*********************************************/ + /** org.asynchttpclient.ListenableFuture **/ + /*********************************************/ + public final void done() { try { @@ -325,13 +238,74 @@ public final void abort(final Throwable t) { try { asyncHandler.onThrowable(t); } catch (Throwable te) { - logger.debug("asyncHandler.onThrowable", te); + LOGGER.debug("asyncHandler.onThrowable", te); } } latch.countDown(); runListeners(); } + @Override + public void touch() { + touch.set(millisTime()); + } + + /*********************************************/ + /** INTERNAL **/ + /*********************************************/ + + public UriComponents getURI() { + return uri; + } + + public void setURI(UriComponents uri) { + this.uri = uri; + } + + public ConnectionPoolKeyStrategy getConnectionPoolKeyStrategy() { + return connectionPoolKeyStrategy; + } + + public ProxyServer getProxyServer() { + return proxyServer; + } + + public void setAsyncHandler(AsyncHandler asyncHandler) { + this.asyncHandler = asyncHandler; + } + + /** + * Is the Future still valid + * + * @return true if response has expired and should be terminated. + */ + public boolean hasExpired() { + return requestTimeoutReached || idleConnectionTimeoutReached; + } + + public void setRequestTimeoutReached() { + this.requestTimeoutReached = true; + } + + public boolean isRequestTimeoutReached() { + return requestTimeoutReached; + } + + public void setIdleConnectionTimeoutReached() { + this.idleConnectionTimeoutReached = true; + } + + public boolean isIdleConnectionTimeoutReached() { + return idleConnectionTimeoutReached; + } + + public void cancelTimeouts() { + if (timeoutsHolder != null) { + timeoutsHolder.cancel(); + timeoutsHolder = null; + } + } + public final Request getRequest() { return request; } @@ -408,11 +382,6 @@ public void setStreamWasAlreadyConsumed(boolean streamWasAlreadyConsumed) { this.streamWasAlreadyConsumed = streamWasAlreadyConsumed; } - @Override - public void touch() { - touch.set(millisTime()); - } - public long getLastTouch() { return touch.get(); } @@ -470,9 +439,9 @@ public boolean canRetry() { } public SocketAddress getChannelRemoteAddress() { - return channel() != null? channel().remoteAddress(): null; + return channel() != null ? channel().remoteAddress() : null; } - + public void setRequest(Request request) { this.request = request; } @@ -492,10 +461,6 @@ public long getStart() { return start; } - public long getRequestTimeoutInMs() { - return requestTimeoutInMs; - } - @Override public String toString() { return "NettyResponseFuture{" + // @@ -503,7 +468,6 @@ public String toString() { ",\n\tisDone=" + isDone + // ",\n\tisCancelled=" + isCancelled + // ",\n\tasyncHandler=" + asyncHandler + // - ",\n\trequestTimeoutInMs=" + requestTimeoutInMs + // ",\n\tnettyRequest=" + nettyRequest + // ",\n\tcontent=" + content + // ",\n\turi=" + uri + // 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 71bb2d1b1c..ff18a9d5b8 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 @@ -253,14 +253,12 @@ private ListenableFuture sendRequestWithNewChannel(// private NettyResponseFuture newNettyResponseFuture(UriComponents uri, Request request, AsyncHandler asyncHandler, NettyRequest nettyRequest, ProxyServer proxyServer) { - int requestTimeout = AsyncHttpProviderUtils.requestTimeout(config, request); NettyResponseFuture f = new NettyResponseFuture(// uri,// request,// asyncHandler,// nettyRequest,// - requestTimeout,// - config,// + config.getMaxRequestRetry(),// request.getConnectionPoolKeyStrategy(),// proxyServer); @@ -388,7 +386,7 @@ private void scheduleTimeouts(NettyResponseFuture nettyResponseFuture) { TimeoutsHolder timeoutsHolder = new TimeoutsHolder(); if (requestTimeoutInMs != -1) { Timeout requestTimeout = channels.newTimeoutInMs(new RequestTimeoutTimerTask(nettyResponseFuture, channels, timeoutsHolder, - closed), requestTimeoutInMs); + closed, requestTimeoutInMs), requestTimeoutInMs); timeoutsHolder.requestTimeout = requestTimeout; } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java index 13cae2eb43..cb4d2ca509 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java @@ -26,12 +26,16 @@ public class RequestTimeoutTimerTask extends TimeoutTimerTask { + private final long requestTimeout; + public RequestTimeoutTimerTask(// NettyResponseFuture nettyResponseFuture,// Channels channels,// TimeoutsHolder timeoutsHolder,// - AtomicBoolean clientClosed) { + AtomicBoolean clientClosed,// + long requestTimeout) { super(nettyResponseFuture, channels, timeoutsHolder, clientClosed); + this.requestTimeout = requestTimeout; } @Override @@ -45,7 +49,7 @@ public void run(Timeout timeout) throws Exception { } if (!nettyResponseFuture.isDone() && !nettyResponseFuture.isCancelled()) { - String message = "Request timed out to " + nettyResponseFuture.getChannelRemoteAddress() + " of " + nettyResponseFuture.getRequestTimeoutInMs() + " ms"; + String message = "Request timed out to " + nettyResponseFuture.getChannelRemoteAddress() + " of " + requestTimeout + " ms"; long age = millisTime() - nettyResponseFuture.getStart(); expire(message, age); nettyResponseFuture.setRequestTimeoutReached(); From 1196a96816d912ad4b454acb25a848fa0f9cdd86 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 17 Jul 2014 15:45:19 +0200 Subject: [PATCH 0102/2020] NettyResponseFuture.getContent shouldn't lose registered exception, close #271 --- .../providers/netty/future/NettyResponseFuture.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 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 e95e5bfff6..f5078b1d21 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 @@ -169,16 +169,15 @@ public V get(long l, TimeUnit tu) throws InterruptedException, TimeoutException, } private V getContent() throws ExecutionException { - // FIXME why lose the exception??? - ExecutionException e = exEx.getAndSet(null); - if (e != null) { + + ExecutionException e = exEx.get(); + if (e != null) throw e; - } V update = content.get(); // No more retry currentRetry.set(maxRetry); - if (exEx.get() == null && !contentProcessed.getAndSet(true)) { + if (!contentProcessed.getAndSet(true)) { try { update = asyncHandler.onCompleted(); } catch (Throwable ex) { From 712c04aef1c0588ce1cff0e5d7ba9ba5a9569556 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 17 Jul 2014 16:16:47 +0200 Subject: [PATCH 0103/2020] Make NettyResponseFuture done and abort mutually exclusive, close #247 --- .../providers/netty/future/NettyResponseFuture.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 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 f5078b1d21..1418c3c4a5 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 @@ -205,14 +205,14 @@ private V getContent() throws ExecutionException { public final void done() { - try { - cancelTimeouts(); + cancelTimeouts(); - if (exEx.get() != null) { - return; - } + if (isDone.getAndSet(true) || isCancelled.get()) + return; + + try { getContent(); - isDone.set(true); + } catch (ExecutionException t) { return; } catch (RuntimeException t) { @@ -227,6 +227,7 @@ public final void done() { } public final void abort(final Throwable t) { + cancelTimeouts(); if (isDone.get() || isCancelled.getAndSet(true)) From b06e0868826df349b8796800c453ec2d44259dd3 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 17 Jul 2014 23:13:24 +0200 Subject: [PATCH 0104/2020] Rename AsyncHttpClientConfig parameters, close #622 --- .../AsyncHttpClientConfig.java | 427 ++++++++---------- .../AsyncHttpClientConfigBean.java | 64 +-- .../AsyncHttpClientConfigDefaults.java | 38 +- .../SimpleAsyncHttpClient.java | 39 +- .../util/AsyncHttpProviderUtils.java | 4 +- .../async/AsyncProvidersBasicTest.java | 6 +- .../async/AuthTimeoutTest.java | 2 +- .../asynchttpclient/async/BasicAuthTest.java | 2 +- .../asynchttpclient/async/BasicHttpsTest.java | 4 +- .../asynchttpclient/async/BodyChunkTest.java | 6 +- .../async/BodyDeferringAsyncHandlerTest.java | 2 +- .../asynchttpclient/async/ChunkingTest.java | 8 +- .../async/ConnectionPoolTest.java | 8 +- .../async/FilePartLargeFileTest.java | 2 +- .../async/IdleStateHandlerTest.java | 2 +- .../async/MaxConnectionsInThreads.java | 4 +- .../async/MaxTotalConnectionTest.java | 6 +- .../async/NoNullResponseTest.java | 4 +- .../async/PerRequestTimeoutTest.java | 6 +- .../async/PutLargeFileTest.java | 2 +- .../org/asynchttpclient/async/RC10KTest.java | 2 +- .../async/RedirectConnectionUsageTest.java | 8 +- .../asynchttpclient/async/RemoteSiteTest.java | 16 +- .../async/SimpleAsyncHttpClientTest.java | 16 +- .../async/TransferListenerTest.java | 2 +- .../providers/grizzly/ConnectionManager.java | 10 +- .../providers/grizzly/EventHandler.java | 2 +- .../grizzly/GrizzlyAsyncHttpProvider.java | 4 +- .../grizzly/GrizzlyConnectionPoolTest.java | 4 +- .../GrizzlyFeedableBodyGeneratorTest.java | 4 +- .../GrizzlyNoTransferEncodingTest.java | 6 +- .../GrizzlyUnexpectingTimeoutTest.java | 2 +- .../netty/channel/ChannelManager.java | 16 +- .../providers/netty/channel/Channels.java | 6 +- .../channel/pool/DefaultChannelPool.java | 8 +- .../netty/request/NettyRequestSender.java | 8 +- .../NettyRequestThrottleTimeoutTest.java | 2 +- .../netty/RetryNonBlockingIssue.java | 16 +- 38 files changed, 360 insertions(+), 408 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java index 2668db7550..265008c2bb 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -34,24 +34,12 @@ import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; /** * Configuration class to use with a {@link AsyncHttpClient}. System property can be also used to configure this * object default behavior by doing: *

* -Dorg.asynchttpclient.AsyncHttpClientConfig.nameOfTheProperty - * ex: - *

- * -Dorg.asynchttpclient.AsyncHttpClientConfig.maxTotalConnections - * -Dorg.asynchttpclient.AsyncHttpClientConfig.maxTotalConnections - * -Dorg.asynchttpclient.AsyncHttpClientConfig.maxConnectionsPerHost - * -Dorg.asynchttpclient.AsyncHttpClientConfig.connectionTimeoutInMs - * -Dorg.asynchttpclient.AsyncHttpClientConfig.idleConnectionInPoolTimeoutInMs - * -Dorg.asynchttpclient.AsyncHttpClientConfig.requestTimeoutInMs - * -Dorg.asynchttpclient.AsyncHttpClientConfig.redirectsEnabled - * -Dorg.asynchttpclient.AsyncHttpClientConfig.maxRedirects */ public class AsyncHttpClientConfig { @@ -78,115 +66,122 @@ public class AsyncHttpClientConfig { AHC_VERSION = prop.getProperty("ahc.version", "UNKNOWN"); } - protected int maxTotalConnections; - protected int maxConnectionPerHost; - protected int connectionTimeOutInMs; - protected int webSocketIdleTimeoutInMs; - protected int idleConnectionInPoolTimeoutInMs; - protected int idleConnectionTimeoutInMs; - protected int requestTimeoutInMs; + protected int connectionTimeout; + + protected int maxConnections; + protected int maxConnectionsPerHost; + + protected int requestTimeout; + protected int readTimeout; + protected int webSocketReadTimeout; + + protected boolean allowPoolingConnections; + protected boolean allowPoolingSslConnections; + protected int pooledConnectionIdleTimeout; + protected int connectionTTL; + + protected SSLContext sslContext; + protected HostnameVerifier hostnameVerifier; + protected boolean acceptAnyCertificate; + protected boolean followRedirect; protected int maxRedirects; + protected boolean removeQueryParamOnRedirect; + protected boolean strict302Handling; + + protected ProxyServerSelector proxyServerSelector; + protected boolean useRelativeURIsWithSSLProxies; + protected boolean compressionEnabled; protected String userAgent; - protected boolean allowPoolingConnection; protected ExecutorService applicationThreadPool; - protected ProxyServerSelector proxyServerSelector; - protected SSLContext sslContext; - protected AsyncHttpProviderConfig providerConfig; protected Realm realm; protected List requestFilters; protected List responseFilters; protected List ioExceptionFilters; protected int maxRequestRetry; - protected boolean allowSslConnectionPool; protected boolean disableUrlEncodingForBoundRequests; - protected boolean removeQueryParamOnRedirect; - protected HostnameVerifier hostnameVerifier; protected int ioThreadMultiplier; - protected boolean strict302Handling; - protected int maxConnectionLifeTimeInMs; - protected boolean useRelativeURIsWithSSLProxies; + protected TimeConverter timeConverter; + protected AsyncHttpProviderConfig providerConfig; + + // AHC 2 specific protected boolean spdyEnabled; protected int spdyInitialWindowSize; protected int spdyMaxConcurrentStreams; - protected TimeConverter timeConverter; - protected boolean acceptAnyCertificate; protected AsyncHttpClientConfig() { } - private AsyncHttpClientConfig(int maxTotalConnections, // - int maxConnectionPerHost, // - int connectionTimeOutInMs, // - int webSocketTimeoutInMs, // - int idleConnectionInPoolTimeoutInMs, // - int idleConnectionTimeoutInMs, // - int requestTimeoutInMs, // - int connectionMaxLifeTimeInMs, // + private AsyncHttpClientConfig(int connectionTimeout,// + int maxConnections,// + int maxConnectionsPerHost,// + int requestTimeout,// + int readTimeout,// + int webSocketIdleTimeout,// + boolean allowPoolingConnection,// + boolean allowSslConnectionPool,// + int idleConnectionInPoolTimeout,// + int maxConnectionLifeTime,// + SSLContext sslContext, // + HostnameVerifier hostnameVerifier,// + boolean acceptAnyCertificate, // boolean followRedirect, // int maxRedirects, // - boolean compressionEnabled, // - String userAgent, // - boolean keepAlive, // - ScheduledExecutorService reaper, // - ExecutorService applicationThreadPool, // + boolean removeQueryParamOnRedirect,// + boolean strict302Handling, // + ExecutorService applicationThreadPool,// ProxyServerSelector proxyServerSelector, // - SSLContext sslContext, // - SSLEngineFactory sslEngineFactory, // - AsyncHttpProviderConfig providerConfig, // - Realm realm, // - List requestFilters, // - List responseFilters, // - List ioExceptionFilters, // + boolean useRelativeURIsWithSSLProxies, // + boolean compressionEnabled, // + String userAgent,// + Realm realm,// + List requestFilters,// + List responseFilters,// + List ioExceptionFilters,// int maxRequestRetry, // - boolean allowSslConnectionCaching, // boolean disableUrlEncodingForBoundRequests, // - boolean removeQueryParamOnRedirect, // - HostnameVerifier hostnameVerifier, // int ioThreadMultiplier, // - boolean strict302Handling, // - boolean useRelativeURIsWithSSLProxies, // + TimeConverter timeConverter,// + AsyncHttpProviderConfig providerConfig,// boolean spdyEnabled, // int spdyInitialWindowSize, // - int spdyMaxConcurrentStreams, // - TimeConverter timeConverter, // - boolean acceptAnyCertificate) { - - this.maxTotalConnections = maxTotalConnections; - this.maxConnectionPerHost = maxConnectionPerHost; - this.connectionTimeOutInMs = connectionTimeOutInMs; - this.webSocketIdleTimeoutInMs = webSocketTimeoutInMs; - this.idleConnectionInPoolTimeoutInMs = idleConnectionInPoolTimeoutInMs; - this.idleConnectionTimeoutInMs = idleConnectionTimeoutInMs; - this.requestTimeoutInMs = requestTimeoutInMs; - this.maxConnectionLifeTimeInMs = connectionMaxLifeTimeInMs; + int spdyMaxConcurrentStreams) { + + this.connectionTimeout = connectionTimeout; + this.maxConnections = maxConnections; + this.maxConnectionsPerHost = maxConnectionsPerHost; + this.requestTimeout = requestTimeout; + this.readTimeout = readTimeout; + this.webSocketReadTimeout = webSocketIdleTimeout; + this.allowPoolingConnections = allowPoolingConnection; + this.allowPoolingSslConnections = allowSslConnectionPool; + this.pooledConnectionIdleTimeout = idleConnectionInPoolTimeout; + this.connectionTTL = maxConnectionLifeTime; + this.sslContext = sslContext; + this.hostnameVerifier = hostnameVerifier; + this.acceptAnyCertificate = acceptAnyCertificate; this.followRedirect = followRedirect; this.maxRedirects = maxRedirects; + this.removeQueryParamOnRedirect = removeQueryParamOnRedirect; + this.strict302Handling = strict302Handling; + this.proxyServerSelector = proxyServerSelector; + this.useRelativeURIsWithSSLProxies = useRelativeURIsWithSSLProxies; this.compressionEnabled = compressionEnabled; this.userAgent = userAgent; - this.allowPoolingConnection = keepAlive; - this.sslContext = sslContext; - this.providerConfig = providerConfig; + this.applicationThreadPool = applicationThreadPool == null ? Executors.newCachedThreadPool() : applicationThreadPool; this.realm = realm; this.requestFilters = requestFilters; this.responseFilters = responseFilters; this.ioExceptionFilters = ioExceptionFilters; this.maxRequestRetry = maxRequestRetry; - this.allowSslConnectionPool = allowSslConnectionCaching; - this.removeQueryParamOnRedirect = removeQueryParamOnRedirect; - this.hostnameVerifier = hostnameVerifier; - this.ioThreadMultiplier = ioThreadMultiplier; - this.strict302Handling = strict302Handling; - this.useRelativeURIsWithSSLProxies = useRelativeURIsWithSSLProxies; - this.applicationThreadPool = applicationThreadPool; - this.proxyServerSelector = proxyServerSelector; this.disableUrlEncodingForBoundRequests = disableUrlEncodingForBoundRequests; + this.ioThreadMultiplier = ioThreadMultiplier; + this.timeConverter = timeConverter; + this.providerConfig = providerConfig; this.spdyEnabled = spdyEnabled; this.spdyInitialWindowSize = spdyInitialWindowSize; this.spdyMaxConcurrentStreams = spdyMaxConcurrentStreams; - this.timeConverter = timeConverter; - this.acceptAnyCertificate = acceptAnyCertificate; } @@ -195,8 +190,8 @@ private AsyncHttpClientConfig(int maxTotalConnections, // * * @return the maximum number of connections an {@link AsyncHttpClient} can handle. */ - public int getMaxTotalConnections() { - return maxTotalConnections; + public int getMaxConnections() { + return maxConnections; } /** @@ -204,8 +199,8 @@ public int getMaxTotalConnections() { * * @return the maximum number of connections per host an {@link AsyncHttpClient} can handle. */ - public int getMaxConnectionPerHost() { - return maxConnectionPerHost; + public int getMaxConnectionsPerHost() { + return maxConnectionsPerHost; } /** @@ -213,16 +208,16 @@ public int getMaxConnectionPerHost() { * * @return the maximum time in millisecond an {@link AsyncHttpClient} can wait when connecting to a remote host */ - public int getConnectionTimeoutInMs() { - return connectionTimeOutInMs; + public int getConnectionTimeout() { + return connectionTimeout; } /** * Return the maximum time, in milliseconds, a {@link org.asynchttpclient.websocket.WebSocket} may be idle before being timed out. * @return the maximum time, in milliseconds, a {@link org.asynchttpclient.websocket.WebSocket} may be idle before being timed out. */ - public int getWebSocketIdleTimeoutInMs() { - return webSocketIdleTimeoutInMs; + public int getWebSocketReadTimeout() { + return webSocketReadTimeout; } /** @@ -230,8 +225,8 @@ public int getWebSocketIdleTimeoutInMs() { * * @return the maximum time in millisecond an {@link AsyncHttpClient} can stay idle. */ - public int getIdleConnectionTimeoutInMs() { - return idleConnectionTimeoutInMs; + public int getReadTimeout() { + return readTimeout; } /** @@ -241,8 +236,8 @@ public int getIdleConnectionTimeoutInMs() { * @return the maximum time in millisecond an {@link AsyncHttpClient} will keep connection * in pool. */ - public int getIdleConnectionInPoolTimeoutInMs() { - return idleConnectionInPoolTimeoutInMs; + public int getPooledConnectionIdleTimeout() { + return pooledConnectionIdleTimeout; } /** @@ -250,8 +245,8 @@ public int getIdleConnectionInPoolTimeoutInMs() { * * @return the maximum time in millisecond an {@link AsyncHttpClient} wait for a response */ - public int getRequestTimeoutInMs() { - return requestTimeoutInMs; + public int getRequestTimeout() { + return requestTimeout; } /** @@ -277,8 +272,8 @@ public int getMaxRedirects() { * * @return true if keep-alive is enabled */ - public boolean isAllowPoolingConnection() { - return allowPoolingConnection; + public boolean isAllowPoolingConnections() { + return allowPoolingConnections; } /** @@ -405,8 +400,8 @@ public int getMaxRequestRetry() { * * @return true is enabled. */ - public boolean isSslConnectionPoolEnabled() { - return allowSslConnectionPool; + public boolean isAllowPoolingSslConnections() { + return allowPoolingSslConnections; } /** @@ -511,8 +506,8 @@ public boolean isUseRelativeURIsWithSSLProxies() { * * @return the maximum time in millisecond an {@link AsyncHttpClient} will keep connection in the pool, or -1 to keep connection while possible. */ - public int getMaxConnectionLifeTimeInMs() { - return maxConnectionLifeTimeInMs; + public int getConnectionTTL() { + return connectionTTL; } /** @@ -532,103 +527,102 @@ public boolean isAcceptAnyCertificate() { * Builder for an {@link AsyncHttpClient} */ public static class Builder { - private int maxTotalConnections = defaultMaxTotalConnections(); - private int maxConnectionPerHost = defaultMaxConnectionPerHost(); - private int connectionTimeOutInMs = defaultConnectionTimeOutInMs(); - private int webSocketIdleTimeoutInMs = defaultWebSocketIdleTimeoutInMs(); - private int idleConnectionInPoolTimeoutInMs = defaultIdleConnectionInPoolTimeoutInMs(); - private int idleConnectionTimeoutInMs = defaultIdleConnectionTimeoutInMs(); - private int requestTimeoutInMs = defaultRequestTimeoutInMs(); - private int maxConnectionLifeTimeInMs = defaultMaxConnectionLifeTimeInMs(); + private int connectionTimeout = defaultConnectionTimeout(); + private int maxConnections = defaultMaxConnections(); + private int maxConnectionsPerHost = defaultMaxConnectionsPerHost(); + private int requestTimeout = defaultRequestTimeout(); + private int readTimeout = defaultReadTimeout(); + private int webSocketReadTimeout = defaultWebSocketReadTimeout(); + private boolean allowPoolingConnections = defaultAllowPoolingConnections(); + private boolean allowPoolingSslConnections = defaultAllowPoolingSslConnections(); + private int pooledConnectionIdleTimeout = defaultPooledConnectionIdleTimeout(); + private int connectionTTL = defaultConnectionTTL(); + private SSLContext sslContext; + private HostnameVerifier hostnameVerifier = defaultHostnameVerifier(); + private boolean acceptAnyCertificate = defaultAcceptAnyCertificate(); private boolean followRedirect = defaultFollowRedirect(); private int maxRedirects = defaultMaxRedirects(); - private boolean compressionEnabled = defaultCompressionEnabled(); - private String userAgent = defaultUserAgent(); - private boolean useProxyProperties = defaultUseProxyProperties(); - private boolean useProxySelector = defaultUseProxySelector(); - private boolean allowPoolingConnection = defaultAllowPoolingConnection(); - private boolean useRelativeURIsWithSSLProxies = defaultUseRelativeURIsWithSSLProxies(); - private int maxRequestRetry = defaultMaxRequestRetry(); - private int ioThreadMultiplier = defaultIoThreadMultiplier(); - private boolean allowSslConnectionPool = defaultAllowSslConnectionPool(); - private boolean disableUrlEncodingForBoundRequests = defaultDisableUrlEncodingForBoundRequests(); private boolean removeQueryParamOnRedirect = defaultRemoveQueryParamOnRedirect(); private boolean strict302Handling = defaultStrict302Handling(); - private HostnameVerifier hostnameVerifier = defaultHostnameVerifier(); - private boolean spdyEnabled = defaultSpdyEnabled(); - private int spdyInitialWindowSize = defaultSpdyInitialWindowSize(); - private int spdyMaxConcurrentStreams = defaultSpdyMaxConcurrentStreams(); - private boolean acceptAnyCertificate = defaultAcceptAnyCertificate(); - - private ScheduledExecutorService reaper; - private ExecutorService applicationThreadPool; private ProxyServerSelector proxyServerSelector = null; - private SSLContext sslContext; - private SSLEngineFactory sslEngineFactory; - private AsyncHttpProviderConfig providerConfig; + private boolean useProxySelector = defaultUseProxySelector(); + private boolean useProxyProperties = defaultUseProxyProperties(); + private boolean useRelativeURIsWithSSLProxies = defaultUseRelativeURIsWithSSLProxies(); + private boolean compressionEnabled = defaultCompressionEnabled(); + private String userAgent = defaultUserAgent(); + private ExecutorService applicationThreadPool; private Realm realm; private final List requestFilters = new LinkedList(); private final List responseFilters = new LinkedList(); private final List ioExceptionFilters = new LinkedList(); + private int maxRequestRetry = defaultMaxRequestRetry(); + private boolean disableUrlEncodingForBoundRequests = defaultDisableUrlEncodingForBoundRequests(); + private int ioThreadMultiplier = defaultIoThreadMultiplier(); private TimeConverter timeConverter; - + private AsyncHttpProviderConfig providerConfig; + + // AHC 2 + private boolean spdyEnabled = defaultSpdyEnabled(); + private int spdyInitialWindowSize = defaultSpdyInitialWindowSize(); + private int spdyMaxConcurrentStreams = defaultSpdyMaxConcurrentStreams(); + public Builder() { } /** * Set the maximum number of connections an {@link AsyncHttpClient} can handle. * - * @param maxTotalConnections the maximum number of connections an {@link AsyncHttpClient} can handle. + * @param maxConnections the maximum number of connections an {@link AsyncHttpClient} can handle. * @return a {@link Builder} */ - public Builder setMaxConnectionsTotal(int maxTotalConnections) { - this.maxTotalConnections = maxTotalConnections; + public Builder setMaxConnections(int maxConnections) { + this.maxConnections = maxConnections; return this; } /** * Set the maximum number of connections per hosts an {@link AsyncHttpClient} can handle. * - * @param maxConnectionPerHost the maximum number of connections per host an {@link AsyncHttpClient} can handle. + * @param maxConnectionsPerHost the maximum number of connections per host an {@link AsyncHttpClient} can handle. * @return a {@link Builder} */ - public Builder setMaxConnectionsPerHost(int maxConnectionPerHost) { - this.maxConnectionPerHost = maxConnectionPerHost; + public Builder setMaxConnectionsPerHost(int maxConnectionsPerHost) { + this.maxConnectionsPerHost = maxConnectionsPerHost; return this; } /** * Set the maximum time in millisecond an {@link AsyncHttpClient} can wait when connecting to a remote host * - * @param connectionTimeOutInMs the maximum time in millisecond an {@link AsyncHttpClient} can wait when connecting to a remote host + * @param connectionTimeout the maximum time in millisecond an {@link AsyncHttpClient} can wait when connecting to a remote host * @return a {@link Builder} */ - public Builder setConnectionTimeoutInMs(int connectionTimeOutInMs) { - this.connectionTimeOutInMs = connectionTimeOutInMs; + public Builder setConnectionTimeout(int connectionTimeout) { + this.connectionTimeout = connectionTimeout; return this; } /** * Set the maximum time in millisecond an {@link org.asynchttpclient.websocket.WebSocket} can stay idle. * - * @param webSocketIdleTimeoutInMs + * @param webSocketIdleTimeout * the maximum time in millisecond an {@link org.asynchttpclient.websocket.WebSocket} can stay idle. * @return a {@link Builder} */ - public Builder setWebSocketIdleTimeoutInMs(int webSocketIdleTimeoutInMs) { - this.webSocketIdleTimeoutInMs = webSocketIdleTimeoutInMs; + public Builder setWebSocketReadTimeout(int webSocketReadTimeout) { + this.webSocketReadTimeout = webSocketReadTimeout; return this; } /** * Set the maximum time in millisecond an {@link AsyncHttpClient} can stay idle. * - * @param idleConnectionTimeoutInMs + * @param readTimeout * the maximum time in millisecond an {@link AsyncHttpClient} can stay idle. * @return a {@link Builder} */ - public Builder setIdleConnectionTimeoutInMs(int idleConnectionTimeoutInMs) { - this.idleConnectionTimeoutInMs = idleConnectionTimeoutInMs; + public Builder setReadTimeout(int readTimeout) { + this.readTimeout = readTimeout; return this; } @@ -636,24 +630,24 @@ public Builder setIdleConnectionTimeoutInMs(int idleConnectionTimeoutInMs) { * Set the maximum time in millisecond an {@link AsyncHttpClient} will keep connection * idle in pool. * - * @param idleConnectionInPoolTimeoutInMs + * @param pooledConnectionIdleTimeout * the maximum time in millisecond an {@link AsyncHttpClient} will keep connection * idle in pool. * @return a {@link Builder} */ - public Builder setIdleConnectionInPoolTimeoutInMs(int idleConnectionInPoolTimeoutInMs) { - this.idleConnectionInPoolTimeoutInMs = idleConnectionInPoolTimeoutInMs; + public Builder setPooledConnectionIdleTimeout(int pooledConnectionIdleTimeout) { + this.pooledConnectionIdleTimeout = pooledConnectionIdleTimeout; return this; } /** * Set the maximum time in millisecond an {@link AsyncHttpClient} wait for a response * - * @param requestTimeoutInMs the maximum time in millisecond an {@link AsyncHttpClient} wait for a response + * @param requestTimeout the maximum time in millisecond an {@link AsyncHttpClient} wait for a response * @return a {@link Builder} */ - public Builder setRequestTimeoutInMs(int requestTimeoutInMs) { - this.requestTimeoutInMs = requestTimeoutInMs; + public Builder setRequestTimeout(int requestTimeout) { + this.requestTimeout = requestTimeout; return this; } @@ -704,22 +698,11 @@ public Builder setUserAgent(String userAgent) { /** * 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} + * @param allowPoolingConnections true if connection can be pooled by a {@link ConnectionsPool} * @return a {@link Builder} */ - public Builder setAllowPoolingConnection(boolean allowPoolingConnection) { - this.allowPoolingConnection = allowPoolingConnection; - return this; - } - - /** - * Set the{@link ScheduledExecutorService} used to expire idle connections. - * - * @param reaper the{@link ScheduledExecutorService} used to expire idle connections. - * @return a {@link Builder} - */ - public Builder setScheduledExecutorService(ScheduledExecutorService reaper) { - this.reaper = reaper; + public Builder setAllowPoolingConnections(boolean allowPoolingConnections) { + this.allowPoolingConnections = allowPoolingConnections; return this; } @@ -758,17 +741,6 @@ public Builder setProxyServer(ProxyServer proxyServer) { return this; } - /** - * Set the {@link SSLEngineFactory} for secure connection. - * - * @param sslEngineFactory the {@link SSLEngineFactory} for secure connection - * @return a {@link Builder} - */ - public Builder setSSLEngineFactory(SSLEngineFactory sslEngineFactory) { - this.sslEngineFactory = sslEngineFactory; - return this; - } - /** * Set the {@link SSLContext} for secure connection. * @@ -776,8 +748,6 @@ public Builder setSSLEngineFactory(SSLEngineFactory sslEngineFactory) { * @return a {@link Builder} */ public Builder setSSLContext(final SSLContext sslContext) { - // reset previously set value so it will be lazily recreated - this.sslEngineFactory = null; this.sslContext = sslContext; return this; } @@ -888,11 +858,11 @@ public Builder setMaxRequestRetry(int maxRequestRetry) { /** * Return true is if connections pooling is enabled. * - * @param allowSslConnectionPool true if enabled + * @param pooledConnectionIdleTimeout true if enabled * @return this */ - public Builder setAllowSslConnectionPool(boolean allowSslConnectionPool) { - this.allowSslConnectionPool = allowSslConnectionPool; + public Builder setAllowPoolingSslConnections(boolean allowPoolingSslConnections) { + this.allowPoolingSslConnections = allowPoolingSslConnections; return this; } @@ -982,11 +952,11 @@ public Builder setStrict302Handling(final boolean strict302Handling) { /** * Set the maximum time in millisecond connection can be added to the pool for further reuse * - * @param maxConnectionLifeTimeInMs the maximum time in millisecond connection can be added to the pool for further reuse + * @param connectionTTL the maximum time in millisecond connection can be added to the pool for further reuse * @return a {@link Builder} */ - public Builder setMaxConnectionLifeTimeInMs(int maxConnectionLifeTimeInMs) { - this.maxConnectionLifeTimeInMs = maxConnectionLifeTimeInMs; + public Builder setConnectionTTL(int connectionTTL) { + this.connectionTTL = connectionTTL; return this; } @@ -1064,18 +1034,18 @@ public Builder setAcceptAnyCertificate(boolean acceptAnyCertificate) { * @param prototype the configuration to use as a prototype. */ public Builder(AsyncHttpClientConfig prototype) { - allowPoolingConnection = prototype.isAllowPoolingConnection(); + allowPoolingConnections = prototype.isAllowPoolingConnections(); providerConfig = prototype.getAsyncHttpProviderConfig(); - connectionTimeOutInMs = prototype.getConnectionTimeoutInMs(); - idleConnectionInPoolTimeoutInMs = prototype.getIdleConnectionInPoolTimeoutInMs(); - idleConnectionTimeoutInMs = prototype.getIdleConnectionTimeoutInMs(); - maxConnectionPerHost = prototype.getMaxConnectionPerHost(); - maxConnectionLifeTimeInMs = prototype.getMaxConnectionLifeTimeInMs(); + connectionTimeout = prototype.getConnectionTimeout(); + pooledConnectionIdleTimeout = prototype.getPooledConnectionIdleTimeout(); + readTimeout = prototype.getReadTimeout(); + maxConnectionsPerHost = prototype.getMaxConnectionsPerHost(); + connectionTTL = prototype.getConnectionTTL(); maxRedirects = prototype.getMaxRedirects(); - maxTotalConnections = prototype.getMaxTotalConnections(); + maxConnections = prototype.getMaxConnections(); proxyServerSelector = prototype.getProxyServerSelector(); realm = prototype.getRealm(); - requestTimeoutInMs = prototype.getRequestTimeoutInMs(); + requestTimeout = prototype.getRequestTimeout(); sslContext = prototype.getSSLContext(); userAgent = prototype.getUserAgent(); followRedirect = prototype.isFollowRedirect(); @@ -1093,13 +1063,16 @@ public Builder(AsyncHttpClientConfig prototype) { disableUrlEncodingForBoundRequests = prototype.isDisableUrlEncodingForBoundRequests(); ioThreadMultiplier = prototype.getIoThreadMultiplier(); maxRequestRetry = prototype.getMaxRequestRetry(); - allowSslConnectionPool = prototype.isAllowPoolingConnection(); + allowPoolingSslConnections = prototype.isAllowPoolingConnections(); removeQueryParamOnRedirect = prototype.isRemoveQueryParamOnRedirect(); hostnameVerifier = prototype.getHostnameVerifier(); strict302Handling = prototype.isStrict302Handling(); - useRelativeURIsWithSSLProxies = prototype.isUseRelativeURIsWithSSLProxies(); - timeConverter = prototype.getTimeConverter(); - acceptAnyCertificate = prototype.isAcceptAnyCertificate(); + timeConverter = prototype.timeConverter; + acceptAnyCertificate = prototype.acceptAnyCertificate; + + spdyEnabled = prototype.isSpdyEnabled(); + spdyInitialWindowSize = prototype.getSpdyInitialWindowSize(); + spdyMaxConcurrentStreams = prototype.getSpdyMaxConcurrentStreams(); } /** @@ -1109,16 +1082,6 @@ public Builder(AsyncHttpClientConfig prototype) { */ public AsyncHttpClientConfig build() { - if (reaper == null) { - reaper = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() { - public Thread newThread(Runnable r) { - Thread t = new Thread(r, "AsyncHttpClient-Reaper"); - t.setDaemon(true); - return t; - } - }); - } - if (proxyServerSelector == null && useProxySelector) { proxyServerSelector = ProxyUtils.getJdkDefaultProxyServerSelector(); } @@ -1131,42 +1094,40 @@ public Thread newThread(Runnable r) { proxyServerSelector = ProxyServerSelector.NO_PROXY_SELECTOR; } - return new AsyncHttpClientConfig(maxTotalConnections, // - maxConnectionPerHost, // - connectionTimeOutInMs, // - webSocketIdleTimeoutInMs, // - idleConnectionInPoolTimeoutInMs, // - idleConnectionTimeoutInMs, // - requestTimeoutInMs, // - maxConnectionLifeTimeInMs, // + return new AsyncHttpClientConfig(connectionTimeout,// + maxConnections,// + maxConnectionsPerHost,// + requestTimeout,// + readTimeout,// + webSocketReadTimeout,// + allowPoolingConnections,// + allowPoolingSslConnections,// + pooledConnectionIdleTimeout,// + connectionTTL,// + sslContext, // + hostnameVerifier,// + acceptAnyCertificate, // followRedirect, // maxRedirects, // - compressionEnabled, // - userAgent, // - allowPoolingConnection, // - reaper, // + removeQueryParamOnRedirect,// + strict302Handling, // applicationThreadPool, // proxyServerSelector, // - sslContext, // - sslEngineFactory, // - providerConfig, // - realm, // + useRelativeURIsWithSSLProxies, // + compressionEnabled, // + userAgent,// + realm,// requestFilters, // - responseFilters, // - ioExceptionFilters, // + responseFilters,// + ioExceptionFilters,// maxRequestRetry, // - allowSslConnectionPool, // disableUrlEncodingForBoundRequests, // - removeQueryParamOnRedirect, // - hostnameVerifier, // ioThreadMultiplier, // - strict302Handling, // - useRelativeURIsWithSSLProxies, // + timeConverter,// + providerConfig, // spdyEnabled, // spdyInitialWindowSize, // - spdyMaxConcurrentStreams, // - timeConverter, // - acceptAnyCertificate); + spdyMaxConcurrentStreams); } } } diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java index 254d00403d..e276b9f10a 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java @@ -45,36 +45,38 @@ void configureFilters() { } void configureDefaults() { - maxTotalConnections = defaultMaxTotalConnections(); - maxConnectionPerHost = defaultMaxConnectionPerHost(); - connectionTimeOutInMs = defaultConnectionTimeOutInMs(); - webSocketIdleTimeoutInMs = defaultWebSocketIdleTimeoutInMs(); - idleConnectionInPoolTimeoutInMs = defaultIdleConnectionInPoolTimeoutInMs(); - idleConnectionTimeoutInMs = defaultIdleConnectionTimeoutInMs(); - requestTimeoutInMs = defaultRequestTimeoutInMs(); - maxConnectionLifeTimeInMs = defaultMaxConnectionLifeTimeInMs(); + maxConnections = defaultMaxConnections(); + maxConnectionsPerHost = defaultMaxConnectionsPerHost(); + connectionTimeout = defaultConnectionTimeout(); + webSocketReadTimeout = defaultWebSocketReadTimeout(); + pooledConnectionIdleTimeout = defaultPooledConnectionIdleTimeout(); + readTimeout = defaultReadTimeout(); + requestTimeout = defaultRequestTimeout(); + connectionTTL = defaultConnectionTTL(); followRedirect = defaultFollowRedirect(); maxRedirects = defaultMaxRedirects(); compressionEnabled = defaultCompressionEnabled(); userAgent = defaultUserAgent(); - allowPoolingConnection = defaultAllowPoolingConnection(); + allowPoolingConnections = defaultAllowPoolingConnections(); useRelativeURIsWithSSLProxies = defaultUseRelativeURIsWithSSLProxies(); maxRequestRetry = defaultMaxRequestRetry(); ioThreadMultiplier = defaultIoThreadMultiplier(); - allowSslConnectionPool = defaultAllowSslConnectionPool(); + allowPoolingSslConnections = defaultAllowPoolingSslConnections(); disableUrlEncodingForBoundRequests = defaultDisableUrlEncodingForBoundRequests(); removeQueryParamOnRedirect = defaultRemoveQueryParamOnRedirect(); strict302Handling = defaultStrict302Handling(); hostnameVerifier = defaultHostnameVerifier(); - spdyEnabled = defaultSpdyEnabled(); - spdyInitialWindowSize = defaultSpdyInitialWindowSize(); - spdyMaxConcurrentStreams = defaultSpdyMaxConcurrentStreams(); acceptAnyCertificate = defaultAcceptAnyCertificate(); + if (defaultUseProxySelector()) { proxyServerSelector = ProxyUtils.getJdkDefaultProxyServerSelector(); } else if (defaultUseProxyProperties()) { proxyServerSelector = ProxyUtils.createProxyServerSelector(System.getProperties()); } + // AHC 2 + spdyEnabled = defaultSpdyEnabled(); + spdyInitialWindowSize = defaultSpdyInitialWindowSize(); + spdyMaxConcurrentStreams = defaultSpdyMaxConcurrentStreams(); } void configureExecutors() { @@ -87,38 +89,38 @@ public Thread newThread(Runnable r) { }); } - public AsyncHttpClientConfigBean setMaxTotalConnections(int maxTotalConnections) { - this.maxTotalConnections = maxTotalConnections; + public AsyncHttpClientConfigBean setMaxTotalConnections(int maxConnections) { + this.maxConnections = maxConnections; return this; } - public AsyncHttpClientConfigBean setMaxConnectionPerHost(int maxConnectionPerHost) { - this.maxConnectionPerHost = maxConnectionPerHost; + public AsyncHttpClientConfigBean setMaxConnectionsPerHost(int maxConnectionsPerHost) { + this.maxConnectionsPerHost = maxConnectionsPerHost; return this; } - public AsyncHttpClientConfigBean setConnectionTimeOutInMs(int connectionTimeOutInMs) { - this.connectionTimeOutInMs = connectionTimeOutInMs; + public AsyncHttpClientConfigBean setConnectionTimeout(int connectionTimeout) { + this.connectionTimeout = connectionTimeout; return this; } - public AsyncHttpClientConfigBean setMaxConnectionLifeTimeInMs(int maxConnectionLifeTimeInMs) { - this.maxConnectionLifeTimeInMs = maxConnectionLifeTimeInMs; + public AsyncHttpClientConfigBean setConnectionTTL(int connectionTTL) { + this.connectionTTL = connectionTTL; return this; } - public AsyncHttpClientConfigBean setIdleConnectionInPoolTimeoutInMs(int idleConnectionInPoolTimeoutInMs) { - this.idleConnectionInPoolTimeoutInMs = idleConnectionInPoolTimeoutInMs; + public AsyncHttpClientConfigBean setPooledConnectionIdleTimeout(int pooledConnectionIdleTimeout) { + this.pooledConnectionIdleTimeout = pooledConnectionIdleTimeout; return this; } - public AsyncHttpClientConfigBean setIdleConnectionTimeoutInMs(int idleConnectionTimeoutInMs) { - this.idleConnectionTimeoutInMs = idleConnectionTimeoutInMs; + public AsyncHttpClientConfigBean setReadTimeout(int readTimeout) { + this.readTimeout = readTimeout; return this; } - public AsyncHttpClientConfigBean setRequestTimeoutInMs(int requestTimeoutInMs) { - this.requestTimeoutInMs = requestTimeoutInMs; + public AsyncHttpClientConfigBean setRequestTimeout(int requestTimeout) { + this.requestTimeout = requestTimeout; return this; } @@ -147,8 +149,8 @@ public AsyncHttpClientConfigBean setUserAgent(String userAgent) { return this; } - public AsyncHttpClientConfigBean setAllowPoolingConnection(boolean allowPoolingConnection) { - this.allowPoolingConnection = allowPoolingConnection; + public AsyncHttpClientConfigBean setAllowPoolingConnections(boolean allowPoolingConnections) { + this.allowPoolingConnections = allowPoolingConnections; return this; } @@ -205,8 +207,8 @@ public AsyncHttpClientConfigBean setMaxRequestRetry(int maxRequestRetry) { return this; } - public AsyncHttpClientConfigBean setAllowSslConnectionPool(boolean allowSslConnectionPool) { - this.allowSslConnectionPool = allowSslConnectionPool; + public AsyncHttpClientConfigBean setAllowPoolingSslConnections(boolean allowPoolingSslConnections) { + this.allowPoolingSslConnections = allowPoolingSslConnections; return this; } diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java index 47af64f89b..e64f1a54b5 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java @@ -25,36 +25,36 @@ private AsyncHttpClientConfigDefaults() { public static final String ASYNC_CLIENT = AsyncHttpClientConfig.class.getName() + "."; - public static int defaultMaxTotalConnections() { - return Integer.getInteger(ASYNC_CLIENT + "maxTotalConnections", -1); + public static int defaultMaxConnections() { + return Integer.getInteger(ASYNC_CLIENT + "maxConnections", -1); } - public static int defaultMaxConnectionPerHost() { + public static int defaultMaxConnectionsPerHost() { return Integer.getInteger(ASYNC_CLIENT + "maxConnectionsPerHost", -1); } - public static int defaultConnectionTimeOutInMs() { - return Integer.getInteger(ASYNC_CLIENT + "connectionTimeoutInMs", 60 * 1000); + public static int defaultConnectionTimeout() { + return Integer.getInteger(ASYNC_CLIENT + "connectionTimeout", 60 * 1000); } - public static int defaultIdleConnectionInPoolTimeoutInMs() { - return Integer.getInteger(ASYNC_CLIENT + "idleConnectionInPoolTimeoutInMs", 60 * 1000); + public static int defaultPooledConnectionIdleTimeout() { + return Integer.getInteger(ASYNC_CLIENT + "pooledConnectionIdleTimeout", 60 * 1000); } - public static int defaultIdleConnectionTimeoutInMs() { - return Integer.getInteger(ASYNC_CLIENT + "idleConnectionTimeoutInMs", 60 * 1000); + public static int defaultReadTimeout() { + return Integer.getInteger(ASYNC_CLIENT + "readTimeout", 60 * 1000); } - public static int defaultRequestTimeoutInMs() { - return Integer.getInteger(ASYNC_CLIENT + "requestTimeoutInMs", 60 * 1000); + public static int defaultRequestTimeout() { + return Integer.getInteger(ASYNC_CLIENT + "requestTimeout", 60 * 1000); } - public static int defaultWebSocketIdleTimeoutInMs() { - return Integer.getInteger(ASYNC_CLIENT + "webSocketTimoutInMS", 15 * 60 * 1000); + public static int defaultWebSocketReadTimeout() { + return Integer.getInteger(ASYNC_CLIENT + "webSocketReadTimeout", 15 * 60 * 1000); } - public static int defaultMaxConnectionLifeTimeInMs() { - return Integer.getInteger(ASYNC_CLIENT + "maxConnectionLifeTimeInMs", -1); + public static int defaultConnectionTTL() { + return Integer.getInteger(ASYNC_CLIENT + "connectionTTL", -1); } public static boolean defaultFollowRedirect() { @@ -89,8 +89,8 @@ public static boolean defaultStrict302Handling() { return Boolean.getBoolean(ASYNC_CLIENT + "strict302Handling"); } - public static boolean defaultAllowPoolingConnection() { - return getBoolean(ASYNC_CLIENT + "allowPoolingConnection", true); + public static boolean defaultAllowPoolingConnections() { + return getBoolean(ASYNC_CLIENT + "allowPoolingConnections", true); } public static boolean defaultUseRelativeURIsWithSSLProxies() { @@ -101,8 +101,8 @@ public static int defaultMaxRequestRetry() { return Integer.getInteger(ASYNC_CLIENT + "maxRequestRetry", 5); } - public static boolean defaultAllowSslConnectionPool() { - return getBoolean(ASYNC_CLIENT + "allowSslConnectionPool", true); + public static boolean defaultAllowPoolingSslConnections() { + return getBoolean(ASYNC_CLIENT + "allowPoolingSslConnections", true); } public static boolean defaultDisableUrlEncodingForBoundRequests() { diff --git a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java index ad739b546a..25b9be4075 100644 --- a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java +++ b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java @@ -31,7 +31,6 @@ import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; /** * Simple implementation of {@link AsyncHttpClient} and it's related builders ({@link AsyncHttpClientConfig}, @@ -42,9 +41,9 @@ * {@link AsyncHandler} are required. As simple as: *

  * SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder()
- * .setIdleConnectionInPoolTimeoutInMs(100)
+ * .setIdleConnectionInPoolTimeout(100)
  * .setMaximumConnectionsTotal(50)
- * .setRequestTimeoutInMs(5 * 60 * 1000)
+ * .setRequestTimeout(5 * 60 * 1000)
  * .setUrl(getTargetUrl())
  * .setHeader("Content-Type", "text/html").build();
  * 

@@ -500,28 +499,28 @@ public Builder setFollowRedirect(boolean followRedirect) { return this; } - public Builder setMaxConnectionsTotal(int defaultMaxTotalConnections) { - configBuilder.setMaxConnectionsTotal(defaultMaxTotalConnections); + public Builder setMaxConnections(int defaultMaxConnections) { + configBuilder.setMaxConnections(defaultMaxConnections); return this; } - public Builder setMaxConnectionsPerHost(int defaultMaxConnectionPerHost) { - configBuilder.setMaxConnectionsPerHost(defaultMaxConnectionPerHost); + public Builder setMaxConnectionsPerHost(int defaultMaxConnectionsPerHost) { + configBuilder.setMaxConnectionsPerHost(defaultMaxConnectionsPerHost); return this; } - public Builder setConnectionTimeoutInMs(int connectionTimeuot) { - configBuilder.setConnectionTimeoutInMs(connectionTimeuot); + public Builder setConnectionTimeout(int connectionTimeuot) { + configBuilder.setConnectionTimeout(connectionTimeuot); return this; } - public Builder setIdleConnectionInPoolTimeoutInMs(int defaultIdleConnectionInPoolTimeoutInMs) { - configBuilder.setIdleConnectionInPoolTimeoutInMs(defaultIdleConnectionInPoolTimeoutInMs); + public Builder setPooledConnectionIdleTimeout(int pooledConnectionIdleTimeout) { + configBuilder.setPooledConnectionIdleTimeout(pooledConnectionIdleTimeout); return this; } - public Builder setRequestTimeoutInMs(int defaultRequestTimeoutInMs) { - configBuilder.setRequestTimeoutInMs(defaultRequestTimeoutInMs); + public Builder setRequestTimeout(int defaultRequestTimeout) { + configBuilder.setRequestTimeout(defaultRequestTimeout); return this; } @@ -540,13 +539,8 @@ public Builder setUserAgent(String userAgent) { return this; } - public Builder setAllowPoolingConnection(boolean allowPoolingConnection) { - configBuilder.setAllowPoolingConnection(allowPoolingConnection); - return this; - } - - public Builder setScheduledExecutorService(ScheduledExecutorService reaper) { - configBuilder.setScheduledExecutorService(reaper); + public Builder setAllowPoolingConnections(boolean allowPoolingConnections) { + configBuilder.setAllowPoolingConnections(allowPoolingConnections); return this; } @@ -555,11 +549,6 @@ public Builder setExecutorService(ExecutorService applicationThreadPool) { return this; } - public Builder setSSLEngineFactory(SSLEngineFactory sslEngineFactory) { - configBuilder.setSSLEngineFactory(sslEngineFactory); - return this; - } - public Builder setSSLContext(final SSLContext sslContext) { configBuilder.setSSLContext(sslContext); return this; diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java index 49ed3a4f45..d0066c2adf 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java @@ -138,11 +138,11 @@ public static String parseCharset(String contentType) { } public static String keepAliveHeaderValue(AsyncHttpClientConfig config) { - return config.isAllowPoolingConnection() ? "keep-alive" : "close"; + return config.isAllowPoolingConnections() ? "keep-alive" : "close"; } public static int requestTimeout(AsyncHttpClientConfig config, Request request) { - return request.getRequestTimeoutInMs() != 0 ? request.getRequestTimeoutInMs() : config.getRequestTimeoutInMs(); + return request.getRequestTimeoutInMs() != 0 ? request.getRequestTimeoutInMs() : config.getRequestTimeout(); } public static boolean followRedirect(AsyncHttpClientConfig config, Request request) { diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java index d8f8936d33..301074edc6 100755 --- a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java @@ -357,7 +357,7 @@ public Response onCompleted(Response response) throws Exception { // TODO: fix test @Test(groups = { "standalone", "default_provider", "async" }, enabled = false) public void asyncStatusHEADContentLenghtTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(120 * 1000).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(120 * 1000).build()); try { final CountDownLatch l = new CountDownLatch(1); Request request = new RequestBuilder("HEAD").setUrl(getTargetUrl()).build(); @@ -1226,7 +1226,7 @@ public void onThrowable(Throwable t) { @Test(groups = { "standalone", "default_provider", "async" }) public void asyncDoGetDelayHandlerTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(5 * 1000).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(5 * 1000).build()); try { FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); h.add("LockThread", "true"); @@ -1486,7 +1486,7 @@ public void testAsyncHttpProviderConfig() throws Exception { @Test(groups = { "standalone", "default_provider" }) public void idleRequestTimeoutTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setIdleConnectionInPoolTimeoutInMs(5000).setRequestTimeoutInMs(10000).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setPooledConnectionIdleTimeout(5000).setRequestTimeout(10000).build()); try { FluentCaseInsensitiveStringsMap h = new FluentCaseInsensitiveStringsMap(); h.add("Content-Type", "application/x-www-form-urlencoded"); diff --git a/api/src/test/java/org/asynchttpclient/async/AuthTimeoutTest.java b/api/src/test/java/org/asynchttpclient/async/AuthTimeoutTest.java index cb532d74a9..13d54d417e 100644 --- a/api/src/test/java/org/asynchttpclient/async/AuthTimeoutTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AuthTimeoutTest.java @@ -211,7 +211,7 @@ protected void inspectException(Throwable t) { } private AsyncHttpClient newClient() { - return getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setIdleConnectionInPoolTimeoutInMs(2000).setConnectionTimeoutInMs(20000).setRequestTimeoutInMs(2000).build()); + return getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setPooledConnectionIdleTimeout(2000).setConnectionTimeout(20000).setRequestTimeout(2000).build()); } protected Future execute(AsyncHttpClient client, Server server, boolean preemptive) throws IOException { diff --git a/api/src/test/java/org/asynchttpclient/async/BasicAuthTest.java b/api/src/test/java/org/asynchttpclient/async/BasicAuthTest.java index 2c0c3c553c..958361c6b2 100644 --- a/api/src/test/java/org/asynchttpclient/async/BasicAuthTest.java +++ b/api/src/test/java/org/asynchttpclient/async/BasicAuthTest.java @@ -350,7 +350,7 @@ public void basicAuthAsyncConfigTest() throws Exception { @Test(groups = { "standalone", "default_provider" }) public void basicAuthFileNoKeepAliveTest() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAllowPoolingConnection(false).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(false).build()); try { Future f = client.preparePost(getTargetUrl())// diff --git a/api/src/test/java/org/asynchttpclient/async/BasicHttpsTest.java b/api/src/test/java/org/asynchttpclient/async/BasicHttpsTest.java index 513786fc91..9f13a7b51b 100644 --- a/api/src/test/java/org/asynchttpclient/async/BasicHttpsTest.java +++ b/api/src/test/java/org/asynchttpclient/async/BasicHttpsTest.java @@ -81,7 +81,7 @@ public void multipleSSLRequestsTest() throws Exception { @Test(groups = { "standalone", "default_provider" }) public void multipleSSLWithoutCacheTest() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new Builder().setSSLContext(createSSLContext(new AtomicBoolean(true))).setAllowSslConnectionPool(false).build()); + AsyncHttpClient c = getAsyncHttpClient(new Builder().setSSLContext(createSSLContext(new AtomicBoolean(true))).setAllowPoolingSslConnections(false).build()); try { String body = "hello there"; c.preparePost(getTargetUrl()).setBody(body).setHeader("Content-Type", "text/html").execute(); @@ -139,7 +139,7 @@ public void failInstantlyIfHostNamesDiffer() throws Exception { public boolean verify(String arg0, SSLSession arg1) { return false; } - }).setRequestTimeoutInMs(20000); + }).setRequestTimeout(20000); client = getAsyncHttpClient(builder.build()); diff --git a/api/src/test/java/org/asynchttpclient/async/BodyChunkTest.java b/api/src/test/java/org/asynchttpclient/async/BodyChunkTest.java index 4dbc04bfb9..727e0d67a0 100644 --- a/api/src/test/java/org/asynchttpclient/async/BodyChunkTest.java +++ b/api/src/test/java/org/asynchttpclient/async/BodyChunkTest.java @@ -35,9 +35,9 @@ public abstract class BodyChunkTest extends AbstractBasicTest { public void negativeContentTypeTest() throws Exception { AsyncHttpClientConfig.Builder confbuilder = new AsyncHttpClientConfig.Builder(); - confbuilder = confbuilder.setConnectionTimeoutInMs(100); - confbuilder = confbuilder.setMaxConnectionsTotal(50); - confbuilder = confbuilder.setRequestTimeoutInMs(5 * 60 * 1000); // 5 minutes + confbuilder = confbuilder.setConnectionTimeout(100); + confbuilder = confbuilder.setMaxConnections(50); + confbuilder = confbuilder.setRequestTimeout(5 * 60 * 1000); // 5 minutes // Create client AsyncHttpClient client = getAsyncHttpClient(confbuilder.build()); diff --git a/api/src/test/java/org/asynchttpclient/async/BodyDeferringAsyncHandlerTest.java b/api/src/test/java/org/asynchttpclient/async/BodyDeferringAsyncHandlerTest.java index 2b7c26ee16..baf91d11e2 100644 --- a/api/src/test/java/org/asynchttpclient/async/BodyDeferringAsyncHandlerTest.java +++ b/api/src/test/java/org/asynchttpclient/async/BodyDeferringAsyncHandlerTest.java @@ -109,7 +109,7 @@ public AbstractHandler configureHandler() throws Exception { public AsyncHttpClientConfig getAsyncHttpClientConfig() { // for this test brevity's sake, we are limiting to 1 retries - return new AsyncHttpClientConfig.Builder().setMaxRequestRetry(0).setRequestTimeoutInMs(10000).build(); + return new AsyncHttpClientConfig.Builder().setMaxRequestRetry(0).setRequestTimeout(10000).build(); } @Test(groups = { "standalone", "default_provider" }) diff --git a/api/src/test/java/org/asynchttpclient/async/ChunkingTest.java b/api/src/test/java/org/asynchttpclient/async/ChunkingTest.java index 8d8845faa7..0566ecdbaa 100644 --- a/api/src/test/java/org/asynchttpclient/async/ChunkingTest.java +++ b/api/src/test/java/org/asynchttpclient/async/ChunkingTest.java @@ -45,11 +45,11 @@ abstract public class ChunkingTest extends AbstractBasicTest { public void testCustomChunking() throws Exception { AsyncHttpClientConfig.Builder bc = new AsyncHttpClientConfig.Builder(); - bc.setAllowPoolingConnection(true); + bc.setAllowPoolingConnections(true); bc.setMaxConnectionsPerHost(1); - bc.setMaxConnectionsTotal(1); - bc.setConnectionTimeoutInMs(1000); - bc.setRequestTimeoutInMs(1000); + bc.setMaxConnections(1); + bc.setConnectionTimeout(1000); + bc.setRequestTimeout(1000); bc.setFollowRedirect(true); AsyncHttpClient c = getAsyncHttpClient(bc.build()); diff --git a/api/src/test/java/org/asynchttpclient/async/ConnectionPoolTest.java b/api/src/test/java/org/asynchttpclient/async/ConnectionPoolTest.java index be3dc83020..86ea33b6dc 100644 --- a/api/src/test/java/org/asynchttpclient/async/ConnectionPoolTest.java +++ b/api/src/test/java/org/asynchttpclient/async/ConnectionPoolTest.java @@ -42,7 +42,7 @@ public abstract class ConnectionPoolTest extends AbstractBasicTest { @Test(groups = { "standalone", "default_provider" }) public void testMaxTotalConnections() { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAllowPoolingConnection(true).setMaxConnectionsTotal(1).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(true).setMaxConnections(1).build()); try { String url = getTargetUrl(); int i; @@ -64,7 +64,7 @@ public void testMaxTotalConnections() { @Test(groups = { "standalone", "default_provider" }) public void testMaxTotalConnectionsException() { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAllowPoolingConnection(true).setMaxConnectionsTotal(1).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(true).setMaxConnections(1).build()); try { String url = getTargetUrl(); int i; @@ -132,7 +132,7 @@ public Response onCompleted(Response response) throws Exception { @Test(groups = { "standalone", "default_provider" }) public void multipleMaxConnectionOpenTest() throws Exception { - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setAllowPoolingConnection(true).setConnectionTimeoutInMs(5000).setMaxConnectionsTotal(1).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(true).setConnectionTimeout(5000).setMaxConnections(1).build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { String body = "hello there"; @@ -160,7 +160,7 @@ public void multipleMaxConnectionOpenTest() throws Exception { @Test(groups = { "standalone", "default_provider" }) public void multipleMaxConnectionOpenTestWithQuery() throws Exception { - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setAllowPoolingConnection(true).setConnectionTimeoutInMs(5000).setMaxConnectionsTotal(1).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(true).setConnectionTimeout(5000).setMaxConnections(1).build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { String body = "hello there"; diff --git a/api/src/test/java/org/asynchttpclient/async/FilePartLargeFileTest.java b/api/src/test/java/org/asynchttpclient/async/FilePartLargeFileTest.java index 470eeaaf8e..9783fc4fcf 100644 --- a/api/src/test/java/org/asynchttpclient/async/FilePartLargeFileTest.java +++ b/api/src/test/java/org/asynchttpclient/async/FilePartLargeFileTest.java @@ -62,7 +62,7 @@ public void handle(String target, Request baseRequest, HttpServletRequest req, H @Test(groups = { "standalone", "default_provider" }, enabled = true) public void testPutImageFile() throws Exception { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(100 * 6000).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(100 * 6000).build()); try { Response response = client.preparePut(getTargetUrl()).addBodyPart(new FilePart("test", LARGE_IMAGE_FILE, "application/octet-stream", StandardCharsets.UTF_8.name())).execute().get(); assertEquals(response.getStatusCode(), 200); diff --git a/api/src/test/java/org/asynchttpclient/async/IdleStateHandlerTest.java b/api/src/test/java/org/asynchttpclient/async/IdleStateHandlerTest.java index 929ec24856..cefa7083aa 100644 --- a/api/src/test/java/org/asynchttpclient/async/IdleStateHandlerTest.java +++ b/api/src/test/java/org/asynchttpclient/async/IdleStateHandlerTest.java @@ -61,7 +61,7 @@ public void setUpGlobal() throws Exception { @Test(groups = { "online", "default_provider" }) public void idleStateTest() throws Exception { - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setIdleConnectionInPoolTimeoutInMs(10 * 1000).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setPooledConnectionIdleTimeout(10 * 1000).build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { diff --git a/api/src/test/java/org/asynchttpclient/async/MaxConnectionsInThreads.java b/api/src/test/java/org/asynchttpclient/async/MaxConnectionsInThreads.java index 31b6ddc359..4f626fd3f9 100644 --- a/api/src/test/java/org/asynchttpclient/async/MaxConnectionsInThreads.java +++ b/api/src/test/java/org/asynchttpclient/async/MaxConnectionsInThreads.java @@ -51,8 +51,8 @@ public void testMaxConnectionsWithinThreads() { String[] urls = new String[] { servletEndpointUri.toString(), servletEndpointUri.toString() }; - final AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeoutInMs(1000).setRequestTimeoutInMs(5000) - .setAllowPoolingConnection(true).setMaxConnectionsTotal(1).setMaxConnectionsPerHost(1).build()); + final AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeout(1000).setRequestTimeout(5000) + .setAllowPoolingConnections(true).setMaxConnections(1).setMaxConnectionsPerHost(1).build()); try { final Boolean[] caughtError = new Boolean[] { Boolean.FALSE }; diff --git a/api/src/test/java/org/asynchttpclient/async/MaxTotalConnectionTest.java b/api/src/test/java/org/asynchttpclient/async/MaxTotalConnectionTest.java index 245abb9de9..d12b98c613 100644 --- a/api/src/test/java/org/asynchttpclient/async/MaxTotalConnectionTest.java +++ b/api/src/test/java/org/asynchttpclient/async/MaxTotalConnectionTest.java @@ -39,7 +39,7 @@ public abstract class MaxTotalConnectionTest extends AbstractBasicTest { public void testMaxTotalConnectionsExceedingException() { String[] urls = new String[] { "http://google.com/", "http://github.com/" }; - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeoutInMs(1000).setRequestTimeoutInMs(5000).setAllowPoolingConnection(false).setMaxConnectionsTotal(1).setMaxConnectionsPerHost(1).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeout(1000).setRequestTimeout(5000).setAllowPoolingConnections(false).setMaxConnections(1).setMaxConnectionsPerHost(1).build()); try { boolean caughtError = false; for (int i = 0; i < urls.length; i++) { @@ -61,7 +61,7 @@ public void testMaxTotalConnectionsExceedingException() { public void testMaxTotalConnections() { String[] urls = new String[] { "http://google.com/", "http://lenta.ru/" }; - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeoutInMs(1000).setRequestTimeoutInMs(5000).setAllowPoolingConnection(false).setMaxConnectionsTotal(2).setMaxConnectionsPerHost(1).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeout(1000).setRequestTimeout(5000).setAllowPoolingConnections(false).setMaxConnections(2).setMaxConnectionsPerHost(1).build()); try { for (String url : urls) { try { @@ -82,7 +82,7 @@ public void testMaxTotalConnections() { public void testMaxTotalConnectionsCorrectExceptionHandling() { String[] urls = new String[] { "http://google.com/", "http://github.com/" }; - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeoutInMs(1000).setRequestTimeoutInMs(5000).setAllowPoolingConnection(false).setMaxConnectionsTotal(1).setMaxConnectionsPerHost(1).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeout(1000).setRequestTimeout(5000).setAllowPoolingConnections(false).setMaxConnections(1).setMaxConnectionsPerHost(1).build()); try { List> futures = new ArrayList>(); boolean caughtError = false; diff --git a/api/src/test/java/org/asynchttpclient/async/NoNullResponseTest.java b/api/src/test/java/org/asynchttpclient/async/NoNullResponseTest.java index 0e27a0fb86..8ce64afc51 100644 --- a/api/src/test/java/org/asynchttpclient/async/NoNullResponseTest.java +++ b/api/src/test/java/org/asynchttpclient/async/NoNullResponseTest.java @@ -56,8 +56,8 @@ public void multipleSslRequestsWithDelayAndKeepAlive() throws Exception { } private AsyncHttpClient create() throws GeneralSecurityException { - final AsyncHttpClientConfig.Builder configBuilder = new AsyncHttpClientConfig.Builder().setCompressionEnabled(true).setFollowRedirect(true).setSSLContext(getSSLContext()).setAllowPoolingConnection(true).setConnectionTimeoutInMs(10000) - .setIdleConnectionInPoolTimeoutInMs(60000).setRequestTimeoutInMs(10000).setMaxConnectionsPerHost(-1).setMaxConnectionsTotal(-1); + final AsyncHttpClientConfig.Builder configBuilder = new AsyncHttpClientConfig.Builder().setCompressionEnabled(true).setFollowRedirect(true).setSSLContext(getSSLContext()).setAllowPoolingConnections(true).setConnectionTimeout(10000) + .setPooledConnectionIdleTimeout(60000).setRequestTimeout(10000).setMaxConnectionsPerHost(-1).setMaxConnections(-1); return getAsyncHttpClient(configBuilder.build()); } diff --git a/api/src/test/java/org/asynchttpclient/async/PerRequestTimeoutTest.java b/api/src/test/java/org/asynchttpclient/async/PerRequestTimeoutTest.java index f6cc5764ec..9063a8f87e 100644 --- a/api/src/test/java/org/asynchttpclient/async/PerRequestTimeoutTest.java +++ b/api/src/test/java/org/asynchttpclient/async/PerRequestTimeoutTest.java @@ -115,7 +115,7 @@ public void testRequestTimeout() throws IOException { @Test(groups = { "standalone", "default_provider" }) public void testGlobalDefaultPerRequestInfiniteTimeout() throws IOException { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(100).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(100).build()); try { Future responseFuture = client.prepareGet(getTargetUrl()).setRequestTimeoutInMs(-1).execute(); Response response = responseFuture.get(); @@ -132,7 +132,7 @@ public void testGlobalDefaultPerRequestInfiniteTimeout() throws IOException { @Test(groups = { "standalone", "default_provider" }) public void testGlobalRequestTimeout() throws IOException { - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(100).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(100).build()); try { Future responseFuture = client.prepareGet(getTargetUrl()).execute(); Response response = responseFuture.get(2000, TimeUnit.MILLISECONDS); @@ -153,7 +153,7 @@ public void testGlobalRequestTimeout() throws IOException { public void testGlobalIdleTimeout() throws IOException { final long times[] = new long[] { -1, -1 }; - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setIdleConnectionInPoolTimeoutInMs(2000).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setPooledConnectionIdleTimeout(2000).build()); try { Future responseFuture = client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandler() { @Override diff --git a/api/src/test/java/org/asynchttpclient/async/PutLargeFileTest.java b/api/src/test/java/org/asynchttpclient/async/PutLargeFileTest.java index 0d37f66e5c..51400e3040 100644 --- a/api/src/test/java/org/asynchttpclient/async/PutLargeFileTest.java +++ b/api/src/test/java/org/asynchttpclient/async/PutLargeFileTest.java @@ -41,7 +41,7 @@ public void testPutLargeFile() throws Exception { int timeout = (int) file.length() / 1000; - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeoutInMs(timeout).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeout(timeout).build()); try { Response response = client.preparePut(getTargetUrl()).setBody(file).execute().get(); assertEquals(response.getStatusCode(), 200); diff --git a/api/src/test/java/org/asynchttpclient/async/RC10KTest.java b/api/src/test/java/org/asynchttpclient/async/RC10KTest.java index a52ccd4157..e3a1993058 100644 --- a/api/src/test/java/org/asynchttpclient/async/RC10KTest.java +++ b/api/src/test/java/org/asynchttpclient/async/RC10KTest.java @@ -99,7 +99,7 @@ public void handle(String s, Request r, HttpServletRequest req, HttpServletRespo @Test(timeOut = 10 * 60 * 1000, groups = "scalability") public void rc10kProblem() throws IOException, ExecutionException, TimeoutException, InterruptedException { - AsyncHttpClient ahc = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setMaxConnectionsPerHost(C10K).setAllowPoolingConnection(true).build()); + AsyncHttpClient ahc = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setMaxConnectionsPerHost(C10K).setAllowPoolingConnections(true).build()); try { List> resps = new ArrayList>(C10K); int i = 0; diff --git a/api/src/test/java/org/asynchttpclient/async/RedirectConnectionUsageTest.java b/api/src/test/java/org/asynchttpclient/async/RedirectConnectionUsageTest.java index 8241e49f4d..26bfbfc2f9 100644 --- a/api/src/test/java/org/asynchttpclient/async/RedirectConnectionUsageTest.java +++ b/api/src/test/java/org/asynchttpclient/async/RedirectConnectionUsageTest.java @@ -74,11 +74,11 @@ public void setUp() throws Exception { public void testGetRedirectFinalUrl() throws Exception { AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// - .setAllowPoolingConnection(true)// + .setAllowPoolingConnections(true)// .setMaxConnectionsPerHost(1)// - .setMaxConnectionsTotal(1)// - .setConnectionTimeoutInMs(1000)// - .setRequestTimeoutInMs(1000)// + .setMaxConnections(1)// + .setConnectionTimeout(1000)// + .setRequestTimeout(1000)// .setFollowRedirect(true)// .build(); diff --git a/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java b/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java index 36a735b610..f5b46b21fb 100644 --- a/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java +++ b/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java @@ -53,7 +53,7 @@ public abstract class RemoteSiteTest extends AbstractBasicTest { @Test(groups = { "online", "default_provider" }) public void testGoogleCom() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(10000).build()); + AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build()); try { Response response = c.prepareGet("http://www.google.com/").execute().get(10, TimeUnit.SECONDS); assertNotNull(response); @@ -64,7 +64,7 @@ public void testGoogleCom() throws Exception { @Test(groups = { "online", "default_provider" }) public void testMailGoogleCom() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(10000).build()); + AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build()); try { Response response = c.prepareGet("http://mail.google.com/").execute().get(10, TimeUnit.SECONDS); assertNotNull(response); @@ -77,7 +77,7 @@ public void testMailGoogleCom() throws Exception { @Test(groups = { "online", "default_provider" }, enabled = false) // FIXME public void testMicrosoftCom() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(10000).build()); + AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build()); try { Response response = c.prepareGet("http://microsoft.com/").execute().get(10, TimeUnit.SECONDS); assertNotNull(response); @@ -90,7 +90,7 @@ public void testMicrosoftCom() throws Exception { @Test(groups = { "online", "default_provider" }, enabled = false) // FIXME public void testWwwMicrosoftCom() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(10000).build()); + AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build()); try { Response response = c.prepareGet("http://www.microsoft.com/").execute().get(10, TimeUnit.SECONDS); assertNotNull(response); @@ -103,7 +103,7 @@ public void testWwwMicrosoftCom() throws Exception { @Test(groups = { "online", "default_provider" }, enabled = false) // FIXME public void testUpdateMicrosoftCom() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(10000).build()); + AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build()); try { Response response = c.prepareGet("http://update.microsoft.com/").execute().get(10, TimeUnit.SECONDS); assertNotNull(response); @@ -115,7 +115,7 @@ public void testUpdateMicrosoftCom() throws Exception { @Test(groups = { "online", "default_provider" }) public void testGoogleComWithTimeout() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(10000).build()); + AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).build()); try { Response response = c.prepareGet("http://google.com/").execute().get(10, TimeUnit.SECONDS); assertNotNull(response); @@ -154,8 +154,8 @@ public Response onCompleted(Response response) throws Exception { @Test(groups = { "online", "default_provider" }, enabled = false) public void invalidStreamTest2() throws Exception { - AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(10000).setFollowRedirect(true) - .setAllowPoolingConnection(false).setMaxRedirects(6).build(); + AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().setRequestTimeout(10000).setFollowRedirect(true) + .setAllowPoolingConnections(false).setMaxRedirects(6).build(); AsyncHttpClient c = getAsyncHttpClient(config); try { diff --git a/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java b/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java index c4aa459a2b..a7f4615795 100644 --- a/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java +++ b/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java @@ -49,8 +49,8 @@ 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) - .setMaxConnectionsTotal(50).setRequestTimeoutInMs(5 * 60 * 1000).setUrl(getTargetUrl()).setHeader("Content-Type", "text/html").build(); + SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setPooledConnectionIdleTimeout(100) + .setMaxConnections(50).setRequestTimeout(5 * 60 * 1000).setUrl(getTargetUrl()).setHeader("Content-Type", "text/html").build(); try { Future future = client.post(new InputStreamBodyGenerator(new ByteArrayInputStream(MY_MESSAGE.getBytes()))); @@ -65,8 +65,8 @@ public void inpuStreamBodyConsumerTest() throws Exception { @Test(groups = { "standalone", "default_provider" }) public void stringBuilderBodyConsumerTest() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setIdleConnectionInPoolTimeoutInMs(100) - .setMaxConnectionsTotal(50).setRequestTimeoutInMs(5 * 60 * 1000).setUrl(getTargetUrl()).setHeader("Content-Type", "text/html").build(); + SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setPooledConnectionIdleTimeout(100) + .setMaxConnections(50).setRequestTimeout(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)); @@ -82,8 +82,8 @@ public void stringBuilderBodyConsumerTest() throws Exception { @Test(groups = { "standalone", "default_provider" }) public void byteArrayOutputStreamBodyConsumerTest() throws Exception { - SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setIdleConnectionInPoolTimeoutInMs(100) - .setMaxConnectionsTotal(50).setRequestTimeoutInMs(5 * 60 * 1000).setUrl(getTargetUrl()).setHeader("Content-Type", "text/html").build(); + SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setPooledConnectionIdleTimeout(100) + .setMaxConnections(50).setRequestTimeout(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)); @@ -117,8 +117,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) - .setMaxConnectionsTotal(50).setRequestTimeoutInMs(5 * 1000).setUrl(getTargetUrl() + "/testPutZeroBytesFileTest.txt").setHeader("Content-Type", "text/plain") + SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setPooledConnectionIdleTimeout(100) + .setMaxConnections(50).setRequestTimeout(5 * 1000).setUrl(getTargetUrl() + "/testPutZeroBytesFileTest.txt").setHeader("Content-Type", "text/plain") .build(); try { File tmpfile = File.createTempFile("testPutZeroBytesFile", ".tmp"); diff --git a/api/src/test/java/org/asynchttpclient/async/TransferListenerTest.java b/api/src/test/java/org/asynchttpclient/async/TransferListenerTest.java index 3b1763e768..90ffb19938 100644 --- a/api/src/test/java/org/asynchttpclient/async/TransferListenerTest.java +++ b/api/src/test/java/org/asynchttpclient/async/TransferListenerTest.java @@ -147,7 +147,7 @@ public void basicPutFileTest() throws Exception { File file = createTempFile(1024 * 100 * 10); int timeout = (int) (file.length() / 1000); - AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeoutInMs(timeout).build()); + AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setConnectionTimeout(timeout).build()); try { TransferCompletionHandler tl = new TransferCompletionHandler(); diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java index b83618df63..f887b9624e 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/ConnectionManager.java @@ -75,11 +75,11 @@ public class ConnectionManager { this.connectionPool = connectionPool; canDestroyPool = false; } else { - this.connectionPool = new ConnectionPool(config.getMaxConnectionPerHost(),// - config.getMaxTotalConnections(),// + this.connectionPool = new ConnectionPool(config.getMaxConnectionsPerHost(),// + config.getMaxConnections(),// null,// - config.getConnectionTimeoutInMs(),// - config.getIdleConnectionInPoolTimeoutInMs(),// + config.getConnectionTimeout(),// + config.getPooledConnectionIdleTimeout(),// 2000); canDestroyPool = true; } @@ -211,7 +211,7 @@ private static int getPort(final UriComponents uri, final int p) { private Connection obtainConnection0(final Request request, final GrizzlyResponseFuture requestFuture) throws ExecutionException, InterruptedException, TimeoutException, IOException { - final int cTimeout = provider.getClientConfig().getConnectionTimeoutInMs(); + final int cTimeout = provider.getClientConfig().getConnectionTimeout(); final FutureImpl future = Futures.createSafeFuture(); final CompletionHandler ch = Futures.toCompletionHandler(future, createConnectionCompletionHandler(request, requestFuture, null)); 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 dd8d457fd6..620606a399 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 @@ -277,7 +277,7 @@ public void onHttpHeadersParsed(HttpHeader httpHeader, FilterChainContext ctx) { ws.onConnect(); WebSocketHolder.set(ctx.getConnection(), context.getProtocolHandler(), ws); ((WebSocketUpgradeHandler) context.getHandler()).onSuccess(context.getWebSocket()); - final int wsTimeout = context.getProvider().getClientConfig().getWebSocketIdleTimeoutInMs(); + final int wsTimeout = context.getProvider().getClientConfig().getWebSocketReadTimeout(); IdleTimeoutFilter.setCustomTimeout(ctx.getConnection(), ((wsTimeout <= 0) ? IdleTimeoutFilter.FOREVER : wsTimeout), TimeUnit.MILLISECONDS); context.result(handler.onCompleted()); 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 fa6a759e53..3bc8ed5f80 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 @@ -214,7 +214,7 @@ void initializeTransport(final AsyncHttpClientConfig clientConfig) { final FilterChainBuilder secure = FilterChainBuilder.stateless(); secure.add(new TransportFilter()); - final int timeout = clientConfig.getRequestTimeoutInMs(); + final int timeout = clientConfig.getRequestTimeout(); if (timeout > 0) { int delay = 500; //noinspection ConstantConditions @@ -232,7 +232,7 @@ public long getTimeout(FilterChainContext ctx) { final HttpTxContext context = HttpTxContext.get(ctx); if (context != null) { if (context.isWSRequest()) { - return clientConfig.getWebSocketIdleTimeoutInMs(); + return clientConfig.getWebSocketReadTimeout(); } int requestTimeout = AsyncHttpProviderUtils.requestTimeout(clientConfig, context.getRequest()); if (requestTimeout > 0) { diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyConnectionPoolTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyConnectionPoolTest.java index acf6c92a3a..be0aed7b7f 100644 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyConnectionPoolTest.java +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyConnectionPoolTest.java @@ -40,8 +40,8 @@ public void testMaxTotalConnectionsException() { @Override @Test public void multipleMaxConnectionOpenTest() throws Exception { - AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setAllowPoolingConnection(true).setConnectionTimeoutInMs(5000) - .setMaxConnectionsTotal(1).build(); + AsyncHttpClientConfig cg = new AsyncHttpClientConfig.Builder().setAllowPoolingConnections(true).setConnectionTimeout(5000) + .setMaxConnections(1).build(); AsyncHttpClient c = getAsyncHttpClient(cg); try { String body = "hello there"; diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java index 1aac4fcfce..2beca1a78f 100644 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyFeedableBodyGeneratorTest.java @@ -135,7 +135,7 @@ private void doSimpleFeeder(final boolean secure) { AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder() .setMaxConnectionsPerHost(60) - .setMaxConnectionsTotal(60) + .setMaxConnections(60) .setAcceptAnyCertificate(true) .build(); final AsyncHttpClient client = @@ -238,7 +238,7 @@ private void doNonBlockingFeeder(final boolean secure) { AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder() .setMaxConnectionsPerHost(60) - .setMaxConnectionsTotal(60) + .setMaxConnections(60) .setAcceptAnyCertificate(true) .build(); final AsyncHttpClient client = diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java index ea02d5fc57..116a186459 100644 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyNoTransferEncodingTest.java @@ -87,9 +87,9 @@ public void testNoTransferEncoding() throws Exception { AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder() .setCompressionEnabled(true) .setFollowRedirect(false) - .setConnectionTimeoutInMs(15000) - .setRequestTimeoutInMs(15000) - .setAllowPoolingConnection(false) + .setConnectionTimeout(15000) + .setRequestTimeout(15000) + .setAllowPoolingConnections(false) .setDisableUrlEncodingForBoundRequests(true) .setIOThreadMultiplier(2) // 2 is default .build(); diff --git a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyUnexpectingTimeoutTest.java b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyUnexpectingTimeoutTest.java index 743255d00b..3829983aae 100644 --- a/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyUnexpectingTimeoutTest.java +++ b/providers/grizzly/src/test/java/org/asynchttpclient/providers/grizzly/GrizzlyUnexpectingTimeoutTest.java @@ -82,7 +82,7 @@ public void unexpectingTimeoutTest() throws IOException { final AtomicInteger counts = new AtomicInteger(); final int timeout = 100; - final AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(timeout).build()); + final AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(timeout).build()); try { Future responseFuture = client.prepareGet(getTargetUrl()).execute(new AsyncCompletionHandler() { @Override diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java index 637a619c6f..dd36702b85 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java @@ -31,7 +31,7 @@ public class ChannelManager { private static final Logger LOGGER = LoggerFactory.getLogger(ChannelManager.class); private final ChannelPool channelPool; - private final boolean maxTotalConnectionsEnabled; + private final boolean maxConnectionsEnabled; private final Semaphore freeChannels; private final ChannelGroup openChannels; private final int maxConnectionsPerHost; @@ -42,9 +42,9 @@ public class ChannelManager { public ChannelManager(AsyncHttpClientConfig config, ChannelPool channelPool) { this.channelPool = channelPool; - maxTotalConnectionsEnabled = config.getMaxTotalConnections() > 0; + maxConnectionsEnabled = config.getMaxConnections() > 0; - if (maxTotalConnectionsEnabled) { + if (maxConnectionsEnabled) { openChannels = new CleanupChannelGroup("asyncHttpClient") { @Override public boolean remove(Object o) { @@ -63,14 +63,14 @@ public boolean remove(Object o) { return removed; } }; - freeChannels = new Semaphore(config.getMaxTotalConnections()); + freeChannels = new Semaphore(config.getMaxConnections()); } else { openChannels = new CleanupChannelGroup("asyncHttpClient"); freeChannels = null; } - maxConnectionsPerHost = config.getMaxConnectionPerHost(); - maxConnectionsPerHostEnabled = config.getMaxConnectionPerHost() > 0; + maxConnectionsPerHost = config.getMaxConnectionsPerHost(); + maxConnectionsPerHostEnabled = config.getMaxConnectionsPerHost() > 0; if (maxConnectionsPerHostEnabled) { freeChannelsPerHost = new ConcurrentHashMap(); @@ -103,7 +103,7 @@ public boolean removeAll(Channel connection) { } private boolean tryAcquireGlobal() { - return !maxTotalConnectionsEnabled || freeChannels.tryAcquire(); + return !maxConnectionsEnabled || freeChannels.tryAcquire(); } private Semaphore getFreeConnectionsForHost(String poolKey) { @@ -156,7 +156,7 @@ public void closeChannel(Channel channel) { } public void abortChannelPreemption(String poolKey) { - if (maxTotalConnectionsEnabled) + if (maxConnectionsEnabled) freeChannels.release(); if (maxConnectionsPerHostEnabled) getFreeConnectionsForHost(poolKey).release(); 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 54376e29b1..304d2d7931 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 @@ -126,7 +126,7 @@ public Channels(final AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig ChannelPool cp = nettyProviderConfig.getChannelPool(); if (cp == null) { - if (config.isAllowPoolingConnection()) { + if (config.isAllowPoolingConnections()) { cp = new DefaultChannelPool(config, nettyTimer); } else { cp = new NoopChannelPool(); @@ -143,7 +143,7 @@ public Channels(final AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig secureWebSocketBootstrap.option(key, value); } - int timeOut = config.getConnectionTimeoutInMs() > 0 ? config.getConnectionTimeoutInMs() : Integer.MAX_VALUE; + int timeOut = config.getConnectionTimeout() > 0 ? config.getConnectionTimeout() : Integer.MAX_VALUE; plainBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeOut); webSocketBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeOut); secureBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeOut); @@ -340,7 +340,7 @@ public boolean preemptChannel(AsyncHandler asyncHandler, String poolKey) thro if (channelManager.preemptChannel(poolKey)) { channelPreempted = true; } else { - IOException ex = new IOException(String.format("Too many connections %s", config.getMaxTotalConnections())); + IOException ex = new IOException(String.format("Too many connections %s", config.getMaxConnections())); try { asyncHandler.onThrowable(ex); } catch (Exception e) { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java index f46a63d310..88e9b72556 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java @@ -53,10 +53,10 @@ public final class DefaultChannelPool implements ChannelPool { private final long cleanerPeriod; public DefaultChannelPool(AsyncHttpClientConfig config, Timer hashedWheelTimer) { - this(config.getMaxConnectionPerHost(),// - config.getIdleConnectionInPoolTimeoutInMs(),// - config.getMaxConnectionLifeTimeInMs(),// - config.isSslConnectionPoolEnabled(),// + this(config.getMaxConnectionsPerHost(),// + config.getPooledConnectionIdleTimeout(),// + config.getConnectionTTL(),// + config.isAllowPoolingSslConnections(),// hashedWheelTimer); } 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 ff18a9d5b8..5997c6b77d 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 @@ -230,7 +230,7 @@ private ListenableFuture sendRequestWithNewChannel(// // only compute when maxConnectionPerHost is enabled // FIXME clean up - if (config.getMaxConnectionPerHost() > 0) + if (config.getMaxConnectionsPerHost() > 0) poolKey = channels.getPoolKey(future); channelPreempted = channels.preemptChannel(asyncHandler, poolKey); @@ -390,11 +390,11 @@ private void scheduleTimeouts(NettyResponseFuture nettyResponseFuture) { timeoutsHolder.requestTimeout = requestTimeout; } - int idleConnectionTimeoutInMs = config.getIdleConnectionTimeoutInMs(); - if (idleConnectionTimeoutInMs != -1 && idleConnectionTimeoutInMs < requestTimeoutInMs) { + int readTimeout = config.getReadTimeout(); + if (readTimeout != -1 && readTimeout < requestTimeoutInMs) { // no need for a idleConnectionTimeout that's less than the requestTimeoutInMs Timeout idleConnectionTimeout = channels.newTimeoutInMs(new IdleConnectionTimeoutTimerTask(nettyResponseFuture, channels, - timeoutsHolder, closed, requestTimeoutInMs, idleConnectionTimeoutInMs), idleConnectionTimeoutInMs); + timeoutsHolder, closed, requestTimeoutInMs, readTimeout), readTimeout); timeoutsHolder.idleConnectionTimeout = idleConnectionTimeout; } nettyResponseFuture.setTimeoutsHolder(timeoutsHolder); 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 ef02fa7896..6c333eae1b 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 @@ -82,7 +82,7 @@ public void testRequestTimeout() throws IOException { final Semaphore requestThrottle = new Semaphore(1); final AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setCompressionEnabled(true) - .setAllowPoolingConnection(true).setMaxConnectionsTotal(1).build()); + .setAllowPoolingConnections(true).setMaxConnections(1).build()); int samples = 10; 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 7c29f993e3..c5d1798595 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 @@ -88,10 +88,10 @@ private ListenableFuture testMethodRequest(AsyncHttpClient client, int public void testRetryNonBlocking() throws IOException, InterruptedException, ExecutionException { AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// - .setAllowPoolingConnection(true)// - .setMaxConnectionsTotal(100)// - .setConnectionTimeoutInMs(60000)// - .setRequestTimeoutInMs(30000)// + .setAllowPoolingConnections(true)// + .setMaxConnections(100)// + .setConnectionTimeout(60000)// + .setRequestTimeout(30000)// .build(); AsyncHttpClient client = getAsyncHttpClient(config); @@ -124,10 +124,10 @@ public void testRetryNonBlocking() throws IOException, InterruptedException, Exe public void testRetryNonBlockingAsyncConnect() throws IOException, InterruptedException, ExecutionException { AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()// - .setAllowPoolingConnection(true)// - .setMaxConnectionsTotal(100)// - .setConnectionTimeoutInMs(60000)// - .setRequestTimeoutInMs(30000)// + .setAllowPoolingConnections(true)// + .setMaxConnections(100)// + .setConnectionTimeout(60000)// + .setRequestTimeout(30000)// .build(); AsyncHttpClient client = getAsyncHttpClient(config); From 800cd02544d6ca0ab926f9af1c8c7ada41397ab1 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 17 Jul 2014 23:16:10 +0200 Subject: [PATCH 0105/2020] Rename webSocketReadTimeout into webSocketTimeout --- .../AsyncHttpClientConfig.java | 20 +++++++++---------- .../AsyncHttpClientConfigBean.java | 2 +- .../AsyncHttpClientConfigDefaults.java | 4 ++-- .../providers/grizzly/EventHandler.java | 2 +- .../grizzly/GrizzlyAsyncHttpProvider.java | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java index 265008c2bb..5146b997a6 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -73,7 +73,7 @@ public class AsyncHttpClientConfig { protected int requestTimeout; protected int readTimeout; - protected int webSocketReadTimeout; + protected int webSocketTimeout; protected boolean allowPoolingConnections; protected boolean allowPoolingSslConnections; @@ -118,7 +118,7 @@ private AsyncHttpClientConfig(int connectionTimeout,// int maxConnectionsPerHost,// int requestTimeout,// int readTimeout,// - int webSocketIdleTimeout,// + int webSocketTimeout,// boolean allowPoolingConnection,// boolean allowSslConnectionPool,// int idleConnectionInPoolTimeout,// @@ -153,7 +153,7 @@ private AsyncHttpClientConfig(int connectionTimeout,// this.maxConnectionsPerHost = maxConnectionsPerHost; this.requestTimeout = requestTimeout; this.readTimeout = readTimeout; - this.webSocketReadTimeout = webSocketIdleTimeout; + this.webSocketTimeout = webSocketTimeout; this.allowPoolingConnections = allowPoolingConnection; this.allowPoolingSslConnections = allowSslConnectionPool; this.pooledConnectionIdleTimeout = idleConnectionInPoolTimeout; @@ -216,8 +216,8 @@ public int getConnectionTimeout() { * Return the maximum time, in milliseconds, a {@link org.asynchttpclient.websocket.WebSocket} may be idle before being timed out. * @return the maximum time, in milliseconds, a {@link org.asynchttpclient.websocket.WebSocket} may be idle before being timed out. */ - public int getWebSocketReadTimeout() { - return webSocketReadTimeout; + public int getWebSocketTimeout() { + return webSocketTimeout; } /** @@ -532,7 +532,7 @@ public static class Builder { private int maxConnectionsPerHost = defaultMaxConnectionsPerHost(); private int requestTimeout = defaultRequestTimeout(); private int readTimeout = defaultReadTimeout(); - private int webSocketReadTimeout = defaultWebSocketReadTimeout(); + private int webSocketTimeout = defaultWebSocketTimeout(); private boolean allowPoolingConnections = defaultAllowPoolingConnections(); private boolean allowPoolingSslConnections = defaultAllowPoolingSslConnections(); private int pooledConnectionIdleTimeout = defaultPooledConnectionIdleTimeout(); @@ -605,12 +605,12 @@ public Builder setConnectionTimeout(int connectionTimeout) { /** * Set the maximum time in millisecond an {@link org.asynchttpclient.websocket.WebSocket} can stay idle. * - * @param webSocketIdleTimeout + * @param webSocketTimeout * the maximum time in millisecond an {@link org.asynchttpclient.websocket.WebSocket} can stay idle. * @return a {@link Builder} */ - public Builder setWebSocketReadTimeout(int webSocketReadTimeout) { - this.webSocketReadTimeout = webSocketReadTimeout; + public Builder setWebSocketTimeout(int webSocketTimeout) { + this.webSocketTimeout = webSocketTimeout; return this; } @@ -1099,7 +1099,7 @@ public AsyncHttpClientConfig build() { maxConnectionsPerHost,// requestTimeout,// readTimeout,// - webSocketReadTimeout,// + webSocketTimeout,// allowPoolingConnections,// allowPoolingSslConnections,// pooledConnectionIdleTimeout,// diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java index e276b9f10a..d68c6a8984 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java @@ -48,7 +48,7 @@ void configureDefaults() { maxConnections = defaultMaxConnections(); maxConnectionsPerHost = defaultMaxConnectionsPerHost(); connectionTimeout = defaultConnectionTimeout(); - webSocketReadTimeout = defaultWebSocketReadTimeout(); + webSocketTimeout = defaultWebSocketTimeout(); pooledConnectionIdleTimeout = defaultPooledConnectionIdleTimeout(); readTimeout = defaultReadTimeout(); requestTimeout = defaultRequestTimeout(); diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java index e64f1a54b5..8ecd353491 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java @@ -49,8 +49,8 @@ public static int defaultRequestTimeout() { return Integer.getInteger(ASYNC_CLIENT + "requestTimeout", 60 * 1000); } - public static int defaultWebSocketReadTimeout() { - return Integer.getInteger(ASYNC_CLIENT + "webSocketReadTimeout", 15 * 60 * 1000); + public static int defaultWebSocketTimeout() { + return Integer.getInteger(ASYNC_CLIENT + "webSocketTimeout", 15 * 60 * 1000); } public static int defaultConnectionTTL() { 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 620606a399..6a99e9335a 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 @@ -277,7 +277,7 @@ public void onHttpHeadersParsed(HttpHeader httpHeader, FilterChainContext ctx) { ws.onConnect(); WebSocketHolder.set(ctx.getConnection(), context.getProtocolHandler(), ws); ((WebSocketUpgradeHandler) context.getHandler()).onSuccess(context.getWebSocket()); - final int wsTimeout = context.getProvider().getClientConfig().getWebSocketReadTimeout(); + final int wsTimeout = context.getProvider().getClientConfig().getWebSocketTimeout(); IdleTimeoutFilter.setCustomTimeout(ctx.getConnection(), ((wsTimeout <= 0) ? IdleTimeoutFilter.FOREVER : wsTimeout), TimeUnit.MILLISECONDS); context.result(handler.onCompleted()); 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 3bc8ed5f80..9215900d30 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 @@ -232,7 +232,7 @@ public long getTimeout(FilterChainContext ctx) { final HttpTxContext context = HttpTxContext.get(ctx); if (context != null) { if (context.isWSRequest()) { - return clientConfig.getWebSocketReadTimeout(); + return clientConfig.getWebSocketTimeout(); } int requestTimeout = AsyncHttpProviderUtils.requestTimeout(clientConfig, context.getRequest()); if (requestTimeout > 0) { From 6e340549e2591e52684d450199cdceaceedd71ba Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 17 Jul 2014 23:25:52 +0200 Subject: [PATCH 0106/2020] Rename IdleConnectionTimeout into ReadTimeout --- .../netty/request/NettyRequestSender.java | 6 ++--- ...merTask.java => ReadTimeoutTimerTask.java} | 24 +++++++++---------- .../netty/request/timeout/TimeoutsHolder.java | 8 +++---- 3 files changed, 19 insertions(+), 19 deletions(-) rename providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/{IdleConnectionTimeoutTimerTask.java => ReadTimeoutTimerTask.java} (71%) 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 5997c6b77d..79b76edb3d 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 @@ -33,7 +33,7 @@ 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.timeout.IdleConnectionTimeoutTimerTask; +import org.asynchttpclient.providers.netty.request.timeout.ReadTimeoutTimerTask; import org.asynchttpclient.providers.netty.request.timeout.RequestTimeoutTimerTask; import org.asynchttpclient.providers.netty.request.timeout.TimeoutsHolder; import org.asynchttpclient.uri.UriComponents; @@ -393,9 +393,9 @@ private void scheduleTimeouts(NettyResponseFuture nettyResponseFuture) { int readTimeout = config.getReadTimeout(); if (readTimeout != -1 && readTimeout < requestTimeoutInMs) { // no need for a idleConnectionTimeout that's less than the requestTimeoutInMs - Timeout idleConnectionTimeout = channels.newTimeoutInMs(new IdleConnectionTimeoutTimerTask(nettyResponseFuture, channels, + Timeout idleConnectionTimeout = channels.newTimeoutInMs(new ReadTimeoutTimerTask(nettyResponseFuture, channels, timeoutsHolder, closed, requestTimeoutInMs, readTimeout), readTimeout); - timeoutsHolder.idleConnectionTimeout = idleConnectionTimeout; + timeoutsHolder.readTimeout = idleConnectionTimeout; } nettyResponseFuture.setTimeoutsHolder(timeoutsHolder); } catch (RejectedExecutionException ex) { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/IdleConnectionTimeoutTimerTask.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/ReadTimeoutTimerTask.java similarity index 71% rename from providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/IdleConnectionTimeoutTimerTask.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/ReadTimeoutTimerTask.java index bea473c9d8..6199c9cd9b 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/IdleConnectionTimeoutTimerTask.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/ReadTimeoutTimerTask.java @@ -24,20 +24,20 @@ import java.util.concurrent.atomic.AtomicBoolean; -public class IdleConnectionTimeoutTimerTask extends TimeoutTimerTask { +public class ReadTimeoutTimerTask extends TimeoutTimerTask { - private final long idleConnectionTimeout; + private final long readTimeout; private final long requestTimeoutInstant; - public IdleConnectionTimeoutTimerTask(// + public ReadTimeoutTimerTask(// NettyResponseFuture nettyResponseFuture,// Channels channels,// TimeoutsHolder timeoutsHolder,// AtomicBoolean clientClosed,// long requestTimeout,// - long idleConnectionTimeout) { + long readTimeout) { super(nettyResponseFuture, channels, timeoutsHolder, clientClosed); - this.idleConnectionTimeout = idleConnectionTimeout; + this.readTimeout = readTimeout; requestTimeoutInstant = requestTimeout >= 0 ? nettyResponseFuture.getStart() + requestTimeout : Long.MAX_VALUE; } @@ -52,23 +52,23 @@ public void run(Timeout timeout) throws Exception { long now = millisTime(); - long currentIdleConnectionTimeoutInstant = idleConnectionTimeout + nettyResponseFuture.getLastTouch(); - long durationBeforeCurrentIdleConnectionTimeout = currentIdleConnectionTimeoutInstant - now; + long currentReadTimeoutInstant = readTimeout + nettyResponseFuture.getLastTouch(); + long durationBeforeCurrentReadTimeout = currentReadTimeoutInstant - now; - if (durationBeforeCurrentIdleConnectionTimeout <= 0L) { + if (durationBeforeCurrentReadTimeout <= 0L) { // idleConnectionTimeout reached - String message = "Idle connection timeout to " + nettyResponseFuture.getChannelRemoteAddress() + " of " + idleConnectionTimeout + " ms"; + String message = "Idle connection timeout to " + nettyResponseFuture.getChannelRemoteAddress() + " of " + readTimeout + " ms"; long durationSinceLastTouch = now - nettyResponseFuture.getLastTouch(); expire(message, durationSinceLastTouch); nettyResponseFuture.setIdleConnectionTimeoutReached(); - } else if (currentIdleConnectionTimeoutInstant < requestTimeoutInstant) { + } else if (currentReadTimeoutInstant < requestTimeoutInstant) { // reschedule - timeoutsHolder.idleConnectionTimeout = channels.newTimeoutInMs(this, durationBeforeCurrentIdleConnectionTimeout); + timeoutsHolder.readTimeout = channels.newTimeoutInMs(this, durationBeforeCurrentReadTimeout); } else { // otherwise, no need to reschedule: requestTimeout will happen sooner - timeoutsHolder.idleConnectionTimeout = null; + timeoutsHolder.readTimeout = null; } } else { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java index a27e903510..d032672a4a 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java @@ -23,7 +23,7 @@ public class TimeoutsHolder { private final AtomicBoolean cancelled = new AtomicBoolean(); public volatile Timeout requestTimeout; - public volatile Timeout idleConnectionTimeout; + public volatile Timeout readTimeout; public void cancel() { if (cancelled.compareAndSet(false, true)) { @@ -31,9 +31,9 @@ public void cancel() { requestTimeout.cancel(); requestTimeout = null; } - if (idleConnectionTimeout != null) { - idleConnectionTimeout.cancel(); - idleConnectionTimeout = null; + if (readTimeout != null) { + readTimeout.cancel(); + readTimeout = null; } } } From 1fb3b426ef513a76b4d4a64f09d999b597dd136c Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 18 Jul 2014 16:42:01 +0200 Subject: [PATCH 0107/2020] Unused parameter --- .../providers/netty/channel/pool/DefaultChannelPool.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java index 88e9b72556..3a04157660 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java @@ -53,15 +53,13 @@ public final class DefaultChannelPool implements ChannelPool { private final long cleanerPeriod; public DefaultChannelPool(AsyncHttpClientConfig config, Timer hashedWheelTimer) { - this(config.getMaxConnectionsPerHost(),// - config.getPooledConnectionIdleTimeout(),// + this(config.getPooledConnectionIdleTimeout(),// config.getConnectionTTL(),// config.isAllowPoolingSslConnections(),// hashedWheelTimer); } - public DefaultChannelPool(// - int maxConnectionPerHost,// + public DefaultChannelPool( long maxIdleTime,// int maxConnectionTTL,// boolean sslConnectionPoolEnabled,// From d091dfb6cc011cd77b0ee23cb7d09a93170b9bc6 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 21 Jul 2014 13:05:57 +0200 Subject: [PATCH 0108/2020] Minor clean up --- .../netty/future/StackTraceInspector.java | 38 +++++++++---------- .../netty/request/NettyConnectListener.java | 13 +++---- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java index 94abc067b2..80d99fa1be 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java @@ -14,57 +14,57 @@ public class StackTraceInspector { - private static boolean exceptionInMethod(Throwable cause, String className, String methodName) { + private static boolean exceptionInMethod(Throwable t, String className, String methodName) { try { - for (StackTraceElement element : cause.getStackTrace()) { + for (StackTraceElement element : t.getStackTrace()) { if (element.getClassName().equals(className) && element.getMethodName().equals(methodName)) return true; } - } catch (Throwable t) { + } catch (Throwable ignore) { } return false; } - private static boolean abortOnConnectCloseException(Throwable cause) { + private static boolean abortOnConnectCloseException(Throwable t) { - if (exceptionInMethod(cause, "sun.nio.ch.SocketChannelImpl", "checkConnect")) + if (exceptionInMethod(t, "sun.nio.ch.SocketChannelImpl", "checkConnect")) return true; - if (cause.getCause() != null) - return abortOnConnectCloseException(cause.getCause()); + if (t.getCause() != null) + return abortOnConnectCloseException(t.getCause()); return false; } - public static boolean abortOnDisconnectException(Throwable cause) { + public static boolean abortOnDisconnectException(Throwable t) { - if (exceptionInMethod(cause, "io.netty.handler.ssl.SslHandler", "disconnect")) + if (exceptionInMethod(t, "io.netty.handler.ssl.SslHandler", "disconnect")) return true; - if (cause.getCause() != null) - return abortOnConnectCloseException(cause.getCause()); + if (t.getCause() != null) + return abortOnConnectCloseException(t.getCause()); return false; } - public static boolean abortOnReadCloseException(Throwable cause) { + public static boolean abortOnReadCloseException(Throwable t) { - if (exceptionInMethod(cause, "sun.nio.ch.SocketDispatcher", "read")) + if (exceptionInMethod(t, "sun.nio.ch.SocketDispatcher", "read")) return true; - if (cause.getCause() != null) - return abortOnReadCloseException(cause.getCause()); + if (t.getCause() != null) + return abortOnReadCloseException(t.getCause()); return false; } - public static boolean abortOnWriteCloseException(Throwable cause) { + public static boolean abortOnWriteCloseException(Throwable t) { - if (exceptionInMethod(cause, "sun.nio.ch.SocketDispatcher", "write")) + if (exceptionInMethod(t, "sun.nio.ch.SocketDispatcher", "write")) return true; - if (cause.getCause() != null) - return abortOnWriteCloseException(cause.getCause()); + if (t.getCause() != null) + return abortOnWriteCloseException(t.getCause()); return false; } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java index 05b36fd230..c1ca441e1f 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java @@ -124,7 +124,7 @@ public void onFutureFailure(Channel channel, Throwable cause) { LOGGER.debug("Trying to recover a dead cached channel {} with a retry value of {} ", channel, canRetry); if (canRetry// && cause != null// - && (StackTraceInspector.abortOnDisconnectException(cause) || cause instanceof ClosedChannelException || future.getState() != NettyResponseFuture.STATE.NEW)) { + && (cause instanceof ClosedChannelException || future.getState() != NettyResponseFuture.STATE.NEW || StackTraceInspector.abortOnDisconnectException(cause))) { LOGGER.debug("Retrying {} ", future.getNettyRequest()); if (requestSender.retry(future, channel)) { @@ -135,19 +135,18 @@ public void onFutureFailure(Channel channel, Throwable cause) { LOGGER.debug("Failed to recover from exception: {} with channel {}", cause, channel); boolean printCause = cause != null && cause.getMessage() != null; - String printedCause = printCause ? cause.getMessage() + " to " + future.getURI().toString() : future.getURI().toString(); + String url = future.getURI().toUrl(); + String printedCause = printCause ? cause.getMessage() + " to " + url : url; ConnectException e = new ConnectException(printedCause); - if (cause != null) { + if (cause != null) e.initCause(cause); - } future.abort(e); } public final void operationComplete(ChannelFuture f) throws Exception { - if (f.isSuccess()) { + if (f.isSuccess()) onFutureSuccess(f.channel()); - } else { + else onFutureFailure(f.channel(), f.cause()); - } } } From e507109646ce70df7e6b095e5dab5ad563f42b8f Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 21 Jul 2014 13:28:19 +0200 Subject: [PATCH 0109/2020] Optimize stack scan, close #635 --- .../netty/future/StackTraceInspector.java | 26 ++++++++----------- .../providers/netty/handler/Processor.java | 2 +- .../netty/request/ProgressListener.java | 3 +-- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java index 80d99fa1be..d47878b419 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java @@ -47,24 +47,20 @@ public static boolean abortOnDisconnectException(Throwable t) { return false; } - public static boolean abortOnReadCloseException(Throwable t) { + public static boolean abortOnReadOrWriteException(Throwable t) { - if (exceptionInMethod(t, "sun.nio.ch.SocketDispatcher", "read")) - return true; - - if (t.getCause() != null) - return abortOnReadCloseException(t.getCause()); - - return false; - } - - public static boolean abortOnWriteCloseException(Throwable t) { - - if (exceptionInMethod(t, "sun.nio.ch.SocketDispatcher", "write")) - return true; + try { + for (StackTraceElement element : t.getStackTrace()) { + String className = element.getClassName(); + String methodName = element.getMethodName(); + if (className.equals("sun.nio.ch.SocketDispatcher") && (methodName.equals("read") || methodName.equals("write"))) + return true; + } + } catch (Throwable ignore) { + } if (t.getCause() != null) - return abortOnWriteCloseException(t.getCause()); + return abortOnReadOrWriteException(t.getCause()); return false; } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java index 7c69daeba7..f1d6a1d3e0 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java @@ -183,7 +183,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Excep } } - if (StackTraceInspector.abortOnReadCloseException(cause) || StackTraceInspector.abortOnWriteCloseException(cause)) { + if (StackTraceInspector.abortOnReadOrWriteException(cause)) { LOGGER.debug("Trying to recover from dead Channel: {}", channel); return; } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/ProgressListener.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/ProgressListener.java index ad31a075bc..78c9f35919 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/ProgressListener.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/ProgressListener.java @@ -61,8 +61,7 @@ private boolean abortOnThrowable(Throwable cause, Channel channel) { } catch (RuntimeException ex) { LOGGER.debug(ex.getMessage(), ex); } - } else if (cause instanceof ClosedChannelException || StackTraceInspector.abortOnReadCloseException(cause) - || StackTraceInspector.abortOnWriteCloseException(cause)) { + } else if (cause instanceof ClosedChannelException || StackTraceInspector.abortOnReadOrWriteException(cause)) { if (LOGGER.isDebugEnabled()) LOGGER.debug(cause.getMessage(), cause); From e1c7f4aeef9e07c98d33f8d1121dbd77559efb6b Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 21 Jul 2014 13:32:18 +0200 Subject: [PATCH 0110/2020] minor clean up --- .../netty/future/StackTraceInspector.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java index d47878b419..f4a6589768 100644 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java @@ -26,25 +26,13 @@ private static boolean exceptionInMethod(Throwable t, String className, String m } private static boolean abortOnConnectCloseException(Throwable t) { - - if (exceptionInMethod(t, "sun.nio.ch.SocketChannelImpl", "checkConnect")) - return true; - - if (t.getCause() != null) - return abortOnConnectCloseException(t.getCause()); - - return false; + return exceptionInMethod(t, "sun.nio.ch.SocketChannelImpl", "checkConnect") + || (t.getCause() != null && abortOnConnectCloseException(t.getCause())); } public static boolean abortOnDisconnectException(Throwable t) { - - if (exceptionInMethod(t, "io.netty.handler.ssl.SslHandler", "disconnect")) - return true; - - if (t.getCause() != null) - return abortOnConnectCloseException(t.getCause()); - - return false; + return exceptionInMethod(t, "io.netty.handler.ssl.SslHandler", "disconnect") + || (t.getCause() != null && abortOnConnectCloseException(t.getCause())); } public static boolean abortOnReadOrWriteException(Throwable t) { From c3589e01b878030dddad8ebefb8ed5eef8a375fc Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 21 Jul 2014 14:32:55 +0200 Subject: [PATCH 0111/2020] NettyResponseFuture.get should always block on the latch, close #489 --- .../providers/netty/future/NettyResponseFuture.java | 5 ++--- 1 file changed, 2 insertions(+), 3 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 1418c3c4a5..9d2a692f77 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 @@ -156,14 +156,13 @@ public boolean cancel(boolean force) { @Override public V get() throws InterruptedException, ExecutionException { - if (!isDone()) - latch.await(); + latch.await(); return getContent(); } @Override public V get(long l, TimeUnit tu) throws InterruptedException, TimeoutException, ExecutionException { - if (!isDone() && !latch.await(l, tu)) + if (!latch.await(l, tu)) throw new TimeoutException(); return getContent(); } From 4ae7bc92e8a7b0e6b45e535ac2488bd1e8b929ff Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 21 Jul 2014 15:01:30 +0200 Subject: [PATCH 0112/2020] minor clean up --- .../util/AuthenticatorUtils.java | 44 ++++++++----------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java b/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java index 594eafa1a3..0da1666af1 100644 --- a/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java @@ -50,38 +50,30 @@ private static String computeRealmURI(Realm realm) { public static String computeDigestAuthentication(Realm realm) throws NoSuchAlgorithmException { StringBuilder builder = new StringBuilder().append("Digest "); - construct(builder, "username", realm.getPrincipal()); - construct(builder, "realm", realm.getRealmName()); - construct(builder, "nonce", realm.getNonce()); - construct(builder, "uri", computeRealmURI(realm)); - builder.append("algorithm").append('=').append(realm.getAlgorithm()).append(", "); + append(builder, "username", realm.getPrincipal(), true); + append(builder, "realm", realm.getRealmName(), true); + append(builder, "nonce", realm.getNonce(), true); + append(builder, "uri", computeRealmURI(realm), true); + append(builder, "algorithm", realm.getAlgorithm(), false); - construct(builder, "response", realm.getResponse()); + append(builder, "response", realm.getResponse(), true); if (isNonEmpty(realm.getOpaque())) - construct(builder, "opaque", realm.getOpaque()); - builder.append("qop").append('=').append(realm.getQop()).append(", "); - builder.append("nc").append('=').append(realm.getNc()).append(", "); - construct(builder, "cnonce", realm.getCnonce(), true); + append(builder, "opaque", realm.getOpaque(), true); + append(builder, "qop", realm.getQop(), false); + append(builder, "nc", realm.getNc(), false); + append(builder, "cnonce", realm.getCnonce(), true); + builder.setLength(builder.length() - 2); // remove tailing ", " return new String(builder.toString().getBytes(StandardCharsets.ISO_8859_1)); } - public static String computeDigestAuthentication(ProxyServer proxy) { - try { - StringBuilder builder = new StringBuilder().append("Digest "); - construct(builder, "username", proxy.getPrincipal(), true); - return new String(builder.toString().getBytes(StandardCharsets.ISO_8859_1)); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - private static StringBuilder construct(StringBuilder builder, String name, String value) { - return construct(builder, name, value, false); - } + private static StringBuilder append(StringBuilder builder, String name, String value, boolean quoted) { + builder.append(name).append('='); + if (quoted) + builder.append('"').append(value).append('"'); + else + builder.append(value); - private static StringBuilder construct(StringBuilder builder, String name, String value, boolean tail) { - return builder.append(name).append('=').append('"').append(value).append(tail ? "\"" : "\", "); + return builder.append(", "); } } From 399d78c9dd79d30d3e5e63363d1e27ad1bd2cd84 Mon Sep 17 00:00:00 2001 From: oleksiys Date: Tue, 22 Jul 2014 11:58:28 -0700 Subject: [PATCH 0113/2020] [master] + fix issue #637 https://github.com/AsyncHttpClient/async-http-client/issues/637 "AHC with Grizzly provider not handling SSL Connect/tunnelling with Proxy" --- .../providers/grizzly/EventHandler.java | 15 +++++++++++++++ .../filters/AsyncHttpClientEventFilter.java | 8 ++++++++ .../providers/grizzly/filters/ProxyFilter.java | 2 +- .../providers/grizzly/filters/TunnelFilter.java | 4 ++-- 4 files changed, 26 insertions(+), 3 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 6a99e9335a..2de816a312 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 @@ -55,6 +55,8 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.TimeUnit; +import org.glassfish.grizzly.Buffer; +import org.glassfish.grizzly.http.HttpRequestPacket; public final class EventHandler { @@ -304,6 +306,19 @@ public void onHttpHeadersParsed(HttpHeader httpHeader, FilterChainContext ctx) { } + public boolean onHttpHeaderParsed(final HttpHeader httpHeader, + final Buffer buffer, final FilterChainContext ctx) { + final HttpRequestPacket request = ((HttpResponsePacket) httpHeader).getRequest(); + if (Method.CONNECT.equals(request.getMethod())) { + // finish request/response processing, because Grizzly itself + // treats CONNECT traffic as part of request-response processing + // and we don't want it be treated like that + httpHeader.setExpectContent(false); + } + + return false; + } + @SuppressWarnings("rawtypes") public boolean onHttpPacketParsed(HttpHeader httpHeader, FilterChainContext ctx) { 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 6afb87197a..687984e6a8 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 @@ -22,6 +22,7 @@ import java.io.IOException; import org.asynchttpclient.providers.grizzly.filters.events.GracefulCloseEvent; +import org.glassfish.grizzly.Buffer; import org.glassfish.grizzly.filterchain.FilterChainEvent; import org.glassfish.grizzly.filterchain.NextAction; import org.glassfish.grizzly.http.HttpResponsePacket; @@ -111,6 +112,13 @@ protected void onHttpHeadersParsed(HttpHeader httpHeader, FilterChainContext ctx eventHandler.onHttpHeadersParsed(httpHeader, ctx); } + @Override + protected boolean onHttpHeaderParsed(final HttpHeader httpHeader, + final Buffer buffer, final FilterChainContext ctx) { + super.onHttpHeaderParsed(httpHeader, buffer, ctx); + return eventHandler.onHttpHeaderParsed(httpHeader, buffer, ctx); + } + @Override protected boolean onHttpPacketParsed(HttpHeader httpHeader, FilterChainContext ctx) { return eventHandler.onHttpPacketParsed(httpHeader, ctx); 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 ded8031daf..4b30c7c6cf 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 @@ -100,7 +100,7 @@ private String generateAuthHeader(final Realm realm) { case BASIC: return computeBasicAuthentication(realm); case DIGEST: - return computeDigestAuthentication(proxyServer); + return computeDigestAuthentication(realm); case NTLM: return NTLM_ENGINE.generateType1Msg("NTLM " + realm.getNtlmDomain(), realm.getNtlmHost()); default: diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/TunnelFilter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/TunnelFilter.java index ed9723fa96..aae301883f 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/TunnelFilter.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/filters/TunnelFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Sonatype, Inc. All rights reserved. + * Copyright (c) 2013-2014 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. @@ -94,7 +94,7 @@ public NextAction handleEvent(FilterChainContext ctx, FilterChainEvent event) th suspendedContext.resume(ctx.getInvokeAction()); // Stop further event processing. - ctx.getStopAction(); + return ctx.getStopAction(); } return ctx.getInvokeAction(); } From 34a95f04c22198b6575c9fd82f0f2ef117e1cbed Mon Sep 17 00:00:00 2001 From: oleksiys Date: Tue, 22 Jul 2014 21:37:16 -0700 Subject: [PATCH 0114/2020] [master] + fix issue #631 https://github.com/AsyncHttpClient/async-http-client/issues/631 "Grizzly provider aggressively set cookie missing domain and path to /" --- .../providers/grizzly/EventHandler.java | 6 +++--- .../grizzly/filters/AsyncHttpClientFilter.java | 12 ++++-------- 2 files changed, 7 insertions(+), 11 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 2de816a312..50675690ac 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 @@ -185,7 +185,7 @@ public void onInitialLineParsed(HttpHeader httpHeader, FilterChainContext ctx) { try { context.result(handler.onCompleted()); context.done(); - } catch (Exception e) { + } catch (Throwable e) { context.abort(e); } } @@ -289,7 +289,7 @@ public void onHttpHeadersParsed(HttpHeader httpHeader, FilterChainContext ctx) { "WebSocket protocol error: unexpected HTTP response status during handshake."); context.result(null); } - } catch (Exception e) { + } catch (Throwable e) { httpHeader.setSkipRemainder(true); context.abort(e); } @@ -341,7 +341,7 @@ public boolean onHttpPacketParsed(HttpHeader httpHeader, FilterChainContext ctx) if (handler != null) { try { context.result(handler.onCompleted()); - } catch (Exception e) { + } catch (Throwable e) { context.abort(e); } } else { 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 21dc88c516..7d7f002699 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 @@ -520,17 +520,13 @@ private void addCookies(final Request request, final HttpRequestPacket requestPa } } - private static void convertCookies(final Collection cookies, final org.glassfish.grizzly.http.Cookie[] gCookies) { + private static void convertCookies(final Collection cookies, + final org.glassfish.grizzly.http.Cookie[] gCookies) { int idx = 0; if (!cookies.isEmpty()) { for (final Cookie cookie : cookies) { - final org.glassfish.grizzly.http.Cookie gCookie = new org.glassfish.grizzly.http.Cookie(cookie.getName(), cookie.getValue()); - gCookie.setDomain(cookie.getDomain()); - gCookie.setPath(cookie.getPath()); - gCookie.setMaxAge(cookie.getMaxAge()); - gCookie.setSecure(cookie.isSecure()); - gCookies[idx] = gCookie; - idx++; + gCookies[idx++] = new org.glassfish.grizzly.http.Cookie( + cookie.getName(), cookie.getValue()); } } } From dd92f36a0ff9cfa56d5b93aec9a24e7adfcfd1c4 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 23 Jul 2014 18:26:23 +0200 Subject: [PATCH 0115/2020] Make AHC 1.9 and AHC2 code converge, close #638 --- .../providers/netty/DiscardEvent.java | 0 .../netty/NettyAsyncHttpProvider.java | 56 +- .../netty/NettyAsyncHttpProviderConfig.java | 51 +- .../netty/channel/ChannelManager.java | 298 ++++++++- .../providers/netty/channel/Channels.java | 406 +------------ .../CleanupChannelGroup.java | 2 +- .../netty/channel/SslInitializer.java | 10 +- .../netty/channel/pool/ChannelPool.java | 21 +- .../channel/pool/DefaultChannelPool.java | 0 .../netty/channel/pool/NoopChannelPool.java | 20 +- .../netty/future/NettyResponseFuture.java | 54 +- .../netty/future/StackTraceInspector.java | 0 .../providers/netty/handler/HttpProtocol.java | 318 +++++----- .../providers/netty/handler/Processor.java | 127 ++-- .../providers/netty/handler/Protocol.java | 102 ++-- .../netty/handler/WebSocketProtocol.java | 109 ++-- .../netty/request/NettyConnectListener.java | 38 +- .../providers/netty/request/NettyRequest.java | 22 +- .../netty/request/NettyRequestFactory.java | 56 +- .../netty/request/NettyRequestSender.java | 572 ++++++++++-------- .../netty/request/ProgressListener.java | 37 +- .../netty/request/body/BodyChunkedInput.java | 5 +- .../netty/request/body/BodyFileRegion.java | 5 +- .../request/body/FeedableBodyGenerator.java | 5 +- .../netty/request/body/NettyBody.java | 20 +- .../netty/request/body/NettyBodyBody.java | 20 +- .../request/body/NettyByteArrayBody.java | 20 +- .../netty/request/body/NettyFileBody.java | 20 +- .../request/body/NettyInputStreamBody.java | 20 +- .../request/body/NettyMultipartBody.java | 29 +- .../request/timeout/ReadTimeoutTimerTask.java | 36 +- .../timeout/RequestTimeoutTimerTask.java | 35 +- .../request/timeout/TimeoutTimerTask.java | 44 +- .../netty/request/timeout/TimeoutsHolder.java | 20 +- .../netty/response/EagerResponseBodyPart.java | 4 +- .../netty/response/LazyResponseBodyPart.java | 0 .../netty/response/NettyResponse.java | 46 +- .../netty/response/NettyResponseBodyPart.java | 20 +- .../netty/response/ResponseHeaders.java | 20 +- .../netty/response/ResponseStatus.java | 23 +- .../{ByteBufUtil.java => ByteBufUtils.java} | 4 +- .../util/{HttpUtil.java => HttpUtils.java} | 4 +- .../providers/netty/ws/NettyWebSocket.java | 5 +- ...WebSocketUtil.java => WebSocketUtils.java} | 7 +- 44 files changed, 1290 insertions(+), 1421 deletions(-) mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/DiscardEvent.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/Channels.java rename providers/netty/src/main/java/org/asynchttpclient/providers/netty/{util => channel}/CleanupChannelGroup.java (98%) mode change 100644 => 100755 mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/ChannelPool.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/NoopChannelPool.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/HttpProtocol.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Protocol.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/WebSocketProtocol.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequest.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestSender.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/ProgressListener.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyChunkedInput.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/FeedableBodyGenerator.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBody.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyByteArrayBody.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyFileBody.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyInputStreamBody.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyMultipartBody.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/ReadTimeoutTimerTask.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutTimerTask.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/EagerResponseBodyPart.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/LazyResponseBodyPart.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponse.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseBodyPart.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseHeaders.java mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseStatus.java rename providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/{ByteBufUtil.java => ByteBufUtils.java} (96%) mode change 100644 => 100755 rename providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/{HttpUtil.java => HttpUtils.java} (96%) mode change 100644 => 100755 mode change 100644 => 100755 providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java rename providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/{WebSocketUtil.java => WebSocketUtils.java} (91%) mode change 100644 => 100755 diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/DiscardEvent.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/DiscardEvent.java old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 index 5fb2104212..9f65646fe5 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProvider.java @@ -1,41 +1,44 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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 io.netty.util.HashedWheelTimer; +import io.netty.util.Timer; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.AsyncHttpProvider; import org.asynchttpclient.ListenableFuture; import org.asynchttpclient.Request; -import org.asynchttpclient.providers.netty.channel.Channels; +import org.asynchttpclient.providers.netty.channel.ChannelManager; import org.asynchttpclient.providers.netty.request.NettyRequestSender; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.util.concurrent.atomic.AtomicBoolean; - public class NettyAsyncHttpProvider implements AsyncHttpProvider { private static final Logger LOGGER = LoggerFactory.getLogger(NettyAsyncHttpProvider.class); private final NettyAsyncHttpProviderConfig nettyConfig; private final AtomicBoolean closed = new AtomicBoolean(false); - private final Channels channels; + private final ChannelManager channelManager; private final NettyRequestSender requestSender; + private final boolean allowStopNettyTimer; + private final Timer nettyTimer; public NettyAsyncHttpProvider(AsyncHttpClientConfig config) { @@ -43,16 +46,29 @@ public NettyAsyncHttpProvider(AsyncHttpClientConfig config) { (NettyAsyncHttpProviderConfig) config.getAsyncHttpProviderConfig() : new NettyAsyncHttpProviderConfig(); - channels = new Channels(config, nettyConfig); - requestSender = new NettyRequestSender(closed, config, nettyConfig, channels); - channels.configureProcessor(requestSender, closed); + allowStopNettyTimer = nettyConfig.getNettyTimer() == null; + nettyTimer = allowStopNettyTimer ? newNettyTimer() : nettyConfig.getNettyTimer(); + + channelManager = new ChannelManager(config, nettyConfig, nettyTimer); + requestSender = new NettyRequestSender(config, nettyConfig, channelManager, nettyTimer, closed); + channelManager.configureBootstraps(requestSender, closed); + } + + private Timer newNettyTimer() { + HashedWheelTimer timer = new HashedWheelTimer(); + timer.start(); + return timer; } @Override public void close() { if (closed.compareAndSet(false, true)) { try { - channels.close(); + channelManager.close(); + + if (allowStopNettyTimer) + nettyTimer.stop(); + } catch (Throwable t) { LOGGER.warn("Unexpected error on close", t); } 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 old mode 100644 new mode 100755 index 554e1643c1..f066ab51d5 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java @@ -1,18 +1,15 @@ /* - * Copyright 2010 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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. + * 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; @@ -127,17 +124,17 @@ public NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { /** * HttpClientCodec's maxInitialLineLength */ - private int maxInitialLineLength = 4096; + private int httpClientCodecMaxInitialLineLength = 4096; /** * HttpClientCodec's maxHeaderSize */ - private int maxHeaderSize = 8192; + private int httpClientCodecMaxHeaderSize = 8192; /** * HttpClientCodec's maxChunkSize */ - private int maxChunkSize = 8192; + private int httpClientCodecMaxChunkSize = 8192; private ResponseBodyPartFactory bodyPartFactory = new EagerResponseBodyPartFactory(); @@ -191,28 +188,28 @@ public void setWssAdditionalChannelInitializer(AdditionalChannelInitializer wssA this.wssAdditionalChannelInitializer = wssAdditionalChannelInitializer; } - public int getMaxInitialLineLength() { - return maxInitialLineLength; + public int getHttpClientCodecMaxInitialLineLength() { + return httpClientCodecMaxInitialLineLength; } - public void setMaxInitialLineLength(int maxInitialLineLength) { - this.maxInitialLineLength = maxInitialLineLength; + public void setHttpClientCodecMaxInitialLineLength(int httpClientCodecMaxInitialLineLength) { + this.httpClientCodecMaxInitialLineLength = httpClientCodecMaxInitialLineLength; } - public int getMaxHeaderSize() { - return maxHeaderSize; + public int getHttpClientCodecMaxHeaderSize() { + return httpClientCodecMaxHeaderSize; } - public void setMaxHeaderSize(int maxHeaderSize) { - this.maxHeaderSize = maxHeaderSize; + public void setHttpClientCodecMaxHeaderSize(int httpClientCodecMaxHeaderSize) { + this.httpClientCodecMaxHeaderSize = httpClientCodecMaxHeaderSize; } - public int getMaxChunkSize() { - return maxChunkSize; + public int getHttpClientCodecMaxChunkSize() { + return httpClientCodecMaxChunkSize; } - public void setMaxChunkSize(int maxChunkSize) { - this.maxChunkSize = maxChunkSize; + public void setHttpClientCodecMaxChunkSize(int httpClientCodecMaxChunkSize) { + this.httpClientCodecMaxChunkSize = httpClientCodecMaxChunkSize; } public ResponseBodyPartFactory getBodyPartFactory() { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java old mode 100644 new mode 100755 index dd36702b85..047dfc2a26 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java @@ -13,37 +13,104 @@ */ package org.asynchttpclient.providers.netty.channel; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.providers.netty.channel.pool.ChannelPool; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.util.CleanupChannelGroup; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import static org.asynchttpclient.providers.netty.util.HttpUtils.WEBSOCKET; +import static org.asynchttpclient.providers.netty.util.HttpUtils.isSecure; +import static org.asynchttpclient.providers.netty.util.HttpUtils.isWebSocket; +import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; import io.netty.channel.group.ChannelGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpContentDecompressor; +import io.netty.handler.codec.http.websocketx.WebSocket08FrameDecoder; +import io.netty.handler.codec.http.websocketx.WebSocket08FrameEncoder; +import io.netty.handler.ssl.SslHandler; +import io.netty.handler.stream.ChunkedWriteHandler; +import io.netty.util.Timer; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.providers.netty.Callback; +import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; +import org.asynchttpclient.providers.netty.channel.pool.ChannelPool; +import org.asynchttpclient.providers.netty.channel.pool.DefaultChannelPool; +import org.asynchttpclient.providers.netty.channel.pool.NoopChannelPool; +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; +import org.asynchttpclient.providers.netty.handler.HttpProtocol; +import org.asynchttpclient.providers.netty.handler.Processor; +import org.asynchttpclient.providers.netty.handler.WebSocketProtocol; +import org.asynchttpclient.providers.netty.request.NettyRequestSender; +import org.asynchttpclient.uri.UriComponents; +import org.asynchttpclient.util.SslUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ChannelManager { private static final Logger LOGGER = LoggerFactory.getLogger(ChannelManager.class); + public static final String HTTP_HANDLER = "httpHandler"; + public static final String SSL_HANDLER = "sslHandler"; + public static final String HTTP_PROCESSOR = "httpProcessor"; + public static final String WS_PROCESSOR = "wsProcessor"; + public static final String DEFLATER_HANDLER = "deflater"; + public static final String INFLATER_HANDLER = "inflater"; + public static final String CHUNKED_WRITER_HANDLER = "chunkedWriter"; + public static final String WS_DECODER_HANDLER = "ws-decoder"; + public static final String WS_ENCODER_HANDLER = "ws-encoder"; + + private final AsyncHttpClientConfig config; + private final NettyAsyncHttpProviderConfig nettyConfig; + + private final EventLoopGroup eventLoopGroup; + private final boolean allowReleaseEventLoopGroup; + + private final Bootstrap plainBootstrap; + private final Bootstrap secureBootstrap; + private final Bootstrap webSocketBootstrap; + private final Bootstrap secureWebSocketBootstrap; + + private final long handshakeTimeoutInMillis; private final ChannelPool channelPool; private final boolean maxConnectionsEnabled; private final Semaphore freeChannels; private final ChannelGroup openChannels; - private final int maxConnectionsPerHost; private final boolean maxConnectionsPerHostEnabled; private final ConcurrentHashMap freeChannelsPerHost; private final ConcurrentHashMap channel2KeyPool; - public ChannelManager(AsyncHttpClientConfig config, ChannelPool channelPool) { + private Processor wsProcessor; + + public ChannelManager(AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, Timer nettyTimer) { + + this.config = config; + this.nettyConfig = nettyConfig; + + ChannelPool channelPool = nettyConfig.getChannelPool(); + if (channelPool == null && config.isAllowPoolingConnections()) { + channelPool = new DefaultChannelPool(config, nettyTimer); + } else if (channelPool == null) { + channelPool = new NoopChannelPool(); + } this.channelPool = channelPool; maxConnectionsEnabled = config.getMaxConnections() > 0; - + maxConnectionsPerHostEnabled = config.getMaxConnectionsPerHost() > 0; + if (maxConnectionsEnabled) { openChannels = new CleanupChannelGroup("asyncHttpClient") { @Override @@ -69,9 +136,6 @@ public boolean remove(Object o) { freeChannels = null; } - maxConnectionsPerHost = config.getMaxConnectionsPerHost(); - maxConnectionsPerHostEnabled = config.getMaxConnectionsPerHost() > 0; - if (maxConnectionsPerHostEnabled) { freeChannelsPerHost = new ConcurrentHashMap(); channel2KeyPool = new ConcurrentHashMap(); @@ -79,6 +143,104 @@ public boolean remove(Object o) { freeChannelsPerHost = null; channel2KeyPool = null; } + + handshakeTimeoutInMillis = nettyConfig.getHandshakeTimeoutInMillis(); + + // check if external EventLoopGroup is defined + allowReleaseEventLoopGroup = nettyConfig.getEventLoopGroup() == null; + eventLoopGroup = allowReleaseEventLoopGroup ? new NioEventLoopGroup() : nettyConfig.getEventLoopGroup(); + if (!(eventLoopGroup instanceof NioEventLoopGroup)) + throw new IllegalArgumentException("Only Nio is supported"); + + 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); + + if (config.getConnectionTimeout() > 0) + nettyConfig.addChannelOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectionTimeout()); + for (Entry, Object> entry : nettyConfig.propertiesSet()) { + ChannelOption key = entry.getKey(); + Object value = entry.getValue(); + plainBootstrap.option(key, value); + webSocketBootstrap.option(key, value); + secureBootstrap.option(key, value); + secureWebSocketBootstrap.option(key, value); + } + } + + public void configureBootstraps(NettyRequestSender requestSender, AtomicBoolean closed) { + + HttpProtocol httpProtocol = new HttpProtocol(this, config, nettyConfig, requestSender); + final Processor httpProcessor = new Processor(config, this, requestSender, httpProtocol); + + WebSocketProtocol wsProtocol = new WebSocketProtocol(this, config, nettyConfig, requestSender); + wsProcessor = new Processor(config, this, requestSender, wsProtocol); + + plainBootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline().addLast(HTTP_HANDLER, newHttpClientCodec()); + + if (config.isCompressionEnabled()) { + pipeline.addLast(INFLATER_HANDLER, new HttpContentDecompressor()); + } + pipeline.addLast(CHUNKED_WRITER_HANDLER, new ChunkedWriteHandler())// + .addLast(HTTP_PROCESSOR, httpProcessor); + + if (nettyConfig.getHttpAdditionalChannelInitializer() != null) { + nettyConfig.getHttpAdditionalChannelInitializer().initChannel(ch); + } + } + }); + + webSocketBootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline()// + .addLast(HTTP_HANDLER, newHttpClientCodec())// + .addLast(WS_PROCESSOR, wsProcessor); + + if (nettyConfig.getWsAdditionalChannelInitializer() != null) { + nettyConfig.getWsAdditionalChannelInitializer().initChannel(ch); + } + } + }); + + secureBootstrap.handler(new ChannelInitializer() { + + @Override + protected void initChannel(Channel ch) throws Exception { + + ChannelPipeline pipeline = ch.pipeline()// + .addLast(SSL_HANDLER, new SslInitializer(ChannelManager.this)).addLast(HTTP_HANDLER, newHttpClientCodec()); + + if (config.isCompressionEnabled()) { + pipeline.addLast(INFLATER_HANDLER, new HttpContentDecompressor()); + } + pipeline.addLast(CHUNKED_WRITER_HANDLER, new ChunkedWriteHandler())// + .addLast(HTTP_PROCESSOR, httpProcessor); + + if (nettyConfig.getHttpsAdditionalChannelInitializer() != null) { + nettyConfig.getHttpsAdditionalChannelInitializer().initChannel(ch); + } + } + }); + + secureWebSocketBootstrap.handler(new ChannelInitializer() { + + @Override + protected void initChannel(Channel ch) throws Exception { + ch.pipeline()// + .addLast(SSL_HANDLER, new SslInitializer(ChannelManager.this))// + .addLast(HTTP_HANDLER, newHttpClientCodec())// + .addLast(WS_PROCESSOR, wsProcessor); + + if (nettyConfig.getWssAdditionalChannelInitializer() != null) { + nettyConfig.getWssAdditionalChannelInitializer().initChannel(ch); + } + } + }); } public final void tryToOfferChannelToPool(Channel channel, boolean keepAlive, String poolKey) { @@ -110,26 +272,26 @@ private Semaphore getFreeConnectionsForHost(String poolKey) { Semaphore freeConnections = freeChannelsPerHost.get(poolKey); if (freeConnections == null) { // lazy create the semaphore - Semaphore newFreeConnections = new Semaphore(maxConnectionsPerHost); + Semaphore newFreeConnections = new Semaphore(config.getMaxConnectionsPerHost()); freeConnections = freeChannelsPerHost.putIfAbsent(poolKey, newFreeConnections); if (freeConnections == null) freeConnections = newFreeConnections; } return freeConnections; } - + private boolean tryAcquirePerHost(String poolKey) { return !maxConnectionsPerHostEnabled || getFreeConnectionsForHost(poolKey).tryAcquire(); } - + public boolean preemptChannel(String poolKey) { return channelPool.isOpen() && tryAcquireGlobal() && tryAcquirePerHost(poolKey); } - public void destroy() { + public void close() { channelPool.destroy(); openChannels.close(); - + for (Channel channel : openChannels) { Object attachment = Channels.getDefaultAttribute(channel); if (attachment instanceof NettyResponseFuture) { @@ -137,13 +299,17 @@ public void destroy() { future.cancelTimeouts(); } } + + if (allowReleaseEventLoopGroup) + eventLoopGroup.shutdownGracefully(); } public void closeChannel(Channel channel) { removeAll(channel); Channels.setDiscard(channel); - // The channel may have already been removed if a timeout occurred, and this method may be called just after. + // The channel may have already been removed if a timeout occurred, and + // this method may be called just after. if (channel != null) { LOGGER.debug("Closing Channel {} ", channel); try { @@ -165,4 +331,96 @@ public void abortChannelPreemption(String poolKey) { public void registerOpenChannel(Channel channel) { openChannels.add(channel); } -} \ No newline at end of file + + private HttpClientCodec newHttpClientCodec() { + return new HttpClientCodec(// + nettyConfig.getHttpClientCodecMaxInitialLineLength(),// + nettyConfig.getHttpClientCodecMaxHeaderSize(),// + nettyConfig.getHttpClientCodecMaxChunkSize(),// + false); + } + + public SslHandler createSslHandler(String peerHost, int peerPort) throws IOException, GeneralSecurityException { + + SSLEngine sslEngine = null; + if (nettyConfig.getSslEngineFactory() != null) { + sslEngine = nettyConfig.getSslEngineFactory().newSSLEngine(); + + } else { + SSLContext sslContext = config.getSSLContext(); + if (sslContext == null) + sslContext = SslUtils.getInstance().getSSLContext(config.isAcceptAnyCertificate()); + + sslEngine = sslContext.createSSLEngine(peerHost, peerPort); + sslEngine.setUseClientMode(true); + } + + SslHandler sslHandler = new SslHandler(sslEngine); + if (handshakeTimeoutInMillis > 0) + sslHandler.setHandshakeTimeoutMillis(handshakeTimeoutInMillis); + + return sslHandler; + } + + public void upgradeProtocol(ChannelPipeline pipeline, String scheme, String host, int port) throws IOException, GeneralSecurityException { + if (pipeline.get(HTTP_HANDLER) != null) + pipeline.remove(HTTP_HANDLER); + + if (isSecure(scheme)) + if (pipeline.get(SSL_HANDLER) == null) { + pipeline.addFirst(HTTP_HANDLER, newHttpClientCodec()); + pipeline.addFirst(SSL_HANDLER, createSslHandler(host, port)); + } else { + pipeline.addAfter(SSL_HANDLER, HTTP_HANDLER, newHttpClientCodec()); + } + + else + pipeline.addFirst(HTTP_HANDLER, newHttpClientCodec()); + + if (isWebSocket(scheme)) + pipeline.replace(HTTP_PROCESSOR, WS_PROCESSOR, wsProcessor); + } + + public String getPoolKey(NettyResponseFuture future) { + return future.getConnectionPoolKeyStrategy().getKey(future.getURI(), future.getProxyServer()); + } + + /** + * 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 requires upgrading from http to https. + */ + public void verifyChannelPipeline(ChannelPipeline pipeline, String scheme) throws IOException, GeneralSecurityException { + + boolean isSecure = isSecure(scheme); + if (pipeline.get(SSL_HANDLER) != null) { + if (!isSecure) + pipeline.remove(SSL_HANDLER); + + } else if (isSecure) + pipeline.addFirst(SSL_HANDLER, new SslInitializer(this)); + } + + public Bootstrap getBootstrap(UriComponents uri, boolean useProxy, boolean useSSl) { + return uri.getScheme().startsWith(WEBSOCKET) && !useProxy ? (useSSl ? secureWebSocketBootstrap : webSocketBootstrap) : // + (useSSl ? secureBootstrap : plainBootstrap); + } + + public void upgradePipelineForWebSockets(ChannelPipeline pipeline) { + pipeline.replace(HTTP_HANDLER, WS_ENCODER_HANDLER, new WebSocket08FrameEncoder(true)); + pipeline.addBefore(WS_PROCESSOR, WS_DECODER_HANDLER, new WebSocket08FrameDecoder(false, false, 10 * 1024)); + } + + public final Callback newDrainCallback(final NettyResponseFuture future, final Channel channel, final boolean keepAlive, final String poolKey) { + + return new Callback(future) { + public void call() throws Exception { + tryToOfferChannelToPool(channel, keepAlive, poolKey); + } + }; + } + + public void drainChannel(final Channel channel, final NettyResponseFuture future) { + Channels.setDefaultAttribute(channel, newDrainCallback(future, channel, future.isKeepAlive(), getPoolKey(future))); + } +} 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 old mode 100644 new mode 100755 index 304d2d7931..eda10d57d9 --- 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 @@ -1,409 +1,29 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.channel; -import static org.asynchttpclient.providers.netty.handler.Processor.newHttpProcessor; -import static org.asynchttpclient.providers.netty.handler.Processor.newWsProcessor; -import static org.asynchttpclient.providers.netty.util.HttpUtil.WEBSOCKET; -import static org.asynchttpclient.providers.netty.util.HttpUtil.isSecure; -import static org.asynchttpclient.providers.netty.util.HttpUtil.isWebSocket; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.ConnectionPoolKeyStrategy; -import org.asynchttpclient.ProxyServer; -import org.asynchttpclient.providers.netty.DiscardEvent; -import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; -import org.asynchttpclient.providers.netty.channel.pool.ChannelPool; -import org.asynchttpclient.providers.netty.channel.pool.DefaultChannelPool; -import org.asynchttpclient.providers.netty.channel.pool.NoopChannelPool; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.handler.Processor; -import org.asynchttpclient.providers.netty.request.NettyRequestSender; -import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.util.SslUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.handler.codec.http.HttpClientCodec; -import io.netty.handler.codec.http.HttpContentDecompressor; -import io.netty.handler.codec.http.websocketx.WebSocket08FrameDecoder; -import io.netty.handler.codec.http.websocketx.WebSocket08FrameEncoder; import io.netty.handler.ssl.SslHandler; -import io.netty.handler.stream.ChunkedWriteHandler; import io.netty.util.Attribute; import io.netty.util.AttributeKey; -import io.netty.util.HashedWheelTimer; -import io.netty.util.Timeout; -import io.netty.util.Timer; -import io.netty.util.TimerTask; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.util.Map.Entry; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; +import org.asynchttpclient.providers.netty.DiscardEvent; public class Channels { - private static final Logger LOGGER = LoggerFactory.getLogger(Channels.class); - public static final String HTTP_HANDLER = "httpHandler"; - public static final String SSL_HANDLER = "sslHandler"; - public static final String HTTP_PROCESSOR = "httpProcessor"; - public static final String WS_PROCESSOR = "wsProcessor"; - public static final String DEFLATER_HANDLER = "deflater"; - public static final String INFLATER_HANDLER = "inflater"; - public static final String CHUNKED_WRITER_HANDLER = "chunkedWriter"; - public static final String WS_DECODER_HANDLER = "ws-decoder"; - public static final String WS_ENCODER_HANDLER = "ws-encoder"; - private static final AttributeKey DEFAULT_ATTRIBUTE = AttributeKey.valueOf("default"); - private final AsyncHttpClientConfig config; - private final NettyAsyncHttpProviderConfig nettyProviderConfig; - - private final EventLoopGroup eventLoopGroup; - private final boolean allowReleaseEventLoopGroup; - - private final Bootstrap plainBootstrap; - private final Bootstrap secureBootstrap; - private final Bootstrap webSocketBootstrap; - private final Bootstrap secureWebSocketBootstrap; - - public final ChannelManager channelManager; - private final boolean allowStopNettyTimer; - private final Timer nettyTimer; - private final long handshakeTimeoutInMillis; - - private Processor wsProcessor; - - public Channels(final AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyProviderConfig) { - - this.config = config; - this.nettyProviderConfig = nettyProviderConfig; - - // check if external EventLoopGroup is defined - allowReleaseEventLoopGroup = nettyProviderConfig.getEventLoopGroup() == null; - eventLoopGroup = allowReleaseEventLoopGroup ? new NioEventLoopGroup() : nettyProviderConfig.getEventLoopGroup(); - - // check if external HashedWheelTimer is defined - allowStopNettyTimer = nettyProviderConfig.getNettyTimer() == null; - nettyTimer = allowStopNettyTimer ? newNettyTimer() : nettyProviderConfig.getNettyTimer(); - handshakeTimeoutInMillis = nettyProviderConfig.getHandshakeTimeoutInMillis(); - - if (!(eventLoopGroup instanceof NioEventLoopGroup)) - throw new IllegalArgumentException("Only Nio is supported"); - - 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); - - ChannelPool cp = nettyProviderConfig.getChannelPool(); - if (cp == null) { - if (config.isAllowPoolingConnections()) { - cp = new DefaultChannelPool(config, nettyTimer); - } else { - cp = new NoopChannelPool(); - } - } - this.channelManager = new ChannelManager(config, cp); - - for (Entry, Object> entry : nettyProviderConfig.propertiesSet()) { - ChannelOption key = entry.getKey(); - Object value = entry.getValue(); - plainBootstrap.option(key, value); - webSocketBootstrap.option(key, value); - secureBootstrap.option(key, value); - secureWebSocketBootstrap.option(key, value); - } - - int timeOut = config.getConnectionTimeout() > 0 ? config.getConnectionTimeout() : 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); - } - - private Timer newNettyTimer() { - HashedWheelTimer nettyTimer = new HashedWheelTimer(); - nettyTimer.start(); - return nettyTimer; - } - - public SslHandler createSslHandler(String peerHost, int peerPort) throws IOException, GeneralSecurityException { - - SSLEngine sslEngine = null; - if (nettyProviderConfig.getSslEngineFactory() != null) { - sslEngine = nettyProviderConfig.getSslEngineFactory().newSSLEngine(); - - } else { - SSLContext sslContext = config.getSSLContext(); - if (sslContext == null) - sslContext = SslUtils.getInstance().getSSLContext(config.isAcceptAnyCertificate()); - - sslEngine = sslContext.createSSLEngine(peerHost, peerPort); - sslEngine.setUseClientMode(true); - } - - SslHandler sslHandler = new SslHandler(sslEngine); - if (handshakeTimeoutInMillis > 0) - sslHandler.setHandshakeTimeoutMillis(handshakeTimeoutInMillis); - - return sslHandler; - } - - public void configureProcessor(NettyRequestSender requestSender, AtomicBoolean closed) { - - final Processor httpProcessor = newHttpProcessor(config, nettyProviderConfig, requestSender, this, closed); - wsProcessor = newWsProcessor(config, nettyProviderConfig, requestSender, this, closed); - - plainBootstrap.handler(new ChannelInitializer() { - @Override - protected void initChannel(Channel ch) throws Exception { - ChannelPipeline pipeline = ch.pipeline().addLast(HTTP_HANDLER, newHttpClientCodec()); - - if (config.isCompressionEnabled()) { - pipeline.addLast(INFLATER_HANDLER, new HttpContentDecompressor()); - } - pipeline.addLast(CHUNKED_WRITER_HANDLER, new ChunkedWriteHandler())// - .addLast(HTTP_PROCESSOR, httpProcessor); - - if (nettyProviderConfig.getHttpAdditionalChannelInitializer() != null) { - nettyProviderConfig.getHttpAdditionalChannelInitializer().initChannel(ch); - } - } - }); - - webSocketBootstrap.handler(new ChannelInitializer() { - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline()// - .addLast(HTTP_HANDLER, newHttpClientCodec())// - .addLast(WS_PROCESSOR, wsProcessor); - - if (nettyProviderConfig.getWsAdditionalChannelInitializer() != null) { - nettyProviderConfig.getWsAdditionalChannelInitializer().initChannel(ch); - } - } - }); - - secureBootstrap.handler(new ChannelInitializer() { - - @Override - protected void initChannel(Channel ch) throws Exception { - - ChannelPipeline pipeline = ch.pipeline()// - .addLast(SSL_HANDLER, new SslInitializer(Channels.this)).addLast(HTTP_HANDLER, newHttpClientCodec()); - - if (config.isCompressionEnabled()) { - pipeline.addLast(INFLATER_HANDLER, new HttpContentDecompressor()); - } - pipeline.addLast(CHUNKED_WRITER_HANDLER, new ChunkedWriteHandler())// - .addLast(HTTP_PROCESSOR, httpProcessor); - - if (nettyProviderConfig.getHttpsAdditionalChannelInitializer() != null) { - nettyProviderConfig.getHttpsAdditionalChannelInitializer().initChannel(ch); - } - } - }); - - secureWebSocketBootstrap.handler(new ChannelInitializer() { - - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline()// - .addLast(SSL_HANDLER, new SslInitializer(Channels.this))// - .addLast(HTTP_HANDLER, newHttpClientCodec())// - .addLast(WS_PROCESSOR, wsProcessor); - - if (nettyProviderConfig.getWssAdditionalChannelInitializer() != null) { - nettyProviderConfig.getWssAdditionalChannelInitializer().initChannel(ch); - } - } - }); - } - - public Bootstrap getBootstrap(UriComponents uri, boolean useSSl, boolean useProxy) { - return (uri.getScheme().startsWith(WEBSOCKET) && !useProxy) ? (useSSl ? secureWebSocketBootstrap : webSocketBootstrap) - : (useSSl ? secureBootstrap : plainBootstrap); - } - - public void close() { - channelManager.destroy(); - - if (allowReleaseEventLoopGroup) - eventLoopGroup.shutdownGracefully(); - - if (allowStopNettyTimer) - nettyTimer.stop(); - } - - /** - * 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 requires upgrading from http to https. - */ - public void verifyChannelPipeline(ChannelPipeline pipeline, String scheme) throws IOException, GeneralSecurityException { - - boolean isSecure = isSecure(scheme); - if (pipeline.get(SSL_HANDLER) != null) { - if (!isSecure) - pipeline.remove(SSL_HANDLER); - - } else if (isSecure) - pipeline.addFirst(SSL_HANDLER, new SslInitializer(Channels.this)); - } - - protected HttpClientCodec newHttpClientCodec() { - if (nettyProviderConfig != null) { - return new HttpClientCodec(// - nettyProviderConfig.getMaxInitialLineLength(),// - nettyProviderConfig.getMaxHeaderSize(),// - nettyProviderConfig.getMaxChunkSize(),// - false); - - } else { - return new HttpClientCodec(); - } - } - - public void upgradeProtocol(ChannelPipeline p, String scheme, String host, int port) 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, createSslHandler(host, port)); - } else { - p.addAfter(SSL_HANDLER, HTTP_HANDLER, newHttpClientCodec()); - } - - } else { - p.addFirst(HTTP_HANDLER, newHttpClientCodec()); - } - - if (isWebSocket(scheme)) { - p.replace(HTTP_PROCESSOR, WS_PROCESSOR, wsProcessor); - } - } - - public static void upgradePipelineForWebSockets(Channel channel) { - channel.pipeline().replace(HTTP_HANDLER, WS_ENCODER_HANDLER, new WebSocket08FrameEncoder(true)); - channel.pipeline().addBefore(WS_PROCESSOR, WS_DECODER_HANDLER, new WebSocket08FrameDecoder(false, false, 10 * 1024)); - } - - public Channel pollAndVerifyCachedChannel(UriComponents uri, ProxyServer proxy, ConnectionPoolKeyStrategy connectionPoolKeyStrategy) { - final Channel channel = channelManager.poll(connectionPoolKeyStrategy.getKey(uri, proxy)); - - if (channel != null) { - LOGGER.debug("Using cached Channel {}\n for uri {}\n", channel, uri); - - try { - verifyChannelPipeline(channel.pipeline(), uri.getScheme()); - } catch (Exception ex) { - LOGGER.debug(ex.getMessage(), ex); - } - } - return channel; - } - - public boolean preemptChannel(AsyncHandler asyncHandler, String poolKey) throws IOException { - - boolean channelPreempted = false; - if (channelManager.preemptChannel(poolKey)) { - channelPreempted = true; - } else { - IOException ex = new IOException(String.format("Too many connections %s", config.getMaxConnections())); - try { - asyncHandler.onThrowable(ex); - } catch (Exception e) { - LOGGER.warn("asyncHandler.onThrowable crashed", e); - } - throw ex; - } - return channelPreempted; - } - - public void tryToOfferChannelToPool(Channel channel, boolean keepAlive, String poolKey) { - channelManager.tryToOfferChannelToPool(channel, keepAlive, poolKey); - } - - public void abortChannelPreemption(String poolKey) { - channelManager.abortChannelPreemption(poolKey); - } - - public void closeChannel(Channel channel) { - channelManager.closeChannel(channel); - } - - public final Callable> newDrainCallable(final NettyResponseFuture future, final Channel channel, - final boolean keepAlive, final String poolKey) { - - return new Callable>() { - public NettyResponseFuture call() throws Exception { - channelManager.tryToOfferChannelToPool(channel, keepAlive, poolKey); - return null; - } - }; - } - - public void drainChannel(final Channel channel, final NettyResponseFuture future) { - setDefaultAttribute(channel, newDrainCallable(future, channel, future.isKeepAlive(), getPoolKey(future))); - } - - public String getPoolKey(NettyResponseFuture future) { - return future.getConnectionPoolKeyStrategy().getKey(future.getURI(), future.getProxyServer()); - } - - public void removeAll(Channel channel) { - channelManager.removeAll(channel); - } - - public void abort(NettyResponseFuture future, Throwable t) { - - Channel channel = future.channel(); - if (channel != null) - channelManager.closeChannel(channel); - - if (!future.isDone()) { - LOGGER.debug("Aborting Future {}\n", future); - LOGGER.debug(t.getMessage(), t); - } - - future.abort(t); - } - - public Timeout newTimeoutInMs(TimerTask task, long delayInMs) { - return nettyTimer.newTimeout(task, delayInMs, TimeUnit.MILLISECONDS); - } - public static SslHandler getSslHandler(Channel channel) { return channel.pipeline().get(SslHandler.class); } @@ -420,8 +40,8 @@ public static void setDefaultAttribute(Channel channel, Object o) { public static void setDiscard(Channel channel) { setDefaultAttribute(channel, DiscardEvent.INSTANCE); } - - public void registerOpenChannel(Channel channel) { - channelManager.registerOpenChannel(channel); + + public static boolean isChannelValid(Channel channel) { + return channel != null && channel.isOpen() && channel.isActive(); } } 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/channel/CleanupChannelGroup.java old mode 100644 new mode 100755 similarity index 98% rename from providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/CleanupChannelGroup.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/CleanupChannelGroup.java index 853ea319e9..edd0c2643a --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/CleanupChannelGroup.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/CleanupChannelGroup.java @@ -25,7 +25,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.asynchttpclient.providers.netty.util; +package org.asynchttpclient.providers.netty.channel; import io.netty.channel.Channel; import io.netty.channel.group.ChannelGroupFuture; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java old mode 100644 new mode 100755 index e0df603cd0..31819ee86b --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java @@ -30,10 +30,10 @@ */ public class SslInitializer extends ChannelOutboundHandlerAdapter { - private final Channels channels; + private final ChannelManager channelManager; - public SslInitializer(Channels channels) { - this.channels = channels; + public SslInitializer(ChannelManager channelManager) { + this.channelManager = channelManager; } @Override @@ -44,9 +44,9 @@ public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, Sock String peerHost = remoteInetSocketAddress.getHostName(); int peerPort = remoteInetSocketAddress.getPort(); - SslHandler sslHandler = channels.createSslHandler(peerHost, peerPort); + SslHandler sslHandler = channelManager.createSslHandler(peerHost, peerPort); - ctx.pipeline().replace(Channels.SSL_HANDLER, Channels.SSL_HANDLER, sslHandler); + ctx.pipeline().replace(ChannelManager.SSL_HANDLER, ChannelManager.SSL_HANDLER, sslHandler); ctx.connect(remoteAddress, localAddress, promise); } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/ChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/ChannelPool.java old mode 100644 new mode 100755 index 4f1d96ba16..ef1fbd9973 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/ChannelPool.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/ChannelPool.java @@ -1,18 +1,15 @@ /* - * Copyright 2010 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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. + * 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.channel.pool; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java old mode 100644 new mode 100755 diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/NoopChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/NoopChannelPool.java old mode 100644 new mode 100755 index f8d684ad84..ad7fe1ccb3 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/NoopChannelPool.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/NoopChannelPool.java @@ -1,17 +1,15 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.channel.pool; 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 9d2a692f77..ab5a1f04e9 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 @@ -1,35 +1,19 @@ /* - * Copyright 2010 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.future; import static org.asynchttpclient.util.DateUtils.millisTime; - -import org.asynchttpclient.AsyncHandler; -import org.asynchttpclient.ConnectionPoolKeyStrategy; -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.asynchttpclient.providers.netty.request.NettyRequest; -import org.asynchttpclient.providers.netty.request.timeout.TimeoutsHolder; -import org.asynchttpclient.uri.UriComponents; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import io.netty.channel.Channel; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpResponse; @@ -46,6 +30,18 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.ConnectionPoolKeyStrategy; +import org.asynchttpclient.ProxyServer; +import org.asynchttpclient.Request; +import org.asynchttpclient.listenable.AbstractListenableFuture; +import org.asynchttpclient.providers.netty.channel.Channels; +import org.asynchttpclient.providers.netty.request.NettyRequest; +import org.asynchttpclient.providers.netty.request.timeout.TimeoutsHolder; +import org.asynchttpclient.uri.UriComponents; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * A {@link Future} that can be used to track when an asynchronous HTTP request has been fully processed. * @@ -137,7 +133,7 @@ public boolean cancel(boolean force) { return false; try { - Channels.setDefaultAttribute(channel, DiscardEvent.INSTANCE); + Channels.setDiscard(channel); channel.close(); } catch (Throwable t) { // Ignore @@ -452,8 +448,8 @@ public void setRequest(Request request) { * @return true if that {@link Future} cannot be recovered. */ public boolean canBeReplayed() { - return !isDone() && canRetry() && !isCancelled() - && !(channel != null && channel.isOpen() && !uri.getScheme().equalsIgnoreCase("https")) && !isInAuth(); + return !isDone() && canRetry() + && !(Channels.isChannelValid(channel) && !uri.getScheme().equalsIgnoreCase("https")) && !isInAuth(); } public long getStart() { @@ -474,7 +470,7 @@ public String toString() { ",\n\thttpHeaders=" + httpHeaders + // ",\n\texEx=" + exEx + // ",\n\tredirectCount=" + redirectCount + // - ",\n\timeoutsHolder=" + timeoutsHolder + // + ",\n\ttimeoutsHolder=" + timeoutsHolder + // ",\n\tinAuth=" + inAuth + // ",\n\tstatusReceived=" + statusReceived + // ",\n\ttouch=" + touch + // diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/future/StackTraceInspector.java old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 index 377dd130cd..380121278c --- 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 @@ -1,17 +1,15 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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; @@ -19,16 +17,25 @@ import static io.netty.handler.codec.http.HttpResponseStatus.OK; import static io.netty.handler.codec.http.HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED; import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED; -import static org.asynchttpclient.providers.netty.util.HttpUtil.isNTLM; +import static org.asynchttpclient.providers.netty.util.HttpUtils.isNTLM; +import static org.asynchttpclient.util.AsyncHttpProviderUtils.getDefaultPort; import static org.asynchttpclient.util.MiscUtils.isNonEmpty; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +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.util.List; import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHandler.STATE; 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; @@ -37,6 +44,7 @@ import org.asynchttpclient.ntlm.NTLMEngineException; import org.asynchttpclient.providers.netty.Callback; import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; +import org.asynchttpclient.providers.netty.channel.ChannelManager; import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.request.NettyRequest; @@ -46,37 +54,19 @@ import org.asynchttpclient.providers.netty.response.ResponseStatus; import org.asynchttpclient.spnego.SpnegoEngine; import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; -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.util.List; - -final class HttpProtocol extends Protocol { +public final class HttpProtocol extends Protocol { - private static final Logger LOGGER = LoggerFactory.getLogger(HttpProtocol.class); - - public HttpProtocol(Channels channels, AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, - NettyRequestSender requestSender) { - super(channels, config, nettyConfig, requestSender); + public HttpProtocol(ChannelManager channelManager, AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, NettyRequestSender requestSender) { + super(channelManager, config, nettyConfig, requestSender); } private Realm.RealmBuilder newRealmBuilder(Realm realm) { return realm != null ? new Realm.RealmBuilder().clone(realm) : new Realm.RealmBuilder(); } - private Realm kerberosChallenge(List proxyAuth, Request request, ProxyServer proxyServer, - FluentCaseInsensitiveStringsMap headers, Realm realm, NettyResponseFuture future, boolean proxyInd) throws NTLMEngineException { + private Realm kerberosChallenge(List proxyAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, + NettyResponseFuture future, boolean proxyInd) throws NTLMEngineException { UriComponents uri = request.getURI(); String host = request.getVirtualHost() == null ? uri.getHost() : request.getVirtualHost(); @@ -96,21 +86,21 @@ private Realm kerberosChallenge(List proxyAuth, Request request, ProxySe if (isNTLM(proxyAuth)) { return ntlmChallenge(proxyAuth, request, proxyServer, headers, realm, future, proxyInd); } - channels.abort(future, throwable); + requestSender.abort(future, throwable); return null; } } private String authorizationHeaderName(boolean proxyInd) { - return proxyInd? HttpHeaders.Names.PROXY_AUTHORIZATION: HttpHeaders.Names.AUTHORIZATION; + return proxyInd ? HttpHeaders.Names.PROXY_AUTHORIZATION : HttpHeaders.Names.AUTHORIZATION; } - + private void addNTLMAuthorizationHeader(FluentCaseInsensitiveStringsMap headers, String challengeHeader, boolean proxyInd) { headers.add(authorizationHeaderName(proxyInd), "NTLM " + challengeHeader); } - private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, - Realm realm, NettyResponseFuture future, boolean proxyInd) throws NTLMEngineException { + private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, + NettyResponseFuture future, boolean proxyInd) throws NTLMEngineException { boolean useRealm = proxyServer == null && realm != null; @@ -143,13 +133,12 @@ private Realm ntlmChallenge(List wwwAuth, Request request, ProxyServer p } } - private Realm ntlmProxyChallenge(List wwwAuth, Request request, ProxyServer proxyServer, - FluentCaseInsensitiveStringsMap headers, Realm realm, NettyResponseFuture future, boolean proxyInd) throws NTLMEngineException { + private Realm ntlmProxyChallenge(List wwwAuth, Request request, ProxyServer proxyServer, FluentCaseInsensitiveStringsMap headers, Realm realm, + NettyResponseFuture future, boolean proxyInd) throws NTLMEngineException { future.getAndSetAuth(false); headers.remove(HttpHeaders.Names.PROXY_AUTHORIZATION); - addType3NTLMAuthorizationHeader(wwwAuth, headers, proxyServer.getPrincipal(), proxyServer.getPassword(), - proxyServer.getNtlmDomain(), proxyServer.getHost(), proxyInd); + addType3NTLMAuthorizationHeader(wwwAuth, headers, proxyServer.getPrincipal(), proxyServer.getPassword(), proxyServer.getNtlmDomain(), proxyServer.getHost(), proxyInd); return newRealmBuilder(realm)// // .setScheme(realm.getAuthScheme()) @@ -157,8 +146,8 @@ private Realm ntlmProxyChallenge(List wwwAuth, Request request, ProxySer .setMethodName(request.getMethod()).build(); } - private void addType3NTLMAuthorizationHeader(List auth, FluentCaseInsensitiveStringsMap headers, String username, - String password, String domain, String workstation, boolean proxyInd) throws NTLMEngineException { + private void addType3NTLMAuthorizationHeader(List auth, FluentCaseInsensitiveStringsMap headers, String username, String password, String domain, String workstation, + boolean proxyInd) throws NTLMEngineException { headers.remove(authorizationHeaderName(proxyInd)); if (isNonEmpty(auth) && auth.get(0).startsWith("NTLM ")) { @@ -172,19 +161,17 @@ private void finishUpdate(final NettyResponseFuture future, Channel channel, boolean keepAlive = future.isKeepAlive(); if (expectOtherChunks && keepAlive) - channels.drainChannel(channel, future); + channelManager.drainChannel(channel, future); else - channels.tryToOfferChannelToPool(channel, keepAlive, channels.getPoolKey(future)); + channelManager.tryToOfferChannelToPool(channel, keepAlive, channelManager.getPoolKey(future)); markAsDone(future, channel); } - private final boolean updateBodyAndInterrupt(NettyResponseFuture future, AsyncHandler handler, HttpResponseBodyPart bodyPart) - throws Exception { - boolean state = handler.onBodyPartReceived(bodyPart) != STATE.CONTINUE; - if (bodyPart.isUnderlyingConnectionToBeClosed()) { + private boolean updateBodyAndInterrupt(NettyResponseFuture future, AsyncHandler handler, NettyResponseBodyPart bodyPart) throws Exception { + boolean interrupt = handler.onBodyPartReceived(bodyPart) != STATE.CONTINUE; + if (bodyPart.isUnderlyingConnectionToBeClosed()) future.setKeepAlive(false); - } - return state; + return interrupt; } private void markAsDone(NettyResponseFuture future, final Channel channel) { @@ -194,33 +181,39 @@ private void markAsDone(NettyResponseFuture future, final Channel channel) { future.done(); } catch (Throwable t) { // Never propagate exception once we know we are done. - LOGGER.debug(t.getMessage(), t); + logger.debug(t.getMessage(), t); } if (!future.isKeepAlive() || !channel.isActive()) { - channels.closeChannel(channel); + channelManager.closeChannel(channel); } } - private boolean handleUnauthorizedAndExit(int statusCode, Realm realm, final Request request, HttpResponse response, - final NettyResponseFuture future, ProxyServer proxyServer, final Channel channel) throws Exception { - if (statusCode == UNAUTHORIZED.code() && realm != null) { + private boolean exitAfterHandling401(// + final Channel channel,// + final NettyResponseFuture future,// + HttpResponse response,// + final Request request,// + int statusCode,// + Realm realm,// + ProxyServer proxyServer) throws Exception { + + if (statusCode == UNAUTHORIZED.code() && realm != null && !future.getAndSetAuth(true)) { - List authenticateHeaders = response.headers().getAll(HttpHeaders.Names.WWW_AUTHENTICATE); + List wwwAuthHeaders = response.headers().getAll(HttpHeaders.Names.WWW_AUTHENTICATE); - if (!authenticateHeaders.isEmpty() && !future.getAndSetAuth(true)) { + if (!wwwAuthHeaders.isEmpty()) { future.setState(NettyResponseFuture.STATE.NEW); Realm newRealm = null; - // NTLM - boolean negociate = authenticateHeaders.contains("Negotiate"); - if (!authenticateHeaders.contains("Kerberos") && (isNTLM(authenticateHeaders) || negociate)) { - newRealm = ntlmChallenge(authenticateHeaders, request, proxyServer, request.getHeaders(), realm, future, false); - // SPNEGO KERBEROS + boolean negociate = wwwAuthHeaders.contains("Negotiate"); + if (!wwwAuthHeaders.contains("Kerberos") && (isNTLM(wwwAuthHeaders) || negociate)) { + // NTLM + newRealm = ntlmChallenge(wwwAuthHeaders, request, proxyServer, request.getHeaders(), realm, future, false); } else if (negociate) { - newRealm = kerberosChallenge(authenticateHeaders, request, proxyServer, request.getHeaders(), realm, future, false); - if (newRealm == null) { + newRealm = kerberosChallenge(wwwAuthHeaders, request, proxyServer, request.getHeaders(), realm, future, false); + // SPNEGO KERBEROS + if (newRealm == null) return true; - } } else { newRealm = new Realm.RealmBuilder()// .clone(realm)// @@ -228,28 +221,27 @@ private boolean handleUnauthorizedAndExit(int statusCode, Realm realm, final Req .setUri(request.getURI())// .setMethodName(request.getMethod())// .setUsePreemptiveAuth(true)// - .parseWWWAuthenticateHeader(authenticateHeaders.get(0))// + .parseWWWAuthenticateHeader(wwwAuthHeaders.get(0))// .build(); } Realm nr = newRealm; final Request nextRequest = new RequestBuilder(future.getRequest()).setHeaders(request.getHeaders()).setRealm(nr).build(); - LOGGER.debug("Sending authentication to {}", request.getURI()); + logger.debug("Sending authentication to {}", request.getURI()); Callback callback = new Callback(future) { public void call() throws Exception { - channels.drainChannel(channel, future); + channelManager.drainChannel(channel, future); requestSender.sendNextRequest(nextRequest, future); } }; - if (future.isKeepAlive() && HttpHeaders.isTransferEncodingChunked(response)) { + if (future.isKeepAlive() && HttpHeaders.isTransferEncodingChunked(response)) // We must make sure there is no bytes left // before executing the next request. Channels.setDefaultAttribute(channel, callback); - } else { + else callback.call(); - } return true; } @@ -258,7 +250,7 @@ public void call() throws Exception { return false; } - private boolean handleContinueAndExit(final Channel channel, final NettyResponseFuture future, int statusCode) { + private boolean exitAfterHandling100(final Channel channel, final NettyResponseFuture future, int statusCode) { if (statusCode == CONTINUE.code()) { future.setHeadersAlreadyWrittenOnContinue(true); future.setDontWriteBodyBecauseExpectContinue(false); @@ -270,27 +262,31 @@ private boolean handleContinueAndExit(final Channel channel, final NettyResponse return false; } - private boolean handleProxyAuthenticationRequiredAndExit(int statusCode,// - Realm realm,// - final Request request,// + private boolean exitAfterHandling407(// + NettyResponseFuture future,// HttpResponse response,// - final NettyResponseFuture future,// + Request request,// + int statusCode,// + Realm realm,// ProxyServer proxyServer) throws Exception { - if (statusCode == PROXY_AUTHENTICATION_REQUIRED.code() && realm != null) { - List proxyAuthenticateHeaders = response.headers().getAll(HttpHeaders.Names.PROXY_AUTHENTICATE); - if (!proxyAuthenticateHeaders.isEmpty() && !future.getAndSetAuth(true)) { - LOGGER.debug("Sending proxy authentication to {}", request.getURI()); + if (statusCode == PROXY_AUTHENTICATION_REQUIRED.code() && realm != null && !future.getAndSetAuth(true)) { + + List proxyAuthHeaders = response.headers().getAll(HttpHeaders.Names.PROXY_AUTHENTICATE); + + if (!proxyAuthHeaders.isEmpty()) { + logger.debug("Sending proxy authentication to {}", request.getURI()); future.setState(NettyResponseFuture.STATE.NEW); Realm newRealm = null; + FluentCaseInsensitiveStringsMap requestHeaders = request.getHeaders(); - boolean negociate = proxyAuthenticateHeaders.contains("Negotiate"); - if (!proxyAuthenticateHeaders.contains("Kerberos") && (isNTLM(proxyAuthenticateHeaders) || negociate)) { - newRealm = ntlmProxyChallenge(proxyAuthenticateHeaders, request, proxyServer, request.getHeaders(), realm, future, true); + boolean negociate = proxyAuthHeaders.contains("Negotiate"); + if (!proxyAuthHeaders.contains("Kerberos") && (isNTLM(proxyAuthHeaders) || negociate)) { + newRealm = ntlmProxyChallenge(proxyAuthHeaders, request, proxyServer, requestHeaders, realm, future, true); // SPNEGO KERBEROS } else if (negociate) { - newRealm = kerberosChallenge(proxyAuthenticateHeaders, request, proxyServer, request.getHeaders(), realm, future, true); + newRealm = kerberosChallenge(proxyAuthHeaders, request, proxyServer, requestHeaders, realm, future, true); if (newRealm == null) return true; } else { @@ -300,13 +296,13 @@ private boolean handleProxyAuthenticationRequiredAndExit(int statusCode,// .setOmitQuery(true)// .setMethodName(HttpMethod.CONNECT.name())// .setUsePreemptiveAuth(true)// - .parseProxyAuthenticateHeader(proxyAuthenticateHeaders.get(0))// + .parseProxyAuthenticateHeader(proxyAuthHeaders.get(0))// .build(); } future.setReuseChannel(true); future.setConnectAllowed(true); - Request nextRequest = new RequestBuilder(future.getRequest()).setHeaders(request.getHeaders()).setRealm(newRealm).build(); + Request nextRequest = new RequestBuilder(future.getRequest()).setHeaders(requestHeaders).setRealm(newRealm).build(); requestSender.sendNextRequest(nextRequest, future); return true; } @@ -314,27 +310,32 @@ private boolean handleProxyAuthenticationRequiredAndExit(int statusCode,// return false; } - private boolean handleConnectOKAndExit(int statusCode, Realm realm, final Request request, HttpRequest httpRequest, - HttpResponse response, final NettyResponseFuture future, ProxyServer proxyServer, final Channel channel) throws IOException { - if (statusCode == OK.code() && httpRequest.getMethod() == HttpMethod.CONNECT) { + private boolean exitAfterHandlingConnect(// + final Channel channel,// + final NettyResponseFuture future,// + final Request request,// + ProxyServer proxyServer,// + int statusCode,// + HttpRequest httpRequest) throws IOException { - LOGGER.debug("Connected to {}:{}", proxyServer.getHost(), proxyServer.getPort()); + if (statusCode == OK.code() && httpRequest.getMethod() == HttpMethod.CONNECT) { - if (future.isKeepAlive()) { + if (future.isKeepAlive()) future.attachChannel(channel, true); - } try { UriComponents requestURI = request.getURI(); String scheme = requestURI.getScheme(); - LOGGER.debug("Connecting to proxy {} for scheme {}", proxyServer, scheme); String host = requestURI.getHost(); - int port = AsyncHttpProviderUtils.getDefaultPort(requestURI); - - channels.upgradeProtocol(channel.pipeline(), scheme, host, port); + int port = getDefaultPort(requestURI); + + logger.debug("Connecting to proxy {} for scheme {}", proxyServer, scheme); + channelManager.upgradeProtocol(channel.pipeline(), scheme, host, port); + } catch (Throwable ex) { - channels.abort(future, ex); + requestSender.abort(future, ex); } + future.setReuseChannel(true); future.setConnectAllowed(false); requestSender.sendNextRequest(new RequestBuilder(future.getRequest()).build(), future); @@ -344,18 +345,29 @@ private boolean handleConnectOKAndExit(int statusCode, Realm realm, final Reques return false; } - private boolean handleHanderAndExit(Channel channel, NettyResponseFuture future, AsyncHandler handler, HttpResponseStatus status, - HttpResponseHeaders responseHeaders, HttpResponse response) throws Exception { - if (!future.getAndSetStatusReceived(true) - && (handler.onStatusReceived(status) != STATE.CONTINUE || handler.onHeadersReceived(responseHeaders) != STATE.CONTINUE)) { + private boolean exitAfterHandlingStatus(Channel channel, NettyResponseFuture future, HttpResponse response, AsyncHandler handler, ResponseStatus status) + throws IOException, Exception { + if (!future.getAndSetStatusReceived(true) && handler.onStatusReceived(status) != STATE.CONTINUE) { finishUpdate(future, channel, HttpHeaders.isTransferEncodingChunked(response)); return true; } return false; } - private boolean handleResponseAndExit(final Channel channel, final NettyResponseFuture future, AsyncHandler handler, - HttpRequest httpRequest, ProxyServer proxyServer, HttpResponse response) throws Exception { + private boolean exitAfterHandlingHeaders(Channel channel, NettyResponseFuture future, HttpResponse response, AsyncHandler handler, ResponseHeaders responseHeaders) + throws IOException, Exception { + if (!response.headers().isEmpty() && handler.onHeadersReceived(responseHeaders) != STATE.CONTINUE) { + finishUpdate(future, channel, HttpHeaders.isTransferEncodingChunked(response)); + return true; + } + return false; + } + + private boolean handleHttpResponse(final HttpResponse response, final Channel channel, final NettyResponseFuture future, AsyncHandler handler) throws Exception { + + HttpRequest httpRequest = future.getNettyRequest().getHttpRequest(); + ProxyServer proxyServer = future.getProxyServer(); + logger.debug("\n\nRequest {}\n\nResponse {}\n", httpRequest, response); // store the original headers so we can re-send all them to // the handler in case of trailing headers @@ -363,19 +375,20 @@ private boolean handleResponseAndExit(final Channel channel, final NettyResponse future.setKeepAlive(!HttpHeaders.Values.CLOSE.equalsIgnoreCase(response.headers().get(HttpHeaders.Names.CONNECTION))); - HttpResponseStatus status = new ResponseStatus(future.getURI(), response, config); + ResponseStatus status = new ResponseStatus(future.getURI(), config, response); int statusCode = response.getStatus().code(); Request request = future.getRequest(); Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - HttpResponseHeaders responseHeaders = new ResponseHeaders(response.headers()); - - return handleResponseFiltersReplayRequestAndExit(channel, future, status, responseHeaders)// - || handleUnauthorizedAndExit(statusCode, realm, request, response, future, proxyServer, channel)// - || handleContinueAndExit(channel, future, statusCode)// - || handleProxyAuthenticationRequiredAndExit(statusCode, realm, request, response, future, proxyServer) - || handleConnectOKAndExit(statusCode, realm, request, httpRequest, response, future, proxyServer, channel)// - || handleRedirectAndExit(request, future, response, channel)// - || handleHanderAndExit(channel, future, handler, status, responseHeaders, response); + ResponseHeaders responseHeaders = new ResponseHeaders(response.headers()); + + return exitAfterProcessingFilters(channel, future, handler, status, responseHeaders) + || exitAfterHandling401(channel, future, response, request, statusCode, realm, proxyServer) || // + exitAfterHandling407(future, response, request, statusCode, realm, proxyServer) || // + exitAfterHandling100(channel, future, statusCode) || // + exitAfterHandlingRedirect(channel, future, response, request, statusCode) || // + exitAfterHandlingConnect(channel, future, request, proxyServer, statusCode, httpRequest) || // + exitAfterHandlingStatus(channel, future, response, handler, status) || // + exitAfterHandlingHeaders(channel, future, response, handler, responseHeaders); } @Override @@ -385,59 +398,54 @@ public void handle(final Channel channel, final NettyResponseFuture future, f // The connect timeout occurred. if (future.isDone()) { - channels.closeChannel(channel); + channelManager.closeChannel(channel); return; } NettyRequest nettyRequest = future.getNettyRequest(); AsyncHandler handler = future.getAsyncHandler(); - ProxyServer proxyServer = future.getProxyServer(); try { if (e instanceof HttpResponse) { HttpResponse response = (HttpResponse) e; - LOGGER.debug("\n\nRequest {}\n\nResponse {}\n", nettyRequest.getHttpRequest(), response); + logger.debug("\n\nRequest {}\n\nResponse {}\n", nettyRequest.getHttpRequest(), response); + // FIXME why do we buffer the response? I don't remember... future.setPendingResponse(response); return; - } - if (e instanceof HttpContent) { + } else if (e instanceof HttpContent) { HttpResponse response = future.getPendingResponse(); future.setPendingResponse(null); - if (handler != null) { - if (response != null - && handleResponseAndExit(channel, future, handler, nettyRequest.getHttpRequest(), proxyServer, response)) { - return; - } + if (response != null && handleHttpResponse(response, channel, future, handler)) + return; - HttpContent chunk = (HttpContent) e; + HttpContent chunk = (HttpContent) e; - boolean interrupt = false; - boolean last = chunk instanceof LastHttpContent; + boolean interrupt = false; + boolean last = chunk instanceof LastHttpContent; - if (last) { - LastHttpContent lastChunk = (LastHttpContent) chunk; - HttpHeaders trailingHeaders = lastChunk.trailingHeaders(); - if (!trailingHeaders.isEmpty()) { - ResponseHeaders responseHeaders = new ResponseHeaders(future.getHttpHeaders(), trailingHeaders); - interrupt = handler.onHeadersReceived(responseHeaders) != STATE.CONTINUE; - } - } - - ByteBuf buf = chunk.content(); - try { - if (!interrupt && (buf.readableBytes() > 0 || last)) { - NettyResponseBodyPart part = nettyConfig.getBodyPartFactory().newResponseBodyPart(buf, last); - interrupt = updateBodyAndInterrupt(future, handler, part); - } - } finally { - // FIXME we shouldn't need this, should we? But a leak was reported there without it?! - buf.release(); + // Netty 4: the last chunk is not empty + if (last) { + LastHttpContent lastChunk = (LastHttpContent) chunk; + HttpHeaders trailingHeaders = lastChunk.trailingHeaders(); + if (!trailingHeaders.isEmpty()) { + ResponseHeaders responseHeaders = new ResponseHeaders(future.getHttpHeaders(), trailingHeaders); + interrupt = handler.onHeadersReceived(responseHeaders) != STATE.CONTINUE; } + } - if (interrupt || last) { - finishUpdate(future, channel, !last); + ByteBuf buf = chunk.content(); + try { + if (!interrupt && (buf.readableBytes() > 0 || last)) { + NettyResponseBodyPart part = nettyConfig.getBodyPartFactory().newResponseBodyPart(buf, last); + interrupt = updateBodyAndInterrupt(future, handler, part); } + } finally { + // FIXME we shouldn't need this, should we? But a leak was reported there without it?! + buf.release(); } + + if (interrupt || last) + finishUpdate(future, channel, !last); } } catch (Exception t) { if (hasIOExceptionFilters// @@ -447,9 +455,9 @@ && handleResponseAndExit(channel, future, handler, nettyRequest.getHttpRequest() } try { - channels.abort(future, t); + requestSender.abort(future, t); } catch (Exception abortException) { - LOGGER.debug("Abort failed", abortException); + logger.debug("Abort failed", abortException); } finally { finishUpdate(future, channel, false); } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java old mode 100644 new mode 100755 index f1d6a1d3e0..8cbd848de6 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java @@ -1,32 +1,19 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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 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.StackTraceInspector; -import org.asynchttpclient.providers.netty.request.NettyRequestSender; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import static org.asynchttpclient.util.AsyncHttpProviderUtils.REMOTELY_CLOSED_EXCEPTION; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; @@ -36,62 +23,57 @@ 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.ChannelManager; +import org.asynchttpclient.providers.netty.channel.Channels; +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; +import org.asynchttpclient.providers.netty.future.StackTraceInspector; +import org.asynchttpclient.providers.netty.request.NettyRequestSender; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Sharable public class Processor extends ChannelInboundHandlerAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(Processor.class); + public static final IOException CHANNEL_CLOSED_EXCEPTION = new IOException("Channel Closed"); + static { + CHANNEL_CLOSED_EXCEPTION.setStackTrace(new StackTraceElement[0]); + } + private final AsyncHttpClientConfig config; + private final ChannelManager channelManager; private final NettyRequestSender requestSender; - private final Channels channels; - private final AtomicBoolean closed; private final Protocol protocol; - public static Processor newHttpProcessor(AsyncHttpClientConfig config,// - NettyAsyncHttpProviderConfig nettyConfig,// - NettyRequestSender requestSender,// - Channels channels,// - AtomicBoolean isClose) { - HttpProtocol protocol = new HttpProtocol(channels, config, nettyConfig, requestSender); - return new Processor(config, nettyConfig, requestSender, channels, isClose, protocol); - } - - public static Processor newWsProcessor(AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, - NettyRequestSender requestSender, Channels channels, AtomicBoolean isClose) { - WebSocketProtocol protocol = new WebSocketProtocol(channels, config, nettyConfig, requestSender); - return new Processor(config, nettyConfig, requestSender, channels, isClose, protocol); - } - - private Processor(AsyncHttpClientConfig config,// - NettyAsyncHttpProviderConfig nettyConfig,// + public Processor(AsyncHttpClientConfig config,// + ChannelManager channelManager,// NettyRequestSender requestSender,// - Channels channels,// - AtomicBoolean isClose,// Protocol protocol) { this.config = config; + this.channelManager = channelManager; this.requestSender = requestSender; - this.channels = channels; - this.closed = isClose; this.protocol = protocol; } @Override - public void channelRead(final ChannelHandlerContext ctx, Object e) throws Exception { + public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { Channel channel = ctx.channel(); Object attribute = Channels.getDefaultAttribute(channel); - if (attribute instanceof Callback && e instanceof LastHttpContent) { + if (attribute instanceof Callback && msg instanceof LastHttpContent) { Callback ac = (Callback) attribute; ac.call(); Channels.setDefaultAttribute(channel, DiscardEvent.INSTANCE); } else if (attribute instanceof NettyResponseFuture) { NettyResponseFuture future = (NettyResponseFuture) attribute; - - protocol.handle(channel, future, e); + protocol.handle(channel, future, msg); } else if (attribute != DiscardEvent.INSTANCE) { try { @@ -104,9 +86,11 @@ public void channelRead(final ChannelHandlerContext ctx, Object e) throws Except public void channelInactive(ChannelHandlerContext ctx) throws Exception { - if (closed.get()) { + if (requestSender.isClosed()) return; - } + + Channel channel = ctx.channel(); + channelManager.removeAll(channel); try { super.channelInactive(ctx); @@ -114,8 +98,6 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception { LOGGER.trace("super.channelClosed", ex); } - Channel channel = ctx.channel(); - channels.removeAll(channel); Object attachment = Channels.getDefaultAttribute(channel); LOGGER.debug("Channel Closed: {} with attachment {}", channel, attachment); @@ -128,20 +110,16 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception { NettyResponseFuture future = NettyResponseFuture.class.cast(attachment); future.touch(); - if (!config.getIOExceptionFilters().isEmpty() - && requestSender.applyIoExceptionFiltersAndReplayRequest(future, new IOException("Channel Closed"), channel)) { + if (!config.getIOExceptionFilters().isEmpty() && requestSender.applyIoExceptionFiltersAndReplayRequest(future, CHANNEL_CLOSED_EXCEPTION, channel)) return; - } protocol.onClose(channel); - if (future != null && !future.isDone() && !future.isCancelled()) { - if (!requestSender.retry(future, channel)) { - channels.abort(future, AsyncHttpProviderUtils.REMOTELY_CLOSED_EXCEPTION); - } - } else { - channels.closeChannel(channel); - } + if (future == null || future.isDone()) + channelManager.closeChannel(channel); + + else if (!requestSender.retry(future, channel)) + requestSender.abort(future, REMOTELY_CLOSED_EXCEPTION); } } @@ -149,9 +127,8 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception { Throwable cause = e.getCause() != null ? e.getCause() : e; - if (cause instanceof PrematureChannelClosureException || cause instanceof ClosedChannelException) { + if (cause instanceof PrematureChannelClosureException || cause instanceof ClosedChannelException) return; - } Channel channel = ctx.channel(); NettyResponseFuture future = null; @@ -167,12 +144,11 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Excep if (cause instanceof IOException) { - // FIXME why drop the original exception and create a new one? - if (!config.getIOExceptionFilters().isEmpty()) { - if (requestSender.applyIoExceptionFiltersAndReplayRequest(future, new IOException("Channel Closed"), channel)) { + // FIXME why drop the original exception and throw a new one? + if (!config.getIOExceptionFilters().isEmpty()) + if (requestSender.applyIoExceptionFiltersAndReplayRequest(future, CHANNEL_CLOSED_EXCEPTION, channel)) return; - } - } else { + else { // Close the channel so the recovering can occur try { channel.close(); @@ -194,18 +170,17 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Excep cause = t; } - if (future != null) { + if (future != null) try { LOGGER.debug("Was unable to recover Future: {}", future); - channels.abort(future, cause); + requestSender.abort(future, cause); } catch (Throwable t) { LOGGER.error(t.getMessage(), t); } - } protocol.onError(channel, e); - channels.closeChannel(channel); + channelManager.closeChannel(channel); // 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 old mode 100644 new mode 100755 index 59ff36c90e..8eb8363a47 --- 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 @@ -1,9 +1,10 @@ /* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. + * Copyright (c) 2014 AsyncHttpClient Project. 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. + * 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 @@ -16,8 +17,16 @@ import static io.netty.handler.codec.http.HttpResponseStatus.MOVED_PERMANENTLY; import static io.netty.handler.codec.http.HttpResponseStatus.SEE_OTHER; import static io.netty.handler.codec.http.HttpResponseStatus.TEMPORARY_REDIRECT; -import static org.asynchttpclient.providers.netty.util.HttpUtil.HTTP; -import static org.asynchttpclient.providers.netty.util.HttpUtil.WEBSOCKET; +import static org.asynchttpclient.providers.netty.util.HttpUtils.HTTP; +import static org.asynchttpclient.providers.netty.util.HttpUtils.WEBSOCKET; +import static org.asynchttpclient.util.AsyncHttpProviderUtils.followRedirect; +import io.netty.channel.Channel; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpResponse; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpClientConfig; @@ -32,30 +41,21 @@ import org.asynchttpclient.filter.FilterContext; import org.asynchttpclient.filter.FilterException; import org.asynchttpclient.filter.ResponseFilter; +import org.asynchttpclient.providers.netty.Callback; import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; +import org.asynchttpclient.providers.netty.channel.ChannelManager; import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.request.NettyRequestSender; import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.util.AsyncHttpProviderUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.netty.channel.Channel; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.HttpResponse; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.Callable; - public abstract class Protocol { - private final Logger logger = LoggerFactory.getLogger(getClass()); + protected final Logger logger = LoggerFactory.getLogger(getClass()); - protected final Channels channels; + protected final ChannelManager channelManager; protected final AsyncHttpClientConfig config; protected final NettyAsyncHttpProviderConfig nettyConfig; protected final NettyRequestSender requestSender; @@ -64,18 +64,17 @@ public abstract class Protocol { protected final boolean hasIOExceptionFilters; private final TimeConverter timeConverter; - public static final Set REDIRECT_STATUSES = new HashSet(); - + public static final Set REDIRECT_STATUSES = new HashSet(); static { - REDIRECT_STATUSES.add(MOVED_PERMANENTLY); - REDIRECT_STATUSES.add(FOUND); - REDIRECT_STATUSES.add(SEE_OTHER); - REDIRECT_STATUSES.add(TEMPORARY_REDIRECT); + REDIRECT_STATUSES.add(MOVED_PERMANENTLY.code()); + REDIRECT_STATUSES.add(FOUND.code()); + REDIRECT_STATUSES.add(SEE_OTHER.code()); + REDIRECT_STATUSES.add(TEMPORARY_REDIRECT.code()); } - public Protocol(Channels channels, AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, + public Protocol(ChannelManager channelManager, AsyncHttpClientConfig config, NettyAsyncHttpProviderConfig nettyConfig, NettyRequestSender requestSender) { - this.channels = channels; + this.channelManager = channelManager; this.config = config; this.requestSender = requestSender; this.nettyConfig = nettyConfig; @@ -91,12 +90,14 @@ public Protocol(Channels channels, AsyncHttpClientConfig config, NettyAsyncHttpP public abstract void onClose(Channel channel); - protected boolean handleRedirectAndExit(Request request, final NettyResponseFuture future, HttpResponse response, final Channel channel) - throws Exception { - - io.netty.handler.codec.http.HttpResponseStatus status = response.getStatus(); + protected boolean exitAfterHandlingRedirect(// + Channel channel,// + NettyResponseFuture future,// + HttpResponse response,// + Request request,// + int statusCode) throws Exception { - if (AsyncHttpProviderUtils.followRedirect(config, request) && REDIRECT_STATUSES.contains(status)) { + if (followRedirect(config, request) && REDIRECT_STATUSES.contains(statusCode)) { if (future.incrementAndGetCurrentRedirectCount() >= config.getMaxRedirects()) { throw new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects()); @@ -104,7 +105,8 @@ protected boolean handleRedirectAndExit(Request request, final NettyResponseFutu // We must allow 401 handling again. future.getAndSetAuth(false); - String location = response.headers().get(HttpHeaders.Names.LOCATION); + HttpHeaders responseHeaders = response.headers(); + String location = responseHeaders.get(HttpHeaders.Names.LOCATION); UriComponents uri = UriComponents.create(future.getURI(), location); if (!uri.equals(future.getURI())) { @@ -113,15 +115,14 @@ protected boolean handleRedirectAndExit(Request request, final NettyResponseFutu if (!config.isRemoveQueryParamOnRedirect()) requestBuilder.addQueryParams(future.getRequest().getQueryParams()); - // FIXME why not do that for 301 and 307 too? - // FIXME I think condition is wrong - if ((status.equals(FOUND) || status.equals(SEE_OTHER)) && !(status.equals(FOUND) && config.isStrict302Handling())) { - requestBuilder.setMethod(HttpMethod.GET.name()); - } + // if we are to strictly handle 302, we should keep the original method (which browsers don't) + // 303 must force GET + if ((statusCode == FOUND.code() && !config.isStrict302Handling()) || statusCode == SEE_OTHER.code()) + requestBuilder.setMethod("GET"); // in case of a redirect from HTTP to HTTPS, future attributes might change final boolean initialConnectionKeepAlive = future.isKeepAlive(); - final String initialPoolKey = channels.getPoolKey(future); + final String initialPoolKey = channelManager.getPoolKey(future); future.setURI(uri); String newUrl = uri.toString(); @@ -131,23 +132,13 @@ protected boolean handleRedirectAndExit(Request request, final NettyResponseFutu logger.debug("Redirecting to {}", newUrl); - if (future.getHttpHeaders().contains(HttpHeaders.Names.SET_COOKIE2)) { - for (String cookieStr : future.getHttpHeaders().getAll(HttpHeaders.Names.SET_COOKIE2)) { - Cookie c = CookieDecoder.decode(cookieStr, timeConverter); - if (c != null) { - requestBuilder.addOrReplaceCookie(c); - } - } - } else if (future.getHttpHeaders().contains(HttpHeaders.Names.SET_COOKIE)) { - for (String cookieStr : future.getHttpHeaders().getAll(HttpHeaders.Names.SET_COOKIE)) { - Cookie c = CookieDecoder.decode(cookieStr, timeConverter); - if (c != null) { - requestBuilder.addOrReplaceCookie(c); - } - } + for (String cookieStr : responseHeaders.getAll(HttpHeaders.Names.SET_COOKIE)) { + Cookie c = CookieDecoder.decode(cookieStr, timeConverter); + if (c != null) + requestBuilder.addOrReplaceCookie(c); } - Callable> callback = channels.newDrainCallable(future, channel, initialConnectionKeepAlive, initialPoolKey); + Callback callback = channelManager.newDrainCallback(future, channel, initialConnectionKeepAlive, initialPoolKey); if (HttpHeaders.isTransferEncodingChunked(response)) { // We must make sure there is no bytes left before @@ -170,14 +161,15 @@ protected boolean handleRedirectAndExit(Request request, final NettyResponseFutu return false; } - protected boolean handleResponseFiltersReplayRequestAndExit(// + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected boolean exitAfterProcessingFilters(// Channel channel,// NettyResponseFuture future,// + AsyncHandler handler, // HttpResponseStatus status,// HttpResponseHeaders responseHeaders) throws IOException { if (hasResponseFilters) { - AsyncHandler handler = future.getAsyncHandler(); FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(handler).request(future.getRequest()) .responseStatus(status).responseHeaders(responseHeaders).build(); @@ -189,7 +181,7 @@ protected boolean handleResponseFiltersReplayRequestAndExit(// throw new NullPointerException("FilterContext is null"); } } catch (FilterException efe) { - channels.abort(future, efe); + requestSender.abort(future, efe); } } 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 old mode 100644 new mode 100755 index 5aa2ce0663..8859822341 --- 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 @@ -1,21 +1,31 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.SWITCHING_PROTOCOLS; +import static org.asynchttpclient.providers.netty.ws.WebSocketUtils.getAcceptKey; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +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.WebSocketFrame; + +import java.io.IOException; +import java.util.Locale; import org.asynchttpclient.AsyncHandler.STATE; import org.asynchttpclient.AsyncHttpClientConfig; @@ -24,6 +34,7 @@ import org.asynchttpclient.Request; import org.asynchttpclient.providers.netty.DiscardEvent; import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; +import org.asynchttpclient.providers.netty.channel.ChannelManager; import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.request.NettyRequestSender; @@ -31,33 +42,16 @@ 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.util.StandardCharsets; import org.asynchttpclient.websocket.WebSocketUpgradeHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; -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.WebSocketFrame; +public final class WebSocketProtocol extends Protocol { -import java.io.IOException; -import java.util.Locale; - -final class WebSocketProtocol extends Protocol { - - private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketProtocol.class); - - public WebSocketProtocol(Channels channels,// + public WebSocketProtocol(ChannelManager channelManager,// AsyncHttpClientConfig config,// NettyAsyncHttpProviderConfig nettyConfig,// NettyRequestSender requestSender) { - super(channels, config, nettyConfig, requestSender); + super(channelManager, config, nettyConfig, requestSender); } // We don't need to synchronize as replacing the "ws-decoder" will @@ -67,27 +61,27 @@ private void invokeOnSucces(Channel channel, WebSocketUpgradeHandler h) { try { h.onSuccess(new NettyWebSocket(channel)); } catch (Exception ex) { - LOGGER.warn("onSuccess unexpected exception", ex); + logger.warn("onSuccess unexpected exception", ex); } } } @Override public void handle(Channel channel, NettyResponseFuture future, Object e) throws Exception { - WebSocketUpgradeHandler h = WebSocketUpgradeHandler.class.cast(future.getAsyncHandler()); + WebSocketUpgradeHandler handler = WebSocketUpgradeHandler.class.cast(future.getAsyncHandler()); Request request = future.getRequest(); if (e instanceof HttpResponse) { HttpResponse response = (HttpResponse) e; - HttpResponseStatus status = new ResponseStatus(future.getURI(), response, config); + HttpResponseStatus status = new ResponseStatus(future.getURI(), config, response); HttpResponseHeaders responseHeaders = new ResponseHeaders(response.headers()); - if (handleResponseFiltersReplayRequestAndExit(channel, future, status, responseHeaders)) { + if (exitAfterProcessingFilters(channel, future, handler, status, responseHeaders)) { return; } future.setHttpHeaders(response.headers()); - if (handleRedirectAndExit(request, future, response, channel)) + if (exitAfterHandlingRedirect(channel, future, response, request, response.getStatus().code())) return; boolean validStatus = response.getStatus().equals(SWITCHING_PROTOCOLS); @@ -99,45 +93,45 @@ public void handle(Channel channel, NettyResponseFuture future, Object e) thr boolean validConnection = c != null && c.equalsIgnoreCase(HttpHeaders.Values.UPGRADE); - status = new ResponseStatus(future.getURI(), response, config); - final boolean statusReceived = h.onStatusReceived(status) == STATE.UPGRADE; + status = new ResponseStatus(future.getURI(), config, response); + final boolean statusReceived = handler.onStatusReceived(status) == STATE.UPGRADE; if (!statusReceived) { try { - h.onCompleted(); + handler.onCompleted(); } finally { future.done(); } return; } - final boolean headerOK = h.onHeadersReceived(responseHeaders) == STATE.CONTINUE; + final boolean headerOK = handler.onHeadersReceived(responseHeaders) == STATE.CONTINUE; if (!headerOK || !validStatus || !validUpgrade || !validConnection) { - channels.abort(future, new IOException("Invalid handshake response")); + requestSender.abort(future, new IOException("Invalid handshake response")); return; } String accept = response.headers().get(HttpHeaders.Names.SEC_WEBSOCKET_ACCEPT); - String key = WebSocketUtil.getAcceptKey(future.getNettyRequest().getHttpRequest().headers() + String key = getAcceptKey(future.getNettyRequest().getHttpRequest().headers() .get(HttpHeaders.Names.SEC_WEBSOCKET_KEY)); if (accept == null || !accept.equals(key)) { - channels.abort(future, new IOException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, key))); + requestSender.abort(future, new IOException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, key))); } - Channels.upgradePipelineForWebSockets(channel); + channelManager.upgradePipelineForWebSockets(channel.pipeline()); - invokeOnSucces(channel, h); + invokeOnSucces(channel, handler); future.done(); } else if (e instanceof WebSocketFrame) { final WebSocketFrame frame = (WebSocketFrame) e; - NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted()); - invokeOnSucces(channel, h); + NettyWebSocket webSocket = NettyWebSocket.class.cast(handler.onCompleted()); + invokeOnSucces(channel, handler); if (webSocket != null) { if (frame instanceof CloseWebSocketFrame) { - Channels.setDefaultAttribute(channel, DiscardEvent.INSTANCE); + Channels.setDiscard(channel); CloseWebSocketFrame closeFrame = CloseWebSocketFrame.class.cast(frame); webSocket.onClose(closeFrame.statusCode(), closeFrame.reasonText()); } else { @@ -145,7 +139,7 @@ public void handle(Channel channel, NettyResponseFuture future, Object e) thr if (buf != null && buf.readableBytes() > 0) { try { NettyResponseBodyPart rp = nettyConfig.getBodyPartFactory().newResponseBodyPart(buf, frame.isFinalFragment()); - h.onBodyPartReceived(rp); + handler.onBodyPartReceived(rp); if (frame instanceof BinaryWebSocketFrame) { webSocket.onBinaryFragment(rp.getBodyPartBytes(), frame.isFinalFragment()); @@ -158,12 +152,12 @@ public void handle(Channel channel, NettyResponseFuture future, Object e) thr } } } else { - LOGGER.debug("UpgradeHandler returned a null NettyWebSocket "); + 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); + logger.error("Invalid message {}", e); } } @@ -171,7 +165,7 @@ public void handle(Channel channel, NettyResponseFuture future, Object e) thr public void onError(Channel channel, Throwable e) { try { Object attribute = Channels.getDefaultAttribute(channel); - LOGGER.warn("onError {}", e); + logger.warn("onError {}", e); if (!(attribute instanceof NettyResponseFuture)) { return; } @@ -185,17 +179,16 @@ public void onError(Channel channel, Throwable e) { webSocket.close(); } } catch (Throwable t) { - LOGGER.error("onError", t); + logger.error("onError", t); } } @Override public void onClose(Channel channel) { - LOGGER.trace("onClose {}"); + logger.trace("onClose {}"); Object attribute = Channels.getDefaultAttribute(channel); - if (!(attribute instanceof NettyResponseFuture)) { + if (!(attribute instanceof NettyResponseFuture)) return; - } try { NettyResponseFuture nettyResponse = NettyResponseFuture.class.cast(attribute); @@ -203,11 +196,11 @@ public void onClose(Channel channel) { NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted()); // FIXME How could this test not succeed, we just checked above that attribute is a NettyResponseFuture???? - LOGGER.trace("Connection was closed abnormally (that is, with no close frame being sent)."); + logger.trace("Connection was closed abnormally (that is, with no close frame being sent)."); if (attribute != DiscardEvent.INSTANCE && webSocket != null) webSocket.close(1006, "Connection was closed abnormally (that is, with no close frame being sent)."); } catch (Throwable t) { - LOGGER.error("onError", t); + logger.error("onError", t); } } } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java old mode 100644 new mode 100755 index c1ca441e1f..b4254bc088 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java @@ -1,24 +1,23 @@ /* - * Copyright 2010 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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. + * 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.request; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; + import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.providers.netty.channel.ChannelManager; import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.future.StackTraceInspector; @@ -34,6 +33,7 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLSession; + import java.net.ConnectException; import java.nio.channels.ClosedChannelException; @@ -47,27 +47,27 @@ final class NettyConnectListener implements ChannelFutureListener { private final AsyncHttpClientConfig config; private final NettyRequestSender requestSender; private final NettyResponseFuture future; - private final Channels channels; + private final ChannelManager channelManager; private final boolean channelPreempted; private final String poolKey; public NettyConnectListener(AsyncHttpClientConfig config,// - NettyRequestSender requestSender,// NettyResponseFuture future,// - Channels channels,// + NettyRequestSender requestSender,// + ChannelManager channelManager,// boolean channelPreempted,// String poolKey) { - this.requestSender = requestSender; this.config = config; this.future = future; - this.channels = channels; + this.requestSender = requestSender; + this.channelManager = channelManager; this.channelPreempted = channelPreempted; this.poolKey = poolKey; } private void abortChannelPreemption(String poolKey) { if (channelPreempted) - channels.abortChannelPreemption(poolKey); + channelManager.abortChannelPreemption(poolKey); } private void writeRequest(Channel channel) { @@ -79,7 +79,7 @@ private void writeRequest(Channel channel) { return; } - channels.registerOpenChannel(channel); + channelManager.registerOpenChannel(channel); future.attachChannel(channel, false); requestSender.writeRequest(future, channel); } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequest.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequest.java old mode 100644 new mode 100755 index 11eea6d69a..aec5d2fd03 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequest.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequest.java @@ -1,17 +1,15 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.request; @@ -19,7 +17,7 @@ import io.netty.handler.codec.http.HttpRequest; -public class NettyRequest { +public final class NettyRequest { private final HttpRequest httpRequest; private final NettyBody body; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java old mode 100644 new mode 100755 index 55bbc81b85..2b26b06e2e --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java @@ -1,25 +1,29 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.request; -import static org.asynchttpclient.providers.netty.util.HttpUtil.isNTLM; -import static org.asynchttpclient.providers.netty.util.HttpUtil.isSecure; -import static org.asynchttpclient.providers.netty.util.HttpUtil.isWebSocket; +import static org.asynchttpclient.providers.netty.util.HttpUtils.isNTLM; +import static org.asynchttpclient.providers.netty.util.HttpUtils.isSecure; +import static org.asynchttpclient.providers.netty.util.HttpUtils.isWebSocket; +import static org.asynchttpclient.providers.netty.ws.WebSocketUtils.getKey; import static org.asynchttpclient.util.AsyncHttpProviderUtils.DEFAULT_CHARSET; +import static org.asynchttpclient.util.AsyncHttpProviderUtils.constructUserAgent; +import static org.asynchttpclient.util.AsyncHttpProviderUtils.getAuthority; import static org.asynchttpclient.util.AsyncHttpProviderUtils.getNonEmptyPath; +import static org.asynchttpclient.util.AsyncHttpProviderUtils.keepAliveHeaderValue; +import static org.asynchttpclient.util.AuthenticatorUtils.computeBasicAuthentication; +import static org.asynchttpclient.util.AuthenticatorUtils.computeDigestAuthentication; import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.DefaultFullHttpRequest; @@ -53,11 +57,8 @@ import org.asynchttpclient.providers.netty.request.body.NettyFileBody; import org.asynchttpclient.providers.netty.request.body.NettyInputStreamBody; import org.asynchttpclient.providers.netty.request.body.NettyMultipartBody; -import org.asynchttpclient.providers.netty.ws.WebSocketUtil; import org.asynchttpclient.spnego.SpnegoEngine; import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.asynchttpclient.util.AuthenticatorUtils; import org.asynchttpclient.util.UTF8UrlEncoder; public final class NettyRequestFactory { @@ -74,7 +75,7 @@ public NettyRequestFactory(AsyncHttpClientConfig config, NettyAsyncHttpProviderC private String requestUri(UriComponents uri, ProxyServer proxyServer, HttpMethod method) { if (method == HttpMethod.CONNECT) - return AsyncHttpProviderUtils.getAuthority(uri); + return getAuthority(uri); else if (proxyServer != null && !(isSecure(uri) && config.isUseRelativeURIsWithSSLProxies())) return uri.toString(); @@ -101,12 +102,12 @@ private String authorizationHeader(Request request, UriComponents uri, ProxyServ switch (realm.getAuthScheme()) { case BASIC: - authorizationHeader = AuthenticatorUtils.computeBasicAuthentication(realm); + authorizationHeader = computeBasicAuthentication(realm); break; case DIGEST: if (isNonEmpty(realm.getNonce())) { try { - authorizationHeader = AuthenticatorUtils.computeDigestAuthentication(realm); + authorizationHeader = computeDigestAuthentication(realm); } catch (NoSuchAlgorithmException e) { throw new SecurityException(e); } @@ -177,7 +178,7 @@ private String proxyAuthorizationHeader(Request request, ProxyServer proxyServer } } } else { - proxyAuthorization = AuthenticatorUtils.computeBasicAuthentication(proxyServer); + proxyAuthorization = computeBasicAuthentication(proxyServer); } } @@ -228,8 +229,7 @@ private NettyBody body(Request request, HttpMethod method) throws IOException { } else if (request.getBodyGenerator() instanceof FileBodyGenerator) { FileBodyGenerator fileBodyGenerator = (FileBodyGenerator) request.getBodyGenerator(); - nettyBody = new NettyFileBody(fileBodyGenerator.getFile(), fileBodyGenerator.getRegionSeek(), - fileBodyGenerator.getRegionLength(), nettyConfig); + nettyBody = new NettyFileBody(fileBodyGenerator.getFile(), fileBodyGenerator.getRegionSeek(), fileBodyGenerator.getRegionLength(), nettyConfig); } else if (request.getBodyGenerator() instanceof InputStreamBodyGenerator) { nettyBody = new NettyInputStreamBody(InputStreamBodyGenerator.class.cast(request.getBodyGenerator()).getInputStream()); @@ -295,13 +295,12 @@ public NettyRequest newNettyRequest(Request request, UriComponents uri, boolean if (method != HttpMethod.CONNECT && webSocket) { httpRequest.headers().set(HttpHeaders.Names.UPGRADE, HttpHeaders.Values.WEBSOCKET); httpRequest.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.UPGRADE); - httpRequest.headers().set(HttpHeaders.Names.ORIGIN, - "http://" + uri.getHost() + ":" + (uri.getPort() == -1 ? isSecure(uri.getScheme()) ? 443 : 80 : uri.getPort())); - httpRequest.headers().set(HttpHeaders.Names.SEC_WEBSOCKET_KEY, WebSocketUtil.getKey()); + httpRequest.headers().set(HttpHeaders.Names.ORIGIN, "http://" + uri.getHost() + ":" + (uri.getPort() == -1 ? isSecure(uri.getScheme()) ? 443 : 80 : uri.getPort())); + httpRequest.headers().set(HttpHeaders.Names.SEC_WEBSOCKET_KEY, getKey()); httpRequest.headers().set(HttpHeaders.Names.SEC_WEBSOCKET_VERSION, "13"); } else if (!httpRequest.headers().contains(HttpHeaders.Names.CONNECTION)) { - httpRequest.headers().set(HttpHeaders.Names.CONNECTION, AsyncHttpProviderUtils.keepAliveHeaderValue(config)); + httpRequest.headers().set(HttpHeaders.Names.CONNECTION, keepAliveHeaderValue(config)); } String hostHeader = hostHeader(request, uri); @@ -324,8 +323,7 @@ public NettyRequest newNettyRequest(Request request, UriComponents uri, boolean // Add default user agent if (!httpRequest.headers().contains(HttpHeaders.Names.USER_AGENT)) { - String userAgent = config.getUserAgent() != null ? config.getUserAgent() : AsyncHttpProviderUtils.constructUserAgent( - NettyAsyncHttpProvider.class, config); + String userAgent = config.getUserAgent() != null ? config.getUserAgent() : constructUserAgent(NettyAsyncHttpProvider.class, config); httpRequest.headers().set(HttpHeaders.Names.USER_AGENT, userAgent); } 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 old mode 100644 new mode 100755 index 79b76edb3d..5dd4ee7674 --- 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 @@ -1,22 +1,40 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.request; -import static org.asynchttpclient.providers.netty.util.HttpUtil.WEBSOCKET; -import static org.asynchttpclient.providers.netty.util.HttpUtil.isSecure; +import static org.asynchttpclient.providers.netty.util.HttpUtils.WEBSOCKET; +import static org.asynchttpclient.providers.netty.util.HttpUtils.isSecure; +import static org.asynchttpclient.util.AsyncHttpProviderUtils.getDefaultPort; +import static org.asynchttpclient.util.AsyncHttpProviderUtils.requestTimeout; +import static org.asynchttpclient.util.ProxyUtils.avoidProxy; +import static org.asynchttpclient.util.ProxyUtils.getProxyServer; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHandlerExtensions; @@ -31,133 +49,150 @@ import org.asynchttpclient.filter.IOExceptionFilter; import org.asynchttpclient.listener.TransferCompletionHandler; import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; +import org.asynchttpclient.providers.netty.channel.ChannelManager; import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.request.timeout.ReadTimeoutTimerTask; import org.asynchttpclient.providers.netty.request.timeout.RequestTimeoutTimerTask; import org.asynchttpclient.providers.netty.request.timeout.TimeoutsHolder; import org.asynchttpclient.uri.UriComponents; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.asynchttpclient.util.ProxyUtils; import org.asynchttpclient.websocket.WebSocketUpgradeHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.netty.bootstrap.Bootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.util.Timeout; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.util.Map; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; - -public class NettyRequestSender { +public final class NettyRequestSender { private static final Logger LOGGER = LoggerFactory.getLogger(NettyRequestSender.class); - private final AtomicBoolean closed; private final AsyncHttpClientConfig config; - private final Channels channels; + private final ChannelManager channelManager; + private final Timer nettyTimer; + private final AtomicBoolean closed; private final NettyRequestFactory requestFactory; - public NettyRequestSender(AtomicBoolean closed,// - AsyncHttpClientConfig config,// + public NettyRequestSender(AsyncHttpClientConfig config,// NettyAsyncHttpProviderConfig nettyConfig,// - Channels channels) { - this.closed = closed; + ChannelManager channelManager,// + Timer nettyTimer,// + AtomicBoolean closed) { this.config = config; - this.channels = channels; + this.channelManager = channelManager; + this.nettyTimer = nettyTimer; + this.closed = closed; requestFactory = new NettyRequestFactory(config, nettyConfig); } - public boolean retry(NettyResponseFuture future, Channel channel) { + public ListenableFuture sendRequest(final Request request,// + final AsyncHandler asyncHandler,// + NettyResponseFuture future,// + boolean reclaimCache) throws IOException { if (closed.get()) - return false; + throw new IOException("Closed"); - channels.removeAll(channel); + UriComponents uri = request.getURI(); - if (future == null) { - Object attachment = Channels.getDefaultAttribute(channel); - if (attachment instanceof NettyResponseFuture) - future = (NettyResponseFuture) attachment; - } + // FIXME really useful? Why not do this check when building the request? + if (uri.getScheme().startsWith(WEBSOCKET) && !validateWebSocketRequest(request, asyncHandler)) + throw new IOException("WebSocket method must be a GET"); - if (future != null && future.canBeReplayed()) { - future.setState(NettyResponseFuture.STATE.RECONNECTED); - future.getAndSetStatusReceived(false); + ProxyServer proxyServer = getProxyServer(config, request); + boolean resultOfAConnect = future != null && future.getNettyRequest() != null && future.getNettyRequest().getHttpRequest().getMethod() == HttpMethod.CONNECT; + boolean useProxy = proxyServer != null && !resultOfAConnect; - LOGGER.debug("Trying to recover request {}\n", future.getNettyRequest()); - if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) { - AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onRetry(); - } + if (useProxy && isSecure(uri)) + // SSL proxy, have to handle CONNECT + if (future != null && future.isConnectAllowed()) + // CONNECT forced + return sendRequestWithCertainForceConnect(request, asyncHandler, future, reclaimCache, uri, proxyServer, true, true); + else + return sendRequestThroughSslProxy(request, asyncHandler, future, reclaimCache, uri, proxyServer); + else + return sendRequestWithCertainForceConnect(request, asyncHandler, future, reclaimCache, uri, proxyServer, useProxy, false); + } - try { - sendNextRequest(future.getRequest(), future); - return true; - - } catch (IOException iox) { - future.setState(NettyResponseFuture.STATE.CLOSED); - future.abort(iox); - LOGGER.error("Remotely Closed, unable to recover", iox); - return false; - } - } else { - LOGGER.debug("Unable to recover future {}\n", future); - return false; - } + /** + * We know for sure if we have to force to connect or not, so we can build + * the HttpRequest right away This reduces the probability of having a + * pooled channel closed by the server by the time we build the request + */ + private ListenableFuture sendRequestWithCertainForceConnect(// + Request request,// + AsyncHandler asyncHandler,// + NettyResponseFuture future,// + boolean reclaimCache,// + UriComponents uri,// + ProxyServer proxyServer,// + boolean useProxy,// + boolean forceConnect) throws IOException { + + NettyResponseFuture newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, uri, proxyServer, forceConnect); + + Channel channel = getCachedChannel(future, uri, request.getConnectionPoolKeyStrategy(), proxyServer); + + if (Channels.isChannelValid(channel)) + return sendRequestWithCachedChannel(request, uri, proxyServer, newFuture, asyncHandler, channel); + else + return sendRequestWithNewChannel(request, uri, proxyServer, useProxy, newFuture, asyncHandler, reclaimCache); } - public boolean applyIoExceptionFiltersAndReplayRequest(NettyResponseFuture future, IOException e, Channel channel) - throws IOException { + /** + * Using CONNECT depends on wither we can fetch a valid channel or not Loop + * until we get a valid channel from the pool and it's still valid once the + * request is built + */ + private ListenableFuture sendRequestThroughSslProxy(// + Request request,// + AsyncHandler asyncHandler,// + NettyResponseFuture future,// + boolean reclaimCache,// + UriComponents uri,// + ProxyServer proxyServer) throws IOException { - boolean replayed = false; + NettyResponseFuture newFuture = null; + for (int i = 0; i < 3; i++) { + Channel channel = getCachedChannel(future, uri, request.getConnectionPoolKeyStrategy(), proxyServer); + if (Channels.isChannelValid(channel)) + if (newFuture == null) + newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, uri, proxyServer, 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 (Channels.isChannelValid(channel)) + // if the channel is still active, we can use it, otherwise try + // gain + return sendRequestWithCachedChannel(request, uri, proxyServer, newFuture, asyncHandler, channel); + else + // pool is empty + break; } - if (fc.replayRequest()) { - replayRequest(future, fc, channel); - replayed = true; - } - return replayed; + newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, uri, proxyServer, true); + return sendRequestWithNewChannel(request, uri, proxyServer, true, newFuture, asyncHandler, reclaimCache); } - public void sendNextRequest(final Request request, final NettyResponseFuture f) throws IOException { - sendRequest(request, f.getAsyncHandler(), f, true); - } + private NettyResponseFuture newNettyRequestAndResponseFuture(final Request request, final AsyncHandler asyncHandler, NettyResponseFuture originalFuture, + UriComponents uri, ProxyServer proxy, boolean forceConnect) throws IOException { - // FIXME is this useful? Can't we do that when building the request? - private final boolean validateWebSocketRequest(Request request, AsyncHandler asyncHandler) { - return request.getMethod().equals(HttpMethod.GET.name()) && asyncHandler instanceof WebSocketUpgradeHandler; + NettyRequest nettyRequest = requestFactory.newNettyRequest(request, uri, forceConnect, proxy); + + if (originalFuture == null) { + return newNettyResponseFuture(uri, request, asyncHandler, nettyRequest, proxy); + } else { + originalFuture.setNettyRequest(nettyRequest); + originalFuture.setRequest(request); + return originalFuture; + } } private Channel getCachedChannel(NettyResponseFuture future, UriComponents uri, ConnectionPoolKeyStrategy poolKeyGen, ProxyServer proxyServer) { - if (future != null && future.reuseChannel() && isChannelValid(future.channel())) + if (future != null && future.reuseChannel() && Channels.isChannelValid(future.channel())) return future.channel(); else - return channels.pollAndVerifyCachedChannel(uri, proxyServer, poolKeyGen); + return pollAndVerifyCachedChannel(uri, proxyServer, poolKeyGen); } - private ListenableFuture sendRequestWithCachedChannel(Request request, UriComponents uri, ProxyServer proxy, - NettyResponseFuture future, AsyncHandler asyncHandler, Channel channel) throws IOException { + private ListenableFuture sendRequestWithCachedChannel(Request request, UriComponents uri, ProxyServer proxy, NettyResponseFuture future, + AsyncHandler asyncHandler, Channel channel) throws IOException { future.setState(NettyResponseFuture.STATE.POOLED); future.attachChannel(channel, false); @@ -186,26 +221,6 @@ private ListenableFuture sendRequestWithCachedChannel(Request request, Ur return future; } - private InetSocketAddress remoteAddress(Request request, UriComponents uri, ProxyServer proxy, boolean useProxy) { - if (request.getInetAddress() != null) - return new InetSocketAddress(request.getInetAddress(), AsyncHttpProviderUtils.getDefaultPort(uri)); - - else if (!useProxy || ProxyUtils.avoidProxy(proxy, uri.getHost())) - return new InetSocketAddress(uri.getHost(), AsyncHttpProviderUtils.getDefaultPort(uri)); - - else - return new InetSocketAddress(proxy.getHost(), proxy.getPort()); - } - - private ChannelFuture connect(Request request, UriComponents uri, ProxyServer proxy, boolean useProxy, Bootstrap bootstrap) { - InetSocketAddress remoteAddress = remoteAddress(request, uri, proxy, useProxy); - - if (request.getLocalAddress() != null) - return bootstrap.connect(remoteAddress, new InetSocketAddress(request.getLocalAddress(), 0)); - else - return bootstrap.connect(remoteAddress); - } - private ListenableFuture sendRequestWithNewChannel(// Request request,// UriComponents uri,// @@ -217,43 +232,43 @@ private ListenableFuture sendRequestWithNewChannel(// boolean useSSl = isSecure(uri) && !useProxy; - // Do not throw an exception when we need an extra connection for a redirect + // 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? - Bootstrap bootstrap = channels.getBootstrap(request.getURI(), useSSl, useProxy); - + Bootstrap bootstrap = channelManager.getBootstrap(request.getURI(), useProxy, useSSl); boolean channelPreempted = false; String poolKey = null; - - // Do not throw an exception when we need an extra connection for a redirect. + + // Do not throw an exception when we need an extra connection for a + // redirect. if (!reclaimCache) { // only compute when maxConnectionPerHost is enabled // FIXME clean up if (config.getMaxConnectionsPerHost() > 0) - poolKey = channels.getPoolKey(future); + poolKey = channelManager.getPoolKey(future); - channelPreempted = channels.preemptChannel(asyncHandler, poolKey); + channelPreempted = preemptChannel(asyncHandler, poolKey); } try { ChannelFuture channelFuture = connect(request, uri, proxy, useProxy, bootstrap); - channelFuture.addListener(new NettyConnectListener(config, this, future, channels, channelPreempted, poolKey)); + channelFuture.addListener(new NettyConnectListener(config, future, this, channelManager, channelPreempted, poolKey)); } catch (Throwable t) { if (channelPreempted) - channels.abortChannelPreemption(poolKey); + channelManager.abortChannelPreemption(poolKey); - channels.abort(future, t.getCause() == null ? t : t.getCause()); + abort(future, t.getCause() == null ? t : t.getCause()); } return future; } - private NettyResponseFuture newNettyResponseFuture(UriComponents uri, Request request, AsyncHandler asyncHandler, - NettyRequest nettyRequest, ProxyServer proxyServer) { + private NettyResponseFuture newNettyResponseFuture(UriComponents uri, Request request, AsyncHandler asyncHandler, NettyRequest nettyRequest, ProxyServer proxyServer) { - NettyResponseFuture f = new NettyResponseFuture(// + NettyResponseFuture future = new NettyResponseFuture(// uri,// request,// asyncHandler,// @@ -263,110 +278,76 @@ private NettyResponseFuture newNettyResponseFuture(UriComponents uri, Req proxyServer); String expectHeader = request.getHeaders().getFirstValue(HttpHeaders.Names.EXPECT); - if (expectHeader != null && expectHeader.equalsIgnoreCase(HttpHeaders.Values.CONTINUE)) { - f.setDontWriteBodyBecauseExpectContinue(true); - } - return f; + if (expectHeader != null && expectHeader.equalsIgnoreCase(HttpHeaders.Values.CONTINUE)) + future.setDontWriteBodyBecauseExpectContinue(true); + return future; } - private NettyResponseFuture newNettyRequestAndResponseFuture(final Request request, final AsyncHandler asyncHandler, - NettyResponseFuture originalFuture, UriComponents uri, ProxyServer proxy, boolean forceConnect) throws IOException { - - NettyRequest nettyRequest = requestFactory.newNettyRequest(request, uri, forceConnect, proxy); + public void writeRequest(NettyResponseFuture future, Channel channel) { + 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 channelInactive do its work + if (!Channels.isChannelValid(channel)) + return; - if (originalFuture == null) { - return newNettyResponseFuture(uri, request, asyncHandler, nettyRequest, proxy); - } else { - originalFuture.setNettyRequest(nettyRequest); - originalFuture.setRequest(request); - return originalFuture; - } - } + NettyRequest nettyRequest = future.getNettyRequest(); + HttpRequest httpRequest = nettyRequest.getHttpRequest(); + AsyncHandler handler = future.getAsyncHandler(); - private boolean isChannelValid(Channel channel) { - return channel != null && channel.isOpen() && channel.isActive(); - } + if (handler instanceof TransferCompletionHandler) + configureTransferAdapter(handler, httpRequest); - private ListenableFuture sendRequestThroughSslProxy(// - Request request,// - AsyncHandler asyncHandler,// - NettyResponseFuture future,// - boolean reclaimCache,// - UriComponents uri,// - ProxyServer proxyServer) throws IOException { + if (!future.isHeadersAlreadyWrittenOnContinue()) { + try { + if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) + AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onRequestSent(); - // Using CONNECT depends on wither we can fetch a valid channel or not + channel.writeAndFlush(httpRequest, channel.newProgressivePromise()).addListener(new ProgressListener(config, future.getAsyncHandler(), future, true, 0L)); + } catch (Throwable cause) { + // FIXME why not notify? + LOGGER.debug(cause.getMessage(), cause); + try { + channel.close(); + } catch (RuntimeException ex) { + LOGGER.debug(ex.getMessage(), ex); + } + return; + } + } - // Loop until we get a valid channel from the pool and it's still valid once the request is built - NettyResponseFuture newFuture = null; - for (int i = 0; i < 3; i++) { - Channel channel = getCachedChannel(future, uri, request.getConnectionPoolKeyStrategy(), proxyServer); - if (isChannelValid(channel)) { - if (newFuture == null) - newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, uri, proxyServer, false); + if (!future.isDontWriteBodyBecauseExpectContinue() && !httpRequest.getMethod().equals(HttpMethod.CONNECT) && nettyRequest.getBody() != null) + nettyRequest.getBody().write(channel, future, config); - if (isChannelValid(channel)) - // if the channel is still active, we can use it, otherwise try gain - return sendRequestWithCachedChannel(request, uri, proxyServer, newFuture, asyncHandler, channel); - } else - // pool is empty - break; + } catch (Throwable ioe) { + try { + channel.close(); + } catch (RuntimeException ex) { + LOGGER.debug(ex.getMessage(), ex); + } } - newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, uri, proxyServer, true); - return sendRequestWithNewChannel(request, uri, proxyServer, true, newFuture, asyncHandler, reclaimCache); + scheduleTimeouts(future); } - private ListenableFuture sendRequestWithCertainForceConnect(// - Request request,// - AsyncHandler asyncHandler,// - NettyResponseFuture future,// - boolean reclaimCache,// - UriComponents uri,// - ProxyServer proxyServer,// - boolean useProxy,// - boolean forceConnect) throws IOException { - // We know for sure if we have to force to connect or not, so we can build the HttpRequest right away - // This reduces the probability of having a pooled channel closed by the server by the time we build the request - NettyResponseFuture newFuture = newNettyRequestAndResponseFuture(request, asyncHandler, future, uri, proxyServer, forceConnect); + private InetSocketAddress remoteAddress(Request request, UriComponents uri, ProxyServer proxy, boolean useProxy) { + if (request.getInetAddress() != null) + return new InetSocketAddress(request.getInetAddress(), getDefaultPort(uri)); - Channel channel = getCachedChannel(future, uri, request.getConnectionPoolKeyStrategy(), proxyServer); + else if (!useProxy || avoidProxy(proxy, uri.getHost())) + return new InetSocketAddress(uri.getHost(), getDefaultPort(uri)); - if (isChannelValid(channel)) - return sendRequestWithCachedChannel(request, uri, proxyServer, newFuture, asyncHandler, channel); else - return sendRequestWithNewChannel(request, uri, proxyServer, useProxy, newFuture, asyncHandler, reclaimCache); + return new InetSocketAddress(proxy.getHost(), proxy.getPort()); } - public ListenableFuture sendRequest(final Request request,// - final AsyncHandler asyncHandler,// - NettyResponseFuture future,// - 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.getURI().getScheme().startsWith(WEBSOCKET) && !validateWebSocketRequest(request, asyncHandler)) { - throw new IOException("WebSocket method must be a GET"); - } - - UriComponents uri = request.getURI(); - ProxyServer proxyServer = ProxyUtils.getProxyServer(config, request); - boolean resultOfAConnect = future != null && future.getNettyRequest() != null - && future.getNettyRequest().getHttpRequest().getMethod() == HttpMethod.CONNECT; - boolean useProxy = proxyServer != null && !resultOfAConnect; + private ChannelFuture connect(Request request, UriComponents uri, ProxyServer proxy, boolean useProxy, Bootstrap bootstrap) { + InetSocketAddress remoteAddress = remoteAddress(request, uri, proxy, useProxy); - if (useProxy && isSecure(uri)) { - // SSL proxy, have to handle CONNECT - if (future != null && future.isConnectAllowed()) - // CONNECT forced - return sendRequestWithCertainForceConnect(request, asyncHandler, future, reclaimCache, uri, proxyServer, true, true); - else - return sendRequestThroughSslProxy(request, asyncHandler, future, reclaimCache, uri, proxyServer); - } else - return sendRequestWithCertainForceConnect(request, asyncHandler, future, reclaimCache, uri, proxyServer, useProxy, false); + if (request.getLocalAddress() != null) + return bootstrap.connect(remoteAddress, new InetSocketAddress(request.getLocalAddress(), 0)); + else + return bootstrap.connect(remoteAddress); } private void configureTransferAdapter(AsyncHandler handler, HttpRequest httpRequest) { @@ -382,88 +363,163 @@ private void scheduleTimeouts(NettyResponseFuture nettyResponseFuture) { try { nettyResponseFuture.touch(); - int requestTimeoutInMs = AsyncHttpProviderUtils.requestTimeout(config, nettyResponseFuture.getRequest()); + int requestTimeoutInMs = requestTimeout(config, nettyResponseFuture.getRequest()); TimeoutsHolder timeoutsHolder = new TimeoutsHolder(); if (requestTimeoutInMs != -1) { - Timeout requestTimeout = channels.newTimeoutInMs(new RequestTimeoutTimerTask(nettyResponseFuture, channels, timeoutsHolder, - closed, requestTimeoutInMs), requestTimeoutInMs); + Timeout requestTimeout = newTimeout(new RequestTimeoutTimerTask(nettyResponseFuture, this, timeoutsHolder, requestTimeoutInMs), requestTimeoutInMs); timeoutsHolder.requestTimeout = requestTimeout; } int readTimeout = config.getReadTimeout(); if (readTimeout != -1 && readTimeout < requestTimeoutInMs) { - // no need for a idleConnectionTimeout that's less than the requestTimeoutInMs - Timeout idleConnectionTimeout = channels.newTimeoutInMs(new ReadTimeoutTimerTask(nettyResponseFuture, channels, - timeoutsHolder, closed, requestTimeoutInMs, readTimeout), readTimeout); + // no need for a idleConnectionTimeout that's less than the + // requestTimeoutInMs + Timeout idleConnectionTimeout = newTimeout(new ReadTimeoutTimerTask(nettyResponseFuture, this, timeoutsHolder, requestTimeoutInMs, readTimeout), readTimeout); timeoutsHolder.readTimeout = idleConnectionTimeout; } nettyResponseFuture.setTimeoutsHolder(timeoutsHolder); } catch (RejectedExecutionException ex) { - channels.abort(nettyResponseFuture, ex); + abort(nettyResponseFuture, ex); } } - public final void writeRequest(NettyResponseFuture future, Channel channel) { - 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 channelInactive do its work - if (!channel.isOpen() || !channel.isActive()) { - return; + public Timeout newTimeout(TimerTask task, long delay) { + return nettyTimer.newTimeout(task, delay, TimeUnit.MILLISECONDS); + } + + public void abort(NettyResponseFuture future, Throwable t) { + + Channel channel = future.channel(); + if (channel != null) + channelManager.closeChannel(channel); + + if (!future.isDone()) { + LOGGER.debug("Aborting Future {}\n", future); + LOGGER.debug(t.getMessage(), t); + } + + future.abort(t); + } + + public boolean retry(NettyResponseFuture future, Channel channel) { + + if (isClosed()) + return false; + + channelManager.removeAll(channel); + + if (future == null) { + Object attachment = Channels.getDefaultAttribute(channel); + if (attachment instanceof NettyResponseFuture) + future = (NettyResponseFuture) attachment; + } + + if (future != null && future.canBeReplayed()) { + future.setState(NettyResponseFuture.STATE.RECONNECTED); + future.getAndSetStatusReceived(false); + + LOGGER.debug("Trying to recover request {}\n", future.getNettyRequest()); + if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) { + AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onRetry(); } - NettyRequest nettyRequest = future.getNettyRequest(); - HttpRequest httpRequest = nettyRequest.getHttpRequest(); - AsyncHandler handler = future.getAsyncHandler(); + try { + sendNextRequest(future.getRequest(), future); + return true; - if (handler instanceof TransferCompletionHandler) { - configureTransferAdapter(handler, httpRequest); + } catch (IOException iox) { + future.setState(NettyResponseFuture.STATE.CLOSED); + future.abort(iox); + LOGGER.error("Remotely Closed, unable to recover", iox); + return false; } + } else { + LOGGER.debug("Unable to recover future {}\n", future); + return false; + } + } - if (!future.isHeadersAlreadyWrittenOnContinue()) { - try { - if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) { - AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onRequestSent(); - } - channel.writeAndFlush(httpRequest, channel.newProgressivePromise()).addListener( - new ProgressListener(config, future.getAsyncHandler(), future, true, 0L)); - } catch (Throwable cause) { - // FIXME why not notify? - LOGGER.debug(cause.getMessage(), cause); - try { - channel.close(); - } catch (RuntimeException ex) { - LOGGER.debug(ex.getMessage(), ex); - } - return; + public boolean applyIoExceptionFiltersAndReplayRequest(NettyResponseFuture future, IOException e, Channel channel) 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) { + abort(future, efe); } + } - if (!future.isDontWriteBodyBecauseExpectContinue() && !httpRequest.getMethod().equals(HttpMethod.CONNECT) - && nettyRequest.getBody() != null) - nettyRequest.getBody().write(channel, future, config); + if (fc.replayRequest() && future.canBeReplayed()) { + replayRequest(future, fc, channel); + replayed = true; + } + return replayed; + } + + public void sendNextRequest(final Request request, final NettyResponseFuture future) throws IOException { + sendRequest(request, future.getAsyncHandler(), future, true); + } + + // FIXME is this useful? Can't we do that when building the request? + private boolean validateWebSocketRequest(Request request, AsyncHandler asyncHandler) { + return request.getMethod().equals(HttpMethod.GET.name()) && asyncHandler instanceof WebSocketUpgradeHandler; + } + + private Channel pollAndVerifyCachedChannel(UriComponents uri, ProxyServer proxy, ConnectionPoolKeyStrategy connectionPoolKeyStrategy) { + final Channel channel = channelManager.poll(connectionPoolKeyStrategy.getKey(uri, proxy)); + + if (channel != null) { + LOGGER.debug("Using cached Channel {}\n for uri {}\n", channel, uri); - } catch (Throwable ioe) { try { - channel.close(); - } catch (RuntimeException ex) { + channelManager.verifyChannelPipeline(channel.pipeline(), uri.getScheme()); + } catch (Exception ex) { LOGGER.debug(ex.getMessage(), ex); } } + return channel; + } - scheduleTimeouts(future); + private boolean preemptChannel(AsyncHandler asyncHandler, String poolKey) throws IOException { + + boolean channelPreempted = false; + if (channelManager.preemptChannel(poolKey)) { + channelPreempted = true; + } else { + IOException ex = new IOException(String.format("Too many connections %s", config.getMaxConnections())); + try { + asyncHandler.onThrowable(ex); + } catch (Exception e) { + LOGGER.warn("asyncHandler.onThrowable crashed", e); + } + throw ex; + } + return channelPreempted; } + @SuppressWarnings({ "rawtypes", "unchecked" }) public void replayRequest(final NettyResponseFuture future, FilterContext fc, Channel channel) throws IOException { + Request newRequest = fc.getRequest(); future.setAsyncHandler(fc.getAsyncHandler()); future.setState(NettyResponseFuture.STATE.NEW); future.touch(); LOGGER.debug("\n\nReplaying Request {}\n for Future {}\n", newRequest, future); - if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) { + if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onRetry(); - } - channels.drainChannel(channel, future); + + channelManager.drainChannel(channel, future); sendNextRequest(newRequest, future); } + + public boolean isClosed() { + return closed.get(); + } } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/ProgressListener.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/ProgressListener.java old mode 100644 new mode 100755 index 78c9f35919..8e1b7fe2cd --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/ProgressListener.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/ProgressListener.java @@ -1,17 +1,15 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.request; @@ -41,8 +39,11 @@ public class ProgressListener implements ChannelProgressiveFutureListener { private final long expectedTotal; private long lastProgress = 0L; - public ProgressListener(AsyncHttpClientConfig config, AsyncHandler asyncHandler, NettyResponseFuture future, - boolean notifyHeaders, long expectedTotal) { + public ProgressListener(AsyncHttpClientConfig config,// + AsyncHandler asyncHandler,// + NettyResponseFuture future,// + boolean notifyHeaders,// + long expectedTotal) { this.config = config; this.asyncHandler = asyncHandler; this.future = future; @@ -83,7 +84,8 @@ private boolean abortOnThrowable(Throwable cause, Channel channel) { @Override public void operationComplete(ChannelProgressiveFuture cf) { - // The write operation failed. If the channel was cached, it means it got asynchronously closed. + // The write operation failed. If the channel was cached, it means it + // got asynchronously closed. // Let's retry a second time. if (!abortOnThrowable(cf.cause(), cf.channel())) { @@ -91,9 +93,10 @@ public void operationComplete(ChannelProgressiveFuture cf) { 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. + * 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() : config.getRealm(); boolean startPublishing = future.isInAuth() || realm == null || realm.getUsePreemptiveAuth(); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyChunkedInput.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyChunkedInput.java old mode 100644 new mode 100755 index 271046a3e7..e48d6d8a13 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyChunkedInput.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyChunkedInput.java @@ -1,9 +1,10 @@ /* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. + * Copyright (c) 2014 AsyncHttpClient Project. 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. + * 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 diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java old mode 100644 new mode 100755 index 857b610784..49fabeef29 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java @@ -1,9 +1,10 @@ /* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. + * Copyright (c) 2014 AsyncHttpClient Project. 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. + * 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 diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/FeedableBodyGenerator.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/FeedableBodyGenerator.java old mode 100644 new mode 100755 index 97bdc34701..dbff022523 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/FeedableBodyGenerator.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/FeedableBodyGenerator.java @@ -1,9 +1,10 @@ /* - * Copyright (c) 2012 Sonatype, Inc. All rights reserved. + * Copyright (c) 2014 AsyncHttpClient Project. 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. + * 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 diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBody.java old mode 100644 new mode 100755 index c35606d17b..cea79857c3 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBody.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBody.java @@ -1,17 +1,15 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.request.body; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java old mode 100644 new mode 100755 index fb9108e7b1..7dac353e5b --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java @@ -1,17 +1,15 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.request.body; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyByteArrayBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyByteArrayBody.java old mode 100644 new mode 100755 index eb2e9e9bfb..953461fb84 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyByteArrayBody.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyByteArrayBody.java @@ -1,17 +1,15 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.request.body; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyFileBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyFileBody.java old mode 100644 new mode 100755 index 5360943b26..3043bd3e32 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyFileBody.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyFileBody.java @@ -1,17 +1,15 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.request.body; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyInputStreamBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyInputStreamBody.java old mode 100644 new mode 100755 index e848c21e39..4412cd9bf4 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyInputStreamBody.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyInputStreamBody.java @@ -1,17 +1,15 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.request.body; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyMultipartBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyMultipartBody.java old mode 100644 new mode 100755 index 742ed21657..0c69440533 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyMultipartBody.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyMultipartBody.java @@ -1,34 +1,33 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.request.body; +import static org.asynchttpclient.multipart.MultipartUtils.newMultipartBody; + +import java.util.List; + import org.asynchttpclient.FluentCaseInsensitiveStringsMap; import org.asynchttpclient.multipart.MultipartBody; -import org.asynchttpclient.multipart.MultipartUtils; import org.asynchttpclient.multipart.Part; import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; -import java.util.List; - public class NettyMultipartBody extends NettyBodyBody { private final String contentType; public NettyMultipartBody(List parts, FluentCaseInsensitiveStringsMap headers, NettyAsyncHttpProviderConfig nettyConfig) { - this(MultipartUtils.newMultipartBody(parts, headers), nettyConfig); + this(newMultipartBody(parts, headers), nettyConfig); } private NettyMultipartBody(MultipartBody body, NettyAsyncHttpProviderConfig nettyConfig) { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/ReadTimeoutTimerTask.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/ReadTimeoutTimerTask.java old mode 100644 new mode 100755 index 6199c9cd9b..c500bdd369 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/ReadTimeoutTimerTask.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/ReadTimeoutTimerTask.java @@ -1,28 +1,23 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.request.timeout; import static org.asynchttpclient.util.DateUtils.millisTime; - -import org.asynchttpclient.providers.netty.channel.Channels; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; - import io.netty.util.Timeout; -import java.util.concurrent.atomic.AtomicBoolean; +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; +import org.asynchttpclient.providers.netty.request.NettyRequestSender; public class ReadTimeoutTimerTask extends TimeoutTimerTask { @@ -31,19 +26,18 @@ public class ReadTimeoutTimerTask extends TimeoutTimerTask { public ReadTimeoutTimerTask(// NettyResponseFuture nettyResponseFuture,// - Channels channels,// + NettyRequestSender requestSender,// TimeoutsHolder timeoutsHolder,// - AtomicBoolean clientClosed,// long requestTimeout,// long readTimeout) { - super(nettyResponseFuture, channels, timeoutsHolder, clientClosed); + super(nettyResponseFuture, requestSender, timeoutsHolder); this.readTimeout = readTimeout; requestTimeoutInstant = requestTimeout >= 0 ? nettyResponseFuture.getStart() + requestTimeout : Long.MAX_VALUE; } @Override public void run(Timeout timeout) throws Exception { - if (clientClosed.get()) { + if (requestSender.isClosed()) { timeoutsHolder.cancel(); return; } @@ -64,7 +58,7 @@ public void run(Timeout timeout) throws Exception { } else if (currentReadTimeoutInstant < requestTimeoutInstant) { // reschedule - timeoutsHolder.readTimeout = channels.newTimeoutInMs(this, durationBeforeCurrentReadTimeout); + timeoutsHolder.readTimeout = requestSender.newTimeout(this, durationBeforeCurrentReadTimeout); } else { // otherwise, no need to reschedule: requestTimeout will happen sooner diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java old mode 100644 new mode 100755 index cb4d2ca509..51c72d1574 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/RequestTimeoutTimerTask.java @@ -1,28 +1,23 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.request.timeout; import static org.asynchttpclient.util.DateUtils.millisTime; - -import org.asynchttpclient.providers.netty.channel.Channels; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; - import io.netty.util.Timeout; -import java.util.concurrent.atomic.AtomicBoolean; +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; +import org.asynchttpclient.providers.netty.request.NettyRequestSender; public class RequestTimeoutTimerTask extends TimeoutTimerTask { @@ -30,11 +25,10 @@ public class RequestTimeoutTimerTask extends TimeoutTimerTask { public RequestTimeoutTimerTask(// NettyResponseFuture nettyResponseFuture,// - Channels channels,// + NettyRequestSender requestSender,// TimeoutsHolder timeoutsHolder,// - AtomicBoolean clientClosed,// long requestTimeout) { - super(nettyResponseFuture, channels, timeoutsHolder, clientClosed); + super(nettyResponseFuture, requestSender, timeoutsHolder); this.requestTimeout = requestTimeout; } @@ -44,9 +38,8 @@ public void run(Timeout timeout) throws Exception { // in any case, cancel possible idleConnectionTimeout timeoutsHolder.cancel(); - if (clientClosed.get()) { + if (requestSender.isClosed()) return; - } if (!nettyResponseFuture.isDone() && !nettyResponseFuture.isCancelled()) { String message = "Request timed out to " + nettyResponseFuture.getChannelRemoteAddress() + " of " + requestTimeout + " ms"; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutTimerTask.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutTimerTask.java old mode 100644 new mode 100755 index e599e5d871..b774b7763a --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutTimerTask.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutTimerTask.java @@ -1,52 +1,46 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.request.timeout; -import org.asynchttpclient.providers.netty.channel.Channels; -import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import io.netty.util.TimerTask; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; + +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; +import org.asynchttpclient.providers.netty.request.NettyRequestSender; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class TimeoutTimerTask implements TimerTask { private static final Logger LOGGER = LoggerFactory.getLogger(TimeoutTimerTask.class); protected final NettyResponseFuture nettyResponseFuture; - protected final Channels channels; + protected final NettyRequestSender requestSender; protected final TimeoutsHolder timeoutsHolder; - protected final AtomicBoolean clientClosed; public TimeoutTimerTask(// NettyResponseFuture nettyResponseFuture,// - Channels channels,// - TimeoutsHolder timeoutsHolder,// - AtomicBoolean clientClosed) { + NettyRequestSender requestSender,// + TimeoutsHolder timeoutsHolder) { this.nettyResponseFuture = nettyResponseFuture; - this.channels = channels; + this.requestSender = requestSender; this.timeoutsHolder = timeoutsHolder; - this.clientClosed = clientClosed; } protected void expire(String message, long ms) { LOGGER.debug("{} for {} after {} ms", message, nettyResponseFuture, ms); - channels.abort(nettyResponseFuture, new TimeoutException(message)); + requestSender.abort(nettyResponseFuture, new TimeoutException(message)); } } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java old mode 100644 new mode 100755 index d032672a4a..9e6f1cb22f --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/TimeoutsHolder.java @@ -1,17 +1,15 @@ /* - * Copyright 2010-2013 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.request.timeout; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/EagerResponseBodyPart.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/EagerResponseBodyPart.java old mode 100644 new mode 100755 index aad1f2e5af..afccb511ee --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/EagerResponseBodyPart.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/EagerResponseBodyPart.java @@ -12,7 +12,7 @@ */ package org.asynchttpclient.providers.netty.response; -import org.asynchttpclient.providers.netty.util.ByteBufUtil; +import static org.asynchttpclient.providers.netty.util.ByteBufUtils.*; import io.netty.buffer.ByteBuf; @@ -32,7 +32,7 @@ public class EagerResponseBodyPart extends NettyResponseBodyPart { public EagerResponseBodyPart(ByteBuf buf, boolean last) { super(last); - bytes = ByteBufUtil.byteBuf2Bytes(buf); + bytes = byteBuf2Bytes(buf); } /** 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 index 94eb15d70d..17d593541e --- 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 @@ -1,30 +1,20 @@ /* - * Copyright 2010 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.response; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.HttpResponseHeaders; -import org.asynchttpclient.HttpResponseStatus; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.cookie.CookieDecoder; -import org.asynchttpclient.date.TimeConverter; -import org.asynchttpclient.providers.ResponseBase; -import org.asynchttpclient.util.AsyncHttpProviderUtils; -import org.asynchttpclient.util.MiscUtils; - +import static org.asynchttpclient.util.AsyncHttpProviderUtils.contentToBytes; +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; import io.netty.handler.codec.http.HttpHeaders; import java.io.ByteArrayInputStream; @@ -35,6 +25,14 @@ import java.util.Collections; import java.util.List; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.HttpResponseHeaders; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.cookie.Cookie; +import org.asynchttpclient.cookie.CookieDecoder; +import org.asynchttpclient.date.TimeConverter; +import org.asynchttpclient.providers.ResponseBase; + /** * Wrapper around the {@link org.asynchttpclient.Response} API. */ @@ -58,7 +56,7 @@ public String getResponseBodyExcerpt(int maxLength) throws IOException { 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); + byte[] b = contentToBytes(bodyParts, maxLength); return new String(b, charset); } @@ -66,11 +64,11 @@ protected List buildCookies() { List setCookieHeaders = headers.getHeaders().get(HttpHeaders.Names.SET_COOKIE2); - if (!MiscUtils.isNonEmpty(setCookieHeaders)) { + if (!isNonEmpty(setCookieHeaders)) { setCookieHeaders = headers.getHeaders().get(HttpHeaders.Names.SET_COOKIE); } - if (MiscUtils.isNonEmpty(setCookieHeaders)) { + if (isNonEmpty(setCookieHeaders)) { List cookies = new ArrayList(); for (String value : setCookieHeaders) { Cookie c = CookieDecoder.decode(value, timeConverter); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseBodyPart.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseBodyPart.java old mode 100644 new mode 100755 index 2c50ce6a79..76399a14e6 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseBodyPart.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseBodyPart.java @@ -1,17 +1,15 @@ /* - * Copyright 2010 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.response; 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 old mode 100644 new mode 100755 index 5f9f56da92..a40068965f --- 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 @@ -1,17 +1,15 @@ /* - * Copyright 2010 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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.response; 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 old mode 100644 new mode 100755 index 996038de10..1c74721d23 --- 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 @@ -1,18 +1,15 @@ /* - * Copyright 2010 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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. + * 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.response; @@ -34,7 +31,7 @@ public class ResponseStatus extends HttpResponseStatus { private final HttpResponse response; - public ResponseStatus(UriComponents uri, HttpResponse response, AsyncHttpClientConfig config) { + public ResponseStatus(UriComponents uri, AsyncHttpClientConfig config, HttpResponse response) { super(uri, config); this.response = response; } 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/ByteBufUtils.java old mode 100644 new mode 100755 similarity index 96% rename from providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ByteBufUtil.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ByteBufUtils.java index 9c6bb8d7a7..f370ac153b --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ByteBufUtil.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/ByteBufUtils.java @@ -19,11 +19,11 @@ import java.util.List; -public final class ByteBufUtil { +public final class ByteBufUtils { public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - private ByteBufUtil() { + private ByteBufUtils() { } public static byte[] byteBuf2Bytes(ByteBuf buf) { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtil.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtils.java old mode 100644 new mode 100755 similarity index 96% rename from providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtil.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtils.java index 906bc7e8eb..b7ab94e3fc --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtil.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtils.java @@ -18,14 +18,14 @@ import org.asynchttpclient.uri.UriComponents; -public final class HttpUtil { +public final class HttpUtils { public static final String HTTPS = "https"; public static final String HTTP = "http"; public static final String WEBSOCKET = "ws"; public static final String WEBSOCKET_SSL = "wss"; - private HttpUtil() { + private HttpUtils() { } public static boolean isNTLM(List auth) { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java old mode 100644 new mode 100755 index e9c32e6f28..2a87ac293b --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java @@ -1,9 +1,10 @@ /* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. + * Copyright (c) 2014 AsyncHttpClient Project. 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. + * 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 diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/WebSocketUtil.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/WebSocketUtils.java old mode 100644 new mode 100755 similarity index 91% rename from providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/WebSocketUtil.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/WebSocketUtils.java index 4097ab31d6..c308edaea4 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/WebSocketUtil.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/WebSocketUtils.java @@ -1,9 +1,10 @@ /* - * Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved. + * Copyright (c) 2014 AsyncHttpClient Project. 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. + * 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 @@ -19,7 +20,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -public final class WebSocketUtil { +public final class WebSocketUtils { public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; From 5ee604e48ed96f306cbea6fc4dfd83702fe8553b Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 24 Jul 2014 10:59:37 +0200 Subject: [PATCH 0116/2020] Would would write schemes not in lowercase? --- .../org/asynchttpclient/providers/netty/util/HttpUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtils.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtils.java index b7ab94e3fc..e13b7ee51b 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtils.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtils.java @@ -33,11 +33,11 @@ public static boolean isNTLM(List auth) { } public static boolean isWebSocket(String scheme) { - return WEBSOCKET.equalsIgnoreCase(scheme) || WEBSOCKET_SSL.equalsIgnoreCase(scheme); + return WEBSOCKET.equals(scheme) || WEBSOCKET_SSL.equals(scheme); } public static boolean isSecure(String scheme) { - return HTTPS.equalsIgnoreCase(scheme) || WEBSOCKET_SSL.equalsIgnoreCase(scheme); + return HTTPS.equals(scheme) || WEBSOCKET_SSL.equals(scheme); } public static boolean isSecure(UriComponents uri) { From 743e728f885ac0413e1a246c4f7e46828db4ae68 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 24 Jul 2014 11:28:24 +0200 Subject: [PATCH 0117/2020] Rename DefaultAttribute into Attribute --- .../netty/channel/ChannelManager.java | 35 ++++++++++++------- .../providers/netty/channel/Channels.java | 11 ++---- .../channel/pool/DefaultChannelPool.java | 6 ++-- .../providers/netty/handler/HttpProtocol.java | 2 +- .../providers/netty/handler/Processor.java | 20 +++++------ .../providers/netty/handler/Protocol.java | 2 +- .../netty/handler/WebSocketProtocol.java | 4 +-- .../netty/request/NettyConnectListener.java | 4 +-- .../netty/request/NettyRequestSender.java | 8 ++--- 9 files changed, 48 insertions(+), 44 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java index 047dfc2a26..647ced78fe 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java @@ -293,9 +293,9 @@ public void close() { openChannels.close(); for (Channel channel : openChannels) { - Object attachment = Channels.getDefaultAttribute(channel); - if (attachment instanceof NettyResponseFuture) { - NettyResponseFuture future = (NettyResponseFuture) attachment; + Object attribute = Channels.getAttribute(channel); + if (attribute instanceof NettyResponseFuture) { + NettyResponseFuture future = (NettyResponseFuture) attribute; future.cancelTimeouts(); } } @@ -362,16 +362,24 @@ public SslHandler createSslHandler(String peerHost, int peerPort) throws IOExcep return sslHandler; } + public SslHandler getSslHandler(ChannelPipeline pipeline) { + return (SslHandler) pipeline.get(SSL_HANDLER); + } + + private boolean isSslHandlerConfigured(ChannelPipeline pipeline) { + return pipeline.get(SSL_HANDLER) != null; + } + public void upgradeProtocol(ChannelPipeline pipeline, String scheme, String host, int port) throws IOException, GeneralSecurityException { if (pipeline.get(HTTP_HANDLER) != null) pipeline.remove(HTTP_HANDLER); if (isSecure(scheme)) - if (pipeline.get(SSL_HANDLER) == null) { + if (isSslHandlerConfigured(pipeline)) { + pipeline.addAfter(SSL_HANDLER, HTTP_HANDLER, newHttpClientCodec()); + } else { pipeline.addFirst(HTTP_HANDLER, newHttpClientCodec()); pipeline.addFirst(SSL_HANDLER, createSslHandler(host, port)); - } else { - pipeline.addAfter(SSL_HANDLER, HTTP_HANDLER, newHttpClientCodec()); } else @@ -392,13 +400,14 @@ public String getPoolKey(NettyResponseFuture future) { */ public void verifyChannelPipeline(ChannelPipeline pipeline, String scheme) throws IOException, GeneralSecurityException { - boolean isSecure = isSecure(scheme); - if (pipeline.get(SSL_HANDLER) != null) { - if (!isSecure) - pipeline.remove(SSL_HANDLER); + boolean sslHandlerConfigured = isSslHandlerConfigured(pipeline); + + if (isSecure(scheme)) { + if (!sslHandlerConfigured) + pipeline.addFirst(SSL_HANDLER, new SslInitializer(this)); - } else if (isSecure) - pipeline.addFirst(SSL_HANDLER, new SslInitializer(this)); + } else if (sslHandlerConfigured) + pipeline.remove(SSL_HANDLER); } public Bootstrap getBootstrap(UriComponents uri, boolean useProxy, boolean useSSl) { @@ -421,6 +430,6 @@ public void call() throws Exception { } public void drainChannel(final Channel channel, final NettyResponseFuture future) { - Channels.setDefaultAttribute(channel, newDrainCallback(future, channel, future.isKeepAlive(), getPoolKey(future))); + Channels.setAttribute(channel, newDrainCallback(future, channel, future.isKeepAlive(), getPoolKey(future))); } } 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 eda10d57d9..a193ae5f01 100755 --- 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 @@ -14,7 +14,6 @@ package org.asynchttpclient.providers.netty.channel; import io.netty.channel.Channel; -import io.netty.handler.ssl.SslHandler; import io.netty.util.Attribute; import io.netty.util.AttributeKey; @@ -24,21 +23,17 @@ public class Channels { private static final AttributeKey DEFAULT_ATTRIBUTE = AttributeKey.valueOf("default"); - public static SslHandler getSslHandler(Channel channel) { - return channel.pipeline().get(SslHandler.class); - } - - public static Object getDefaultAttribute(Channel channel) { + public static Object getAttribute(Channel channel) { Attribute attr = channel.attr(DEFAULT_ATTRIBUTE); return attr != null ? attr.get() : null; } - public static void setDefaultAttribute(Channel channel, Object o) { + public static void setAttribute(Channel channel, Object o) { channel.attr(DEFAULT_ATTRIBUTE).set(o); } public static void setDiscard(Channel channel) { - setDefaultAttribute(channel, DiscardEvent.INSTANCE); + setAttribute(channel, DiscardEvent.INSTANCE); } public static boolean isChannelValid(Channel channel) { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java index 3a04157660..c5cbbaad49 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/pool/DefaultChannelPool.java @@ -150,9 +150,9 @@ private List expiredChannels(ConcurrentLinkedQueue poo } private boolean isChannelCloseable(Channel channel) { - Object attachment = Channels.getDefaultAttribute(channel); - if (attachment instanceof NettyResponseFuture) { - NettyResponseFuture future = (NettyResponseFuture) attachment; + Object attribute = Channels.getAttribute(channel); + if (attribute instanceof NettyResponseFuture) { + NettyResponseFuture future = (NettyResponseFuture) attribute; if (!future.isDone()) LOGGER.error("Future not in appropriate state %s, not closing", future); } 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 380121278c..17d739f515 100755 --- 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 @@ -239,7 +239,7 @@ public void call() throws Exception { if (future.isKeepAlive() && HttpHeaders.isTransferEncodingChunked(response)) // We must make sure there is no bytes left // before executing the next request. - Channels.setDefaultAttribute(channel, callback); + Channels.setAttribute(channel, callback); else callback.call(); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java index 8cbd848de6..87a1a357df 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/handler/Processor.java @@ -64,12 +64,12 @@ public Processor(AsyncHttpClientConfig config,// public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { Channel channel = ctx.channel(); - Object attribute = Channels.getDefaultAttribute(channel); + Object attribute = Channels.getAttribute(channel); if (attribute instanceof Callback && msg instanceof LastHttpContent) { Callback ac = (Callback) attribute; ac.call(); - Channels.setDefaultAttribute(channel, DiscardEvent.INSTANCE); + Channels.setAttribute(channel, DiscardEvent.INSTANCE); } else if (attribute instanceof NettyResponseFuture) { NettyResponseFuture future = (NettyResponseFuture) attribute; @@ -98,16 +98,16 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception { LOGGER.trace("super.channelClosed", ex); } - Object attachment = Channels.getDefaultAttribute(channel); - LOGGER.debug("Channel Closed: {} with attachment {}", channel, attachment); + Object attribute = Channels.getAttribute(channel); + LOGGER.debug("Channel Closed: {} with attribute {}", channel, attribute); - if (attachment instanceof Callback) { - Callback callback = (Callback) attachment; - Channels.setDefaultAttribute(channel, callback.future()); + if (attribute instanceof Callback) { + Callback callback = (Callback) attribute; + Channels.setAttribute(channel, callback.future()); callback.call(); - } else if (attachment instanceof NettyResponseFuture) { - NettyResponseFuture future = NettyResponseFuture.class.cast(attachment); + } else if (attribute instanceof NettyResponseFuture) { + NettyResponseFuture future = NettyResponseFuture.class.cast(attribute); future.touch(); if (!config.getIOExceptionFilters().isEmpty() && requestSender.applyIoExceptionFiltersAndReplayRequest(future, CHANNEL_CLOSED_EXCEPTION, channel)) @@ -136,7 +136,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Excep LOGGER.debug("Unexpected I/O exception on channel {}", channel, cause); try { - Object attribute = Channels.getDefaultAttribute(channel); + Object attribute = Channels.getAttribute(channel); if (attribute instanceof NettyResponseFuture) { future = (NettyResponseFuture) attribute; future.attachChannel(null, false); 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 8eb8363a47..cffa03e2da 100755 --- 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 @@ -144,7 +144,7 @@ protected boolean exitAfterHandlingRedirect(// // We must make sure there is no bytes left before // executing the next request. // FIXME investigate this - Channels.setDefaultAttribute(channel, callback); + Channels.setAttribute(channel, 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? 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 8859822341..ba1ad04b02 100755 --- 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 @@ -164,7 +164,7 @@ public void handle(Channel channel, NettyResponseFuture future, Object e) thr @Override public void onError(Channel channel, Throwable e) { try { - Object attribute = Channels.getDefaultAttribute(channel); + Object attribute = Channels.getAttribute(channel); logger.warn("onError {}", e); if (!(attribute instanceof NettyResponseFuture)) { return; @@ -186,7 +186,7 @@ public void onError(Channel channel, Throwable e) { @Override public void onClose(Channel channel) { logger.trace("onClose {}"); - Object attribute = Channels.getDefaultAttribute(channel); + Object attribute = Channels.getAttribute(channel); if (!(attribute instanceof NettyResponseFuture)) return; diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java index b4254bc088..6268c6acc8 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java @@ -85,9 +85,9 @@ private void writeRequest(Channel channel) { } public void onFutureSuccess(final Channel channel) throws ConnectException { - Channels.setDefaultAttribute(channel, future); + Channels.setAttribute(channel, future); final HostnameVerifier hostnameVerifier = config.getHostnameVerifier(); - final SslHandler sslHandler = Channels.getSslHandler(channel); + final SslHandler sslHandler = channelManager.getSslHandler(channel.pipeline()); if (hostnameVerifier != null && sslHandler != null) { final String host = future.getURI().getHost(); sslHandler.handshakeFuture().addListener(new GenericFutureListener>() { 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 5dd4ee7674..9d4d2e54d0 100755 --- 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 @@ -198,7 +198,7 @@ private ListenableFuture sendRequestWithCachedChannel(Request request, Ur future.attachChannel(channel, false); LOGGER.debug("\nUsing cached Channel {}\n for request \n{}\n", channel, future.getNettyRequest().getHttpRequest()); - Channels.setDefaultAttribute(channel, future); + Channels.setAttribute(channel, future); try { writeRequest(future, channel); @@ -409,9 +409,9 @@ public boolean retry(NettyResponseFuture future, Channel channel) { channelManager.removeAll(channel); if (future == null) { - Object attachment = Channels.getDefaultAttribute(channel); - if (attachment instanceof NettyResponseFuture) - future = (NettyResponseFuture) attachment; + Object attribute = Channels.getAttribute(channel); + if (attribute instanceof NettyResponseFuture) + future = (NettyResponseFuture) attribute; } if (future != null && future.canBeReplayed()) { From e57085973be212c20dc5af95c5aa2e9fc49790c4 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 24 Jul 2014 14:16:40 +0200 Subject: [PATCH 0118/2020] clean up --- .../org/asynchttpclient/SSLEngineFactory.java | 20 +++++------ .../netty/NettyAsyncHttpProviderConfig.java | 35 ++++++++++++------- .../netty/channel/ChannelManager.java | 4 +-- .../netty/request/body/NettyBodyBody.java | 9 ++--- .../netty/request/body/NettyFileBody.java | 26 +++++++------- 5 files changed, 51 insertions(+), 43 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/SSLEngineFactory.java b/api/src/main/java/org/asynchttpclient/SSLEngineFactory.java index 0289a0e109..a7152bbb4a 100644 --- a/api/src/main/java/org/asynchttpclient/SSLEngineFactory.java +++ b/api/src/main/java/org/asynchttpclient/SSLEngineFactory.java @@ -1,17 +1,15 @@ /* - * Copyright 2010 Ning, Inc. + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. * - * 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: + * 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. * - * 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. + * 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; 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 f066ab51d5..9c1c6ed330 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java @@ -31,7 +31,8 @@ import java.util.Set; /** - * This class can be used to pass Netty's internal configuration options. See Netty documentation for more information. + * This class can be used to pass Netty's internal configuration options. See + * Netty documentation for more information. */ public class NettyAsyncHttpProviderConfig implements AsyncHttpProviderConfig, Object> { @@ -41,8 +42,10 @@ public class NettyAsyncHttpProviderConfig implements AsyncHttpProviderConfig name, Object value) { @@ -122,24 +125,19 @@ public NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { private AdditionalChannelInitializer wssAdditionalChannelInitializer; /** - * HttpClientCodec's maxInitialLineLength + * Allow configuring Netty's HttpClientCodecs. */ private int httpClientCodecMaxInitialLineLength = 4096; - - /** - * HttpClientCodec's maxHeaderSize - */ private int httpClientCodecMaxHeaderSize = 8192; - - /** - * HttpClientCodec's maxChunkSize - */ private int httpClientCodecMaxChunkSize = 8192; private ResponseBodyPartFactory bodyPartFactory = new EagerResponseBodyPartFactory(); private ChannelPool channelPool; + /** + * Allow one to disable zero copy for bodies and use chunking instead + */ private boolean disableZeroCopy; private Timer nettyTimer; @@ -148,6 +146,11 @@ public NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { private SSLEngineFactory sslEngineFactory; + /** + * chunkedFileChunkSize + */ + private int chunkedFileChunkSize = 8192; + public EventLoopGroup getEventLoopGroup() { return eventLoopGroup; } @@ -259,4 +262,12 @@ public SSLEngineFactory getSslEngineFactory() { public void setSslEngineFactory(SSLEngineFactory sslEngineFactory) { this.sslEngineFactory = sslEngineFactory; } + + public int getChunkedFileChunkSize() { + return chunkedFileChunkSize; + } + + public void setChunkedFileChunkSize(int chunkedFileChunkSize) { + this.chunkedFileChunkSize = chunkedFileChunkSize; + } } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java index 647ced78fe..bca5bdd1c1 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java @@ -362,11 +362,11 @@ public SslHandler createSslHandler(String peerHost, int peerPort) throws IOExcep return sslHandler; } - public SslHandler getSslHandler(ChannelPipeline pipeline) { + public static SslHandler getSslHandler(ChannelPipeline pipeline) { return (SslHandler) pipeline.get(SSL_HANDLER); } - private boolean isSslHandlerConfigured(ChannelPipeline pipeline) { + public static boolean isSslHandlerConfigured(ChannelPipeline pipeline) { return pipeline.get(SSL_HANDLER) != null; } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java index 7dac353e5b..43c39cfea7 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java @@ -18,6 +18,7 @@ import org.asynchttpclient.BodyGenerator; import org.asynchttpclient.RandomAccessBody; import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; +import org.asynchttpclient.providers.netty.channel.ChannelManager; import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.request.ProgressListener; @@ -38,11 +39,11 @@ public class NettyBodyBody implements NettyBody { private static final Logger LOGGER = LoggerFactory.getLogger(NettyBodyBody.class); private final Body body; - private final boolean disableZeroCopy; + private final NettyAsyncHttpProviderConfig nettyConfig; public NettyBodyBody(Body body, NettyAsyncHttpProviderConfig nettyConfig) { this.body = body; - disableZeroCopy = nettyConfig.isDisableZeroCopy(); + this.nettyConfig = nettyConfig; } public Body getBody() { @@ -61,9 +62,9 @@ public String getContentType() { @Override public void write(final Channel channel, NettyResponseFuture future, AsyncHttpClientConfig config) throws IOException { - Object msg; - if (Channels.getSslHandler(channel) == null && body instanceof RandomAccessBody && !disableZeroCopy) { + Object msg; + if (!ChannelManager.isSslHandlerConfigured(channel.pipeline()) && body instanceof RandomAccessBody && !nettyConfig.isDisableZeroCopy()) { msg = new BodyFileRegion((RandomAccessBody) body); } else { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyFileBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyFileBody.java index 3043bd3e32..7b5234e5cd 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyFileBody.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyFileBody.java @@ -13,14 +13,6 @@ */ package org.asynchttpclient.providers.netty.request.body; -import org.asynchttpclient.AsyncHttpClientConfig; -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.ProgressListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelProgressiveFuture; @@ -33,16 +25,22 @@ import java.io.IOException; import java.io.RandomAccessFile; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; +import org.asynchttpclient.providers.netty.channel.ChannelManager; +import org.asynchttpclient.providers.netty.future.NettyResponseFuture; +import org.asynchttpclient.providers.netty.request.ProgressListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class NettyFileBody implements NettyBody { private static final Logger LOGGER = LoggerFactory.getLogger(NettyFileBody.class); - public final static int MAX_BUFFERED_BYTES = 8192; - private final File file; private final long offset; private final long length; - private final boolean disableZeroCopy; + private final NettyAsyncHttpProviderConfig nettyConfig; public NettyFileBody(File file, NettyAsyncHttpProviderConfig nettyConfig) throws IOException { this(file, 0, file.length(), nettyConfig); @@ -55,7 +53,7 @@ public NettyFileBody(File file, long offset, long length, NettyAsyncHttpProvider this.file = file; this.offset = offset; this.length = length; - disableZeroCopy = nettyConfig.isDisableZeroCopy(); + this.nettyConfig = nettyConfig; } public File getFile() { @@ -82,8 +80,8 @@ public void write(Channel channel, NettyResponseFuture future, AsyncHttpClien try { ChannelFuture writeFuture; - if (Channels.getSslHandler(channel) != null || disableZeroCopy) { - writeFuture = channel.write(new ChunkedFile(raf, offset, length, MAX_BUFFERED_BYTES), channel.newProgressivePromise()); + if (ChannelManager.isSslHandlerConfigured(channel.pipeline()) || nettyConfig.isDisableZeroCopy()) { + writeFuture = channel.write(new ChunkedFile(raf, offset, length, nettyConfig.getChunkedFileChunkSize()), channel.newProgressivePromise()); } else { FileRegion region = new DefaultFileRegion(raf.getChannel(), offset, length); writeFuture = channel.write(region, channel.newProgressivePromise()); From 92023dabaacceee52c56d1702bec45fe11aad664 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 24 Jul 2014 14:37:01 +0200 Subject: [PATCH 0119/2020] Make transferEncoding configurable, fix FilePart constructors, close #647 --- .../multipart/AbstractFilePart.java | 9 ++++--- .../multipart/ByteArrayPart.java | 11 +++++--- .../asynchttpclient/multipart/FilePart.java | 27 ++++++++++--------- .../asynchttpclient/multipart/PartBase.java | 6 ++--- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/multipart/AbstractFilePart.java b/api/src/main/java/org/asynchttpclient/multipart/AbstractFilePart.java index a4d5f4e9b9..8e711d3a98 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/AbstractFilePart.java +++ b/api/src/main/java/org/asynchttpclient/multipart/AbstractFilePart.java @@ -55,9 +55,12 @@ public abstract class AbstractFilePart extends PartBase { * @param charset * the charset encoding for this part */ - public AbstractFilePart(String name, String contentType, String charset, String contentId) { - super(name, contentType == null ? DEFAULT_CONTENT_TYPE : contentType, charset, - DEFAULT_TRANSFER_ENCODING, contentId); + public AbstractFilePart(String name, String contentType, String charset, String contentId, String transfertEncoding) { + super(name,// + contentType == null ? DEFAULT_CONTENT_TYPE : contentType,// + charset,// + contentId,// + transfertEncoding == null ? DEFAULT_TRANSFER_ENCODING : transfertEncoding); } protected void visitDispositionHeader(PartVisitor visitor) throws IOException { diff --git a/api/src/main/java/org/asynchttpclient/multipart/ByteArrayPart.java b/api/src/main/java/org/asynchttpclient/multipart/ByteArrayPart.java index a7cd65337e..bc4660cb8e 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/ByteArrayPart.java +++ b/api/src/main/java/org/asynchttpclient/multipart/ByteArrayPart.java @@ -37,14 +37,17 @@ public ByteArrayPart(String name, byte[] bytes, String contentType, String chars } public ByteArrayPart(String name, byte[] bytes, String contentType, String charset, String fileName, String contentId) { - super(name, contentType, charset, contentId); - if (bytes == null) { + this(name, bytes, contentType, charset, fileName, contentId, null); + } + + public ByteArrayPart(String name, byte[] bytes, String contentType, String charset, String fileName, String contentId, String transferEncoding) { + super(name, contentType, charset, contentId, transferEncoding); + if (bytes == null) throw new NullPointerException("bytes"); - } this.bytes = bytes; setFileName(fileName); } - + @Override protected void sendData(OutputStream out) throws IOException { out.write(bytes); diff --git a/api/src/main/java/org/asynchttpclient/multipart/FilePart.java b/api/src/main/java/org/asynchttpclient/multipart/FilePart.java index ed4cb93f09..3ea77ed562 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/FilePart.java +++ b/api/src/main/java/org/asynchttpclient/multipart/FilePart.java @@ -31,36 +31,37 @@ public class FilePart extends AbstractFilePart { private final File file; public FilePart(String name, File file) { - this(name, file, null, null); + this(name, file, null); } public FilePart(String name, File file, String contentType) { - this(name, file, null, contentType, null); + this(name, file, contentType, null); } public FilePart(String name, File file, String contentType, String charset) { - this(name, file, null, contentType, charset, null); + this(name, file, contentType, charset, null); } public FilePart(String name, File file, String contentType, String charset, String fileName) { - this(name, file, null, contentType, charset, fileName); + this(name, file, contentType, charset, fileName, null); } public FilePart(String name, File file, String contentType, String charset, String fileName, String contentId) { - super(name, contentType, charset, contentId); - this.file = file; - if (file == null) { + this(name, file, contentType, charset, fileName, contentId, null); + } + + public FilePart(String name, File file, String contentType, String charset, String fileName, String contentId, String transferEncoding) { + super(name, contentType, charset, contentId, transferEncoding); + if (file == null) throw new NullPointerException("file"); - } - if (!file.isFile()) { + if (!file.isFile()) throw new IllegalArgumentException("File is not a normal file " + file.getAbsolutePath()); - } - if (!file.canRead()) { + if (!file.canRead()) throw new IllegalArgumentException("File is not readable " + file.getAbsolutePath()); - } + this.file = file; setFileName(fileName != null ? fileName : file.getName()); } - + @Override protected void sendData(OutputStream out) throws IOException { if (getDataLength() == 0) { diff --git a/api/src/main/java/org/asynchttpclient/multipart/PartBase.java b/api/src/main/java/org/asynchttpclient/multipart/PartBase.java index 2d9d274705..6273fce8ed 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/PartBase.java +++ b/api/src/main/java/org/asynchttpclient/multipart/PartBase.java @@ -55,15 +55,15 @@ public abstract class PartBase implements Part { * @param name The name of the part, or null * @param contentType The content type, or null * @param charSet The character encoding, or null - * @param transferEncoding The transfer encoding, or null * @param contentId The content id, or null + * @param transferEncoding The transfer encoding, or null */ - public PartBase(String name, String contentType, String charSet, String transferEncoding, String contentId) { + public PartBase(String name, String contentType, String charSet, String contentId, String transferEncoding) { this.name = name; this.contentType = contentType; this.charSet = charSet; - this.transferEncoding = transferEncoding; this.contentId = contentId; + this.transferEncoding = transferEncoding; } protected void visitStart(PartVisitor visitor, byte[] boundary) throws IOException { From 2a57096f0dc86d65028cf6ed5a19c454c81f5b68 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 24 Jul 2014 17:14:19 +0200 Subject: [PATCH 0120/2020] Drop useless broken test --- .../async/AsyncStreamHandlerTest.java | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java index 835bdf31be..4bd4d6fe6a 100644 --- a/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncStreamHandlerTest.java @@ -327,47 +327,6 @@ public String onCompleted() throws Exception { } } - @Test(groups = { "online", "default_provider" }) - public void asyncStream302WithBody() throws Exception { - AsyncHttpClient c = getAsyncHttpClient(null); - final AtomicReference statusCode = new AtomicReference(0); - final AtomicReference headers = new AtomicReference(); - try { - Future f = c.prepareGet("http://google.com/").execute(new AsyncHandlerAdapter() { - - public STATE onStatusReceived(HttpResponseStatus status) throws Exception { - statusCode.set(status.getStatusCode()); - return STATE.CONTINUE; - } - - @Override - public STATE onHeadersReceived(HttpResponseHeaders content) throws Exception { - headers.set(content.getHeaders()); - return STATE.CONTINUE; - } - - @Override - public STATE onBodyPartReceived(HttpResponseBodyPart content) throws Exception { - return STATE.CONTINUE; - } - - @Override - public String onCompleted() throws Exception { - return null; - } - }); - - f.get(20, TimeUnit.SECONDS); - assertEquals(statusCode.get().intValue(), 302); - FluentCaseInsensitiveStringsMap h = headers.get(); - assertNotNull(h); - assertEquals(h.getJoinedValue("content-type", ", ").toLowerCase(Locale.ENGLISH), TEXT_HTML_CONTENT_TYPE_WITH_UTF_8_CHARSET.toLowerCase(Locale.ENGLISH)); - - } finally { - c.close(); - } - } - @Test(groups = { "online", "default_provider" }) public void asyncStream302RedirectWithBody() throws Exception { AsyncHttpClient c = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setFollowRedirect(true).build()); From 98b66cbdd1594dbb61f2d1479f87e2eda35fd755 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Thu, 24 Jul 2014 17:18:36 +0200 Subject: [PATCH 0121/2020] Fix RemoteSiteTest.testGoogleComWithTimeout, redirect depends on geoloc --- api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java b/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java index f5b46b21fb..7e8ad5f21c 100644 --- a/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java +++ b/api/src/test/java/org/asynchttpclient/async/RemoteSiteTest.java @@ -119,7 +119,7 @@ public void testGoogleComWithTimeout() throws Exception { try { Response response = c.prepareGet("http://google.com/").execute().get(10, TimeUnit.SECONDS); assertNotNull(response); - assertEquals(response.getStatusCode(), 302); + assertTrue(response.getStatusCode() == 301 || response.getStatusCode() == 302); } finally { c.close(); } From 06a727f9f435bc5a7cac1efcca060bcd59605ec5 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 25 Jul 2014 08:53:43 +0200 Subject: [PATCH 0122/2020] Rename handshakeTimeoutInMillis into handshakeTimeout --- .../providers/netty/NettyAsyncHttpProviderConfig.java | 2 +- .../providers/netty/channel/ChannelManager.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 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 9c1c6ed330..185d7ea275 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java @@ -247,7 +247,7 @@ public void setNettyTimer(Timer nettyTimer) { this.nettyTimer = nettyTimer; } - public long getHandshakeTimeoutInMillis() { + public long getHandshakeTimeout() { return handshakeTimeoutInMillis; } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java index bca5bdd1c1..6fb3b03f3b 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java @@ -83,7 +83,7 @@ public class ChannelManager { private final Bootstrap webSocketBootstrap; private final Bootstrap secureWebSocketBootstrap; - private final long handshakeTimeoutInMillis; + private final long handshakeTimeout; private final ChannelPool channelPool; private final boolean maxConnectionsEnabled; @@ -144,7 +144,7 @@ public boolean remove(Object o) { channel2KeyPool = null; } - handshakeTimeoutInMillis = nettyConfig.getHandshakeTimeoutInMillis(); + handshakeTimeout = nettyConfig.getHandshakeTimeout(); // check if external EventLoopGroup is defined allowReleaseEventLoopGroup = nettyConfig.getEventLoopGroup() == null; @@ -356,8 +356,8 @@ public SslHandler createSslHandler(String peerHost, int peerPort) throws IOExcep } SslHandler sslHandler = new SslHandler(sslEngine); - if (handshakeTimeoutInMillis > 0) - sslHandler.setHandshakeTimeoutMillis(handshakeTimeoutInMillis); + if (handshakeTimeout > 0) + sslHandler.setHandshakeTimeoutMillis(handshakeTimeout); return sslHandler; } From a48e469214b450a38bc3734a240d6e9f01560199 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 25 Jul 2014 09:48:18 +0200 Subject: [PATCH 0123/2020] Rename Netty response elements --- .../netty/NettyAsyncHttpProviderConfig.java | 8 ++++---- .../providers/netty/handler/HttpProtocol.java | 14 +++++++------- .../providers/netty/handler/WebSocketProtocol.java | 10 +++++----- ...dyPart.java => EagerNettyResponseBodyPart.java} | 4 ++-- ...odyPart.java => LazyNettyResponseBodyPart.java} | 4 ++-- ...ponseHeaders.java => NettyResponseHeaders.java} | 6 +++--- ...esponseStatus.java => NettyResponseStatus.java} | 4 ++-- .../providers/netty/NettyAsyncResponseTest.java | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) rename providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/{EagerResponseBodyPart.java => EagerNettyResponseBodyPart.java} (93%) rename providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/{LazyResponseBodyPart.java => LazyNettyResponseBodyPart.java} (93%) rename providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/{ResponseHeaders.java => NettyResponseHeaders.java} (90%) rename providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/{ResponseStatus.java => NettyResponseStatus.java} (93%) 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 185d7ea275..36659292ad 100755 --- 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,8 +16,8 @@ import org.asynchttpclient.AsyncHttpProviderConfig; import org.asynchttpclient.SSLEngineFactory; import org.asynchttpclient.providers.netty.channel.pool.ChannelPool; -import org.asynchttpclient.providers.netty.response.EagerResponseBodyPart; -import org.asynchttpclient.providers.netty.response.LazyResponseBodyPart; +import org.asynchttpclient.providers.netty.response.EagerNettyResponseBodyPart; +import org.asynchttpclient.providers.netty.response.LazyNettyResponseBodyPart; import org.asynchttpclient.providers.netty.response.NettyResponseBodyPart; import io.netty.buffer.ByteBuf; @@ -102,7 +102,7 @@ public static class EagerResponseBodyPartFactory implements ResponseBodyPartFact @Override public NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { - return new EagerResponseBodyPart(buf, last); + return new EagerNettyResponseBodyPart(buf, last); } } @@ -110,7 +110,7 @@ public static class LazyResponseBodyPartFactory implements ResponseBodyPartFacto @Override public NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { - return new LazyResponseBodyPart(buf, last); + return new LazyNettyResponseBodyPart(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 17d739f515..98be1659cb 100755 --- 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 @@ -50,8 +50,8 @@ import org.asynchttpclient.providers.netty.request.NettyRequest; import org.asynchttpclient.providers.netty.request.NettyRequestSender; import org.asynchttpclient.providers.netty.response.NettyResponseBodyPart; -import org.asynchttpclient.providers.netty.response.ResponseHeaders; -import org.asynchttpclient.providers.netty.response.ResponseStatus; +import org.asynchttpclient.providers.netty.response.NettyResponseHeaders; +import org.asynchttpclient.providers.netty.response.NettyResponseStatus; import org.asynchttpclient.spnego.SpnegoEngine; import org.asynchttpclient.uri.UriComponents; @@ -345,7 +345,7 @@ private boolean exitAfterHandlingConnect(// return false; } - private boolean exitAfterHandlingStatus(Channel channel, NettyResponseFuture future, HttpResponse response, AsyncHandler handler, ResponseStatus status) + private boolean exitAfterHandlingStatus(Channel channel, NettyResponseFuture future, HttpResponse response, AsyncHandler handler, NettyResponseStatus status) throws IOException, Exception { if (!future.getAndSetStatusReceived(true) && handler.onStatusReceived(status) != STATE.CONTINUE) { finishUpdate(future, channel, HttpHeaders.isTransferEncodingChunked(response)); @@ -354,7 +354,7 @@ private boolean exitAfterHandlingStatus(Channel channel, NettyResponseFuture return false; } - private boolean exitAfterHandlingHeaders(Channel channel, NettyResponseFuture future, HttpResponse response, AsyncHandler handler, ResponseHeaders responseHeaders) + private boolean exitAfterHandlingHeaders(Channel channel, NettyResponseFuture future, HttpResponse response, AsyncHandler handler, NettyResponseHeaders responseHeaders) throws IOException, Exception { if (!response.headers().isEmpty() && handler.onHeadersReceived(responseHeaders) != STATE.CONTINUE) { finishUpdate(future, channel, HttpHeaders.isTransferEncodingChunked(response)); @@ -375,11 +375,11 @@ private boolean handleHttpResponse(final HttpResponse response, final Channel ch future.setKeepAlive(!HttpHeaders.Values.CLOSE.equalsIgnoreCase(response.headers().get(HttpHeaders.Names.CONNECTION))); - ResponseStatus status = new ResponseStatus(future.getURI(), config, response); + NettyResponseStatus status = new NettyResponseStatus(future.getURI(), config, response); int statusCode = response.getStatus().code(); Request request = future.getRequest(); Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); - ResponseHeaders responseHeaders = new ResponseHeaders(response.headers()); + NettyResponseHeaders responseHeaders = new NettyResponseHeaders(response.headers()); return exitAfterProcessingFilters(channel, future, handler, status, responseHeaders) || exitAfterHandling401(channel, future, response, request, statusCode, realm, proxyServer) || // @@ -428,7 +428,7 @@ public void handle(final Channel channel, final NettyResponseFuture future, f LastHttpContent lastChunk = (LastHttpContent) chunk; HttpHeaders trailingHeaders = lastChunk.trailingHeaders(); if (!trailingHeaders.isEmpty()) { - ResponseHeaders responseHeaders = new ResponseHeaders(future.getHttpHeaders(), trailingHeaders); + NettyResponseHeaders responseHeaders = new NettyResponseHeaders(future.getHttpHeaders(), trailingHeaders); interrupt = handler.onHeadersReceived(responseHeaders) != STATE.CONTINUE; } } 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 ba1ad04b02..e8e0917338 100755 --- 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 @@ -39,8 +39,8 @@ import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.request.NettyRequestSender; import org.asynchttpclient.providers.netty.response.NettyResponseBodyPart; -import org.asynchttpclient.providers.netty.response.ResponseHeaders; -import org.asynchttpclient.providers.netty.response.ResponseStatus; +import org.asynchttpclient.providers.netty.response.NettyResponseHeaders; +import org.asynchttpclient.providers.netty.response.NettyResponseStatus; import org.asynchttpclient.providers.netty.ws.NettyWebSocket; import org.asynchttpclient.util.StandardCharsets; import org.asynchttpclient.websocket.WebSocketUpgradeHandler; @@ -73,8 +73,8 @@ public void handle(Channel channel, NettyResponseFuture future, Object e) thr if (e instanceof HttpResponse) { HttpResponse response = (HttpResponse) e; - HttpResponseStatus status = new ResponseStatus(future.getURI(), config, response); - HttpResponseHeaders responseHeaders = new ResponseHeaders(response.headers()); + HttpResponseStatus status = new NettyResponseStatus(future.getURI(), config, response); + HttpResponseHeaders responseHeaders = new NettyResponseHeaders(response.headers()); if (exitAfterProcessingFilters(channel, future, handler, status, responseHeaders)) { return; @@ -93,7 +93,7 @@ public void handle(Channel channel, NettyResponseFuture future, Object e) thr boolean validConnection = c != null && c.equalsIgnoreCase(HttpHeaders.Values.UPGRADE); - status = new ResponseStatus(future.getURI(), config, response); + status = new NettyResponseStatus(future.getURI(), config, response); final boolean statusReceived = handler.onStatusReceived(status) == STATE.UPGRADE; if (!statusReceived) { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/EagerResponseBodyPart.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/EagerNettyResponseBodyPart.java similarity index 93% rename from providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/EagerResponseBodyPart.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/EagerNettyResponseBodyPart.java index afccb511ee..e993bd4975 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/EagerResponseBodyPart.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/EagerNettyResponseBodyPart.java @@ -26,11 +26,11 @@ * A callback class used when an HTTP response body is received. * Bytes are eagerly fetched from the ByteBuf */ -public class EagerResponseBodyPart extends NettyResponseBodyPart { +public class EagerNettyResponseBodyPart extends NettyResponseBodyPart { private final byte[] bytes; - public EagerResponseBodyPart(ByteBuf buf, boolean last) { + public EagerNettyResponseBodyPart(ByteBuf buf, boolean last) { super(last); bytes = byteBuf2Bytes(buf); } 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/LazyNettyResponseBodyPart.java similarity index 93% rename from providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/LazyResponseBodyPart.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/LazyNettyResponseBodyPart.java index 31091bd605..d3f136b9f1 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/LazyResponseBodyPart.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/LazyNettyResponseBodyPart.java @@ -22,13 +22,13 @@ /** * A callback class used when an HTTP response body is received. */ -public class LazyResponseBodyPart extends NettyResponseBodyPart { +public class LazyNettyResponseBodyPart extends NettyResponseBodyPart { 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) { + public LazyNettyResponseBodyPart(ByteBuf buf, boolean last) { super(last); this.buf = buf; } 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/NettyResponseHeaders.java similarity index 90% rename from providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseHeaders.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseHeaders.java index a40068965f..375f0cd3bf 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseHeaders.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseHeaders.java @@ -23,18 +23,18 @@ /** * A class that represent the HTTP headers. */ -public class ResponseHeaders extends HttpResponseHeaders { +public class NettyResponseHeaders extends HttpResponseHeaders { private final HttpHeaders responseHeaders; private final HttpHeaders trailingHeaders; private final FluentCaseInsensitiveStringsMap headers; // FIXME unused AsyncHttpProvider provider - public ResponseHeaders(HttpHeaders responseHeaders) { + public NettyResponseHeaders(HttpHeaders responseHeaders) { this(responseHeaders, null); } - public ResponseHeaders(HttpHeaders responseHeaders, HttpHeaders traillingHeaders) { + public NettyResponseHeaders(HttpHeaders responseHeaders, HttpHeaders traillingHeaders) { super(traillingHeaders != null); this.responseHeaders = responseHeaders; this.trailingHeaders = traillingHeaders; 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/NettyResponseStatus.java similarity index 93% rename from providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseStatus.java rename to providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseStatus.java index 1c74721d23..f869f35d1a 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/ResponseStatus.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/response/NettyResponseStatus.java @@ -27,11 +27,11 @@ /** * A class that represent the HTTP response' status line (code + text) */ -public class ResponseStatus extends HttpResponseStatus { +public class NettyResponseStatus extends HttpResponseStatus { private final HttpResponse response; - public ResponseStatus(UriComponents uri, AsyncHttpClientConfig config, HttpResponse response) { + public NettyResponseStatus(UriComponents uri, AsyncHttpClientConfig config, HttpResponse response) { super(uri, config); this.response = response; } 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 f35b77dafb..9158571853 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 @@ -19,7 +19,7 @@ import org.asynchttpclient.HttpResponseHeaders; import org.asynchttpclient.cookie.Cookie; import org.asynchttpclient.providers.netty.response.NettyResponse; -import org.asynchttpclient.providers.netty.response.ResponseStatus; +import org.asynchttpclient.providers.netty.response.NettyResponseStatus; import org.testng.annotations.Test; import java.text.SimpleDateFormat; @@ -42,7 +42,7 @@ public void testCookieParseExpires() { Date date = new Date(System.currentTimeMillis() + 60000); 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() { + NettyResponse response = new NettyResponse(new NettyResponseStatus(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, null), new HttpResponseHeaders() { + NettyResponse response = new NettyResponse(new NettyResponseStatus(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, null), new HttpResponseHeaders() { + NettyResponse response = new NettyResponse(new NettyResponseStatus(null, null, null), new HttpResponseHeaders() { @Override public FluentCaseInsensitiveStringsMap getHeaders() { return new FluentCaseInsensitiveStringsMap().add("Set-Cookie", cookieDef); From e19c611843dc12b2702045896e4c992dba78e6ca Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 25 Jul 2014 10:01:05 +0200 Subject: [PATCH 0124/2020] Fix log --- .../providers/netty/request/NettyConnectListener.java | 1 - .../providers/netty/request/NettyRequestSender.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java index 6268c6acc8..b463d70d55 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java @@ -126,7 +126,6 @@ public void onFutureFailure(Channel channel, Throwable cause) { && cause != null// && (cause instanceof ClosedChannelException || future.getState() != NettyResponseFuture.STATE.NEW || StackTraceInspector.abortOnDisconnectException(cause))) { - LOGGER.debug("Retrying {} ", future.getNettyRequest()); if (requestSender.retry(future, channel)) { return; } 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 9d4d2e54d0..207d0596e6 100755 --- 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 @@ -418,7 +418,7 @@ public boolean retry(NettyResponseFuture future, Channel channel) { future.setState(NettyResponseFuture.STATE.RECONNECTED); future.getAndSetStatusReceived(false); - LOGGER.debug("Trying to recover request {}\n", future.getNettyRequest()); + LOGGER.debug("Trying to recover request {}\n", future.getNettyRequest().getHttpRequest()); if (future.getAsyncHandler() instanceof AsyncHandlerExtensions) { AsyncHandlerExtensions.class.cast(future.getAsyncHandler()).onRetry(); } From 41e4c4716cb0876be4b5f97639572be86312a9e2 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 25 Jul 2014 14:18:38 +0200 Subject: [PATCH 0125/2020] Make acceptAnyCertificate doesn't disable HostnameVerifier, close #649 --- .../AsyncHttpClientConfig.java | 35 ++++++++++--------- .../AsyncHttpClientConfigBean.java | 1 - .../AsyncHttpClientConfigDefaults.java | 4 --- .../util/AllowAllHostnameVerifier.java | 22 ------------ 4 files changed, 19 insertions(+), 43 deletions(-) delete mode 100644 api/src/main/java/org/asynchttpclient/util/AllowAllHostnameVerifier.java diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java index 5146b997a6..8f1aefbf30 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -17,15 +17,6 @@ import static org.asynchttpclient.AsyncHttpClientConfigDefaults.*; -import org.asynchttpclient.date.TimeConverter; -import org.asynchttpclient.filter.IOExceptionFilter; -import org.asynchttpclient.filter.RequestFilter; -import org.asynchttpclient.filter.ResponseFilter; -import org.asynchttpclient.util.ProxyUtils; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLContext; - import java.io.IOException; import java.io.InputStream; import java.util.Collections; @@ -35,6 +26,16 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; + +import org.asynchttpclient.date.TimeConverter; +import org.asynchttpclient.filter.IOExceptionFilter; +import org.asynchttpclient.filter.RequestFilter; +import org.asynchttpclient.filter.ResponseFilter; +import org.asynchttpclient.util.DefaultHostnameVerifier; +import org.asynchttpclient.util.ProxyUtils; + /** * Configuration class to use with a {@link AsyncHttpClient}. System property can be also used to configure this * object default behavior by doing: @@ -538,7 +539,7 @@ public static class Builder { private int pooledConnectionIdleTimeout = defaultPooledConnectionIdleTimeout(); private int connectionTTL = defaultConnectionTTL(); private SSLContext sslContext; - private HostnameVerifier hostnameVerifier = defaultHostnameVerifier(); + private HostnameVerifier hostnameVerifier; private boolean acceptAnyCertificate = defaultAcceptAnyCertificate(); private boolean followRedirect = defaultFollowRedirect(); private int maxRedirects = defaultMaxRedirects(); @@ -1082,17 +1083,19 @@ public Builder(AsyncHttpClientConfig prototype) { */ public AsyncHttpClientConfig build() { - if (proxyServerSelector == null && useProxySelector) { + if (proxyServerSelector == null && useProxySelector) proxyServerSelector = ProxyUtils.getJdkDefaultProxyServerSelector(); - } - if (proxyServerSelector == null && useProxyProperties) { + if (proxyServerSelector == null && useProxyProperties) proxyServerSelector = ProxyUtils.createProxyServerSelector(System.getProperties()); - } - if (proxyServerSelector == null) { + if (proxyServerSelector == null) proxyServerSelector = ProxyServerSelector.NO_PROXY_SELECTOR; - } + + if (acceptAnyCertificate) + hostnameVerifier = null; + else if (hostnameVerifier == null) + hostnameVerifier = new DefaultHostnameVerifier(); return new AsyncHttpClientConfig(connectionTimeout,// maxConnections,// diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java index d68c6a8984..150187cea6 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java @@ -65,7 +65,6 @@ void configureDefaults() { disableUrlEncodingForBoundRequests = defaultDisableUrlEncodingForBoundRequests(); removeQueryParamOnRedirect = defaultRemoveQueryParamOnRedirect(); strict302Handling = defaultStrict302Handling(); - hostnameVerifier = defaultHostnameVerifier(); acceptAnyCertificate = defaultAcceptAnyCertificate(); if (defaultUseProxySelector()) { diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java index 8ecd353491..83fdcedf3e 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java @@ -113,10 +113,6 @@ public static boolean defaultRemoveQueryParamOnRedirect() { return getBoolean(ASYNC_CLIENT + "removeQueryParamOnRedirect", true); } - public static HostnameVerifier defaultHostnameVerifier() { - return new DefaultHostnameVerifier(); - } - public static boolean defaultSpdyEnabled() { return Boolean.getBoolean(ASYNC_CLIENT + "spdyEnabled"); } diff --git a/api/src/main/java/org/asynchttpclient/util/AllowAllHostnameVerifier.java b/api/src/main/java/org/asynchttpclient/util/AllowAllHostnameVerifier.java deleted file mode 100644 index d32424daf9..0000000000 --- a/api/src/main/java/org/asynchttpclient/util/AllowAllHostnameVerifier.java +++ /dev/null @@ -1,22 +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.util; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSession; - -public class AllowAllHostnameVerifier implements HostnameVerifier { - public boolean verify(String s, SSLSession sslSession) { - return true; - } -} From cec8290eb87c56100656f40c85416a0432ded6e1 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Fri, 25 Jul 2014 14:58:52 +0200 Subject: [PATCH 0126/2020] minor clean up --- .../request/timeout/ReadTimeoutTimerTask.java | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/ReadTimeoutTimerTask.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/ReadTimeoutTimerTask.java index c500bdd369..df77acd780 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/ReadTimeoutTimerTask.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/timeout/ReadTimeoutTimerTask.java @@ -37,36 +37,31 @@ public ReadTimeoutTimerTask(// @Override public void run(Timeout timeout) throws Exception { - if (requestSender.isClosed()) { + + if (requestSender.isClosed() || nettyResponseFuture.isDone()) { timeoutsHolder.cancel(); return; } - if (!nettyResponseFuture.isDone() && !nettyResponseFuture.isCancelled()) { - - long now = millisTime(); + long now = millisTime(); - long currentReadTimeoutInstant = readTimeout + nettyResponseFuture.getLastTouch(); - long durationBeforeCurrentReadTimeout = currentReadTimeoutInstant - now; + long currentReadTimeoutInstant = readTimeout + nettyResponseFuture.getLastTouch(); + long durationBeforeCurrentReadTimeout = currentReadTimeoutInstant - now; - if (durationBeforeCurrentReadTimeout <= 0L) { - // idleConnectionTimeout reached - String message = "Idle connection timeout to " + nettyResponseFuture.getChannelRemoteAddress() + " of " + readTimeout + " ms"; - long durationSinceLastTouch = now - nettyResponseFuture.getLastTouch(); - expire(message, durationSinceLastTouch); - nettyResponseFuture.setIdleConnectionTimeoutReached(); + if (durationBeforeCurrentReadTimeout <= 0L) { + // idleConnectionTimeout reached + String message = "Idle connection timeout to " + nettyResponseFuture.getChannelRemoteAddress() + " of " + readTimeout + " ms"; + long durationSinceLastTouch = now - nettyResponseFuture.getLastTouch(); + expire(message, durationSinceLastTouch); + nettyResponseFuture.setIdleConnectionTimeoutReached(); - } else if (currentReadTimeoutInstant < requestTimeoutInstant) { - // reschedule - timeoutsHolder.readTimeout = requestSender.newTimeout(this, durationBeforeCurrentReadTimeout); - - } else { - // otherwise, no need to reschedule: requestTimeout will happen sooner - timeoutsHolder.readTimeout = null; - } + } else if (currentReadTimeoutInstant < requestTimeoutInstant) { + // reschedule + timeoutsHolder.readTimeout = requestSender.newTimeout(this, durationBeforeCurrentReadTimeout); } else { - timeoutsHolder.cancel(); + // otherwise, no need to reschedule: requestTimeout will happen sooner + timeoutsHolder.readTimeout = null; } } } From eaf6109e3081d56df0da99b305d80d8d00bac3e9 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 28 Jul 2014 11:17:23 +0200 Subject: [PATCH 0127/2020] Fix NPE when passing a null charset, close #651 --- .../asynchttpclient/multipart/StringPart.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/multipart/StringPart.java b/api/src/main/java/org/asynchttpclient/multipart/StringPart.java index a6d60b7153..9ed500a561 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/StringPart.java +++ b/api/src/main/java/org/asynchttpclient/multipart/StringPart.java @@ -18,6 +18,8 @@ import java.nio.channels.WritableByteChannel; import java.nio.charset.Charset; +import org.asynchttpclient.util.StandardCharsets; + public class StringPart extends PartBase { /** @@ -28,7 +30,7 @@ public class StringPart extends PartBase { /** * Default charset of string parameters */ - public static final String DEFAULT_CHARSET = "US-ASCII"; + public static final Charset DEFAULT_CHARSET = StandardCharsets.US_ASCII; /** * Default transfer encoding of string parameters @@ -40,6 +42,10 @@ public class StringPart extends PartBase { */ private final byte[] content; + private static Charset charsetOrDefault(String charset) { + return charset == null ? DEFAULT_CHARSET : Charset.forName(charset); + } + public StringPart(String name, String value, String charset) { this(name, value, charset, null); } @@ -58,15 +64,13 @@ public StringPart(String name, String value, String charset) { */ public StringPart(String name, String value, String charset, String contentId) { - super(name, DEFAULT_CONTENT_TYPE, charset == null ? DEFAULT_CHARSET : charset, DEFAULT_TRANSFER_ENCODING, contentId); - if (value == null) { + super(name, DEFAULT_CONTENT_TYPE, charsetOrDefault(charset).name(), DEFAULT_TRANSFER_ENCODING, contentId); + if (value == null) throw new NullPointerException("value"); - } - if (value.indexOf(0) != -1) { + if (value.indexOf(0) != -1) // See RFC 2048, 2.8. "8bit Data" throw new IllegalArgumentException("NULs may not be present in string parts"); - } - content = value.getBytes(Charset.forName(charset)); + content = value.getBytes(charsetOrDefault(charset)); } /** From cb4b4c4cd8d77b30fb5704b31321e21bdca5cf76 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 28 Jul 2014 11:30:33 +0200 Subject: [PATCH 0128/2020] comment --- .../providers/netty/request/body/BodyChunkedInput.java | 1 + 1 file changed, 1 insertion(+) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyChunkedInput.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyChunkedInput.java index e48d6d8a13..a9b50bd404 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyChunkedInput.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyChunkedInput.java @@ -51,6 +51,7 @@ public ByteBuf readChunk(ChannelHandlerContext ctx) throws Exception { if (endOfInput) { return null; } else { + // FIXME pass a visitor so we can directly pass a pooled ByteBuf ByteBuffer buffer = ByteBuffer.allocate(chunkSize); long r = body.read(buffer); if (r < 0L) { From 30e106a242ea8d407406bb6e85d72dd02098547b Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 28 Jul 2014 11:40:40 +0200 Subject: [PATCH 0129/2020] Clean up warnings --- .../org/asynchttpclient/AsyncHttpClientConfigDefaults.java | 4 ---- .../java/org/asynchttpclient/util/ProxyHostnameChecker.java | 1 + .../test/java/org/asynchttpclient/async/util/TestUtils.java | 1 - .../asynchttpclient/websocket/CloseCodeReasonMessageTest.java | 4 ++-- .../providers/netty/request/NettyConnectListener.java | 2 +- .../providers/netty/request/NettyRequestSender.java | 2 ++ .../providers/netty/request/body/NettyBodyBody.java | 1 - 7 files changed, 6 insertions(+), 9 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java index 83fdcedf3e..36991bfa2d 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java @@ -14,10 +14,6 @@ import static org.asynchttpclient.util.MiscUtils.getBoolean; -import org.asynchttpclient.util.DefaultHostnameVerifier; - -import javax.net.ssl.HostnameVerifier; - public final class AsyncHttpClientConfigDefaults { private AsyncHttpClientConfigDefaults() { diff --git a/api/src/main/java/org/asynchttpclient/util/ProxyHostnameChecker.java b/api/src/main/java/org/asynchttpclient/util/ProxyHostnameChecker.java index 1355d9084f..3796911298 100644 --- a/api/src/main/java/org/asynchttpclient/util/ProxyHostnameChecker.java +++ b/api/src/main/java/org/asynchttpclient/util/ProxyHostnameChecker.java @@ -33,6 +33,7 @@ public ProxyHostnameChecker() { private Object getHostnameChecker() { final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try { + @SuppressWarnings("unchecked") final Class hostnameCheckerClass = (Class) classLoader.loadClass("sun.security.util.HostnameChecker"); final Method instanceMethod = hostnameCheckerClass.getMethod("getInstance", Byte.TYPE); return instanceMethod.invoke(null, TYPE_TLS); diff --git a/api/src/test/java/org/asynchttpclient/async/util/TestUtils.java b/api/src/test/java/org/asynchttpclient/async/util/TestUtils.java index 7c9b46c50b..ad87080b85 100644 --- a/api/src/test/java/org/asynchttpclient/async/util/TestUtils.java +++ b/api/src/test/java/org/asynchttpclient/async/util/TestUtils.java @@ -3,7 +3,6 @@ import static org.testng.Assert.assertEquals; import org.apache.commons.io.FileUtils; -import org.asynchttpclient.async.HostnameVerifierTest; import org.asynchttpclient.util.StandardCharsets; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; diff --git a/api/src/test/java/org/asynchttpclient/websocket/CloseCodeReasonMessageTest.java b/api/src/test/java/org/asynchttpclient/websocket/CloseCodeReasonMessageTest.java index a2b1519d9d..d5640b10ef 100644 --- a/api/src/test/java/org/asynchttpclient/websocket/CloseCodeReasonMessageTest.java +++ b/api/src/test/java/org/asynchttpclient/websocket/CloseCodeReasonMessageTest.java @@ -109,7 +109,7 @@ public void wrongStatusCode() throws Throwable { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference throwable = new AtomicReference(); - WebSocket websocket = c.prepareGet("http://apache.org/").execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketTextListener() { + c.prepareGet("http://apache.org/").execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketTextListener() { @Override public void onMessage(String message) { @@ -149,7 +149,7 @@ public void wrongProtocolCode() throws Throwable { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference throwable = new AtomicReference(); - WebSocket websocket = c.prepareGet("ws://www.google.com").execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketTextListener() { + c.prepareGet("ws://www.google.com").execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketTextListener() { @Override public void onMessage(String message) { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java index b463d70d55..b6d4ee27a7 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java @@ -87,7 +87,7 @@ private void writeRequest(Channel channel) { public void onFutureSuccess(final Channel channel) throws ConnectException { Channels.setAttribute(channel, future); final HostnameVerifier hostnameVerifier = config.getHostnameVerifier(); - final SslHandler sslHandler = channelManager.getSslHandler(channel.pipeline()); + final SslHandler sslHandler = ChannelManager.getSslHandler(channel.pipeline()); if (hostnameVerifier != null && sslHandler != null) { final String host = future.getURI().getHost(); sslHandler.handshakeFuture().addListener(new GenericFutureListener>() { 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 207d0596e6..68da7ab765 100755 --- 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 @@ -141,6 +141,7 @@ private ListenableFuture sendRequestWithCertainForceConnect(// * until we get a valid channel from the pool and it's still valid once the * request is built */ + @SuppressWarnings("unused") private ListenableFuture sendRequestThroughSslProxy(// Request request,// AsyncHandler asyncHandler,// @@ -443,6 +444,7 @@ public boolean applyIoExceptionFiltersAndReplayRequest(NettyResponseFuture fu boolean replayed = false; + @SuppressWarnings({ "unchecked", "rawtypes" }) FilterContext fc = new FilterContext.FilterContextBuilder().asyncHandler(future.getAsyncHandler()).request(future.getRequest()).ioException(e).build(); for (IOExceptionFilter asyncFilter : config.getIOExceptionFilters()) { try { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java index 43c39cfea7..94adb45b88 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java @@ -19,7 +19,6 @@ import org.asynchttpclient.RandomAccessBody; import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; import org.asynchttpclient.providers.netty.channel.ChannelManager; -import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.request.ProgressListener; import org.asynchttpclient.providers.netty.request.body.FeedableBodyGenerator.FeedListener; From 2ca8a3d0edf4842a1c07be6889467bfc9d727a87 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 28 Jul 2014 11:56:47 +0200 Subject: [PATCH 0130/2020] Have Parts take a java.nio.Charset instead of a String, close #652 --- .../multipart/AbstractFilePart.java | 3 ++- .../multipart/ByteArrayPart.java | 9 +++++---- .../asynchttpclient/multipart/FilePart.java | 15 ++++++++------- .../org/asynchttpclient/multipart/Part.java | 3 ++- .../asynchttpclient/multipart/PartBase.java | 19 ++++++++++--------- .../asynchttpclient/multipart/StringPart.java | 10 +++++----- .../async/AsyncProvidersBasicTest.java | 2 +- .../async/FastUnauthorizedUploadTest.java | 2 +- .../async/FilePartLargeFileTest.java | 4 ++-- .../async/MultipartUploadTest.java | 14 +++++++------- .../async/SimpleAsyncHttpClientTest.java | 4 ++-- .../multipart/MultipartBodyTest.java | 7 ++++--- 12 files changed, 49 insertions(+), 43 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/multipart/AbstractFilePart.java b/api/src/main/java/org/asynchttpclient/multipart/AbstractFilePart.java index 8e711d3a98..c3a2533603 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/AbstractFilePart.java +++ b/api/src/main/java/org/asynchttpclient/multipart/AbstractFilePart.java @@ -16,6 +16,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.charset.Charset; /** * This class is an adaptation of the Apache HttpClient implementation @@ -55,7 +56,7 @@ public abstract class AbstractFilePart extends PartBase { * @param charset * the charset encoding for this part */ - public AbstractFilePart(String name, String contentType, String charset, String contentId, String transfertEncoding) { + public AbstractFilePart(String name, String contentType, Charset charset, String contentId, String transfertEncoding) { super(name,// contentType == null ? DEFAULT_CONTENT_TYPE : contentType,// charset,// diff --git a/api/src/main/java/org/asynchttpclient/multipart/ByteArrayPart.java b/api/src/main/java/org/asynchttpclient/multipart/ByteArrayPart.java index bc4660cb8e..f3dc0f11c7 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/ByteArrayPart.java +++ b/api/src/main/java/org/asynchttpclient/multipart/ByteArrayPart.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.channels.WritableByteChannel; +import java.nio.charset.Charset; public class ByteArrayPart extends AbstractFilePart { @@ -28,19 +29,19 @@ public ByteArrayPart(String name, byte[] bytes, String contentType) { this(name, bytes, contentType, null); } - public ByteArrayPart(String name, byte[] bytes, String contentType, String charset) { + public ByteArrayPart(String name, byte[] bytes, String contentType, Charset charset) { this(name, bytes, contentType, charset, null); } - public ByteArrayPart(String name, byte[] bytes, String contentType, String charset, String fileName) { + public ByteArrayPart(String name, byte[] bytes, String contentType, Charset charset, String fileName) { this(name, bytes, contentType, charset, fileName, null); } - public ByteArrayPart(String name, byte[] bytes, String contentType, String charset, String fileName, String contentId) { + public ByteArrayPart(String name, byte[] bytes, String contentType, Charset charset, String fileName, String contentId) { this(name, bytes, contentType, charset, fileName, contentId, null); } - public ByteArrayPart(String name, byte[] bytes, String contentType, String charset, String fileName, String contentId, String transferEncoding) { + public ByteArrayPart(String name, byte[] bytes, String contentType, Charset charset, String fileName, String contentId, String transferEncoding) { super(name, contentType, charset, contentId, transferEncoding); if (bytes == null) throw new NullPointerException("bytes"); diff --git a/api/src/main/java/org/asynchttpclient/multipart/FilePart.java b/api/src/main/java/org/asynchttpclient/multipart/FilePart.java index 3ea77ed562..359ec21f72 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/FilePart.java +++ b/api/src/main/java/org/asynchttpclient/multipart/FilePart.java @@ -12,9 +12,6 @@ */ package org.asynchttpclient.multipart; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -23,6 +20,10 @@ import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.nio.channels.WritableByteChannel; +import java.nio.charset.Charset; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class FilePart extends AbstractFilePart { @@ -38,19 +39,19 @@ public FilePart(String name, File file, String contentType) { this(name, file, contentType, null); } - public FilePart(String name, File file, String contentType, String charset) { + public FilePart(String name, File file, String contentType, Charset charset) { this(name, file, contentType, charset, null); } - public FilePart(String name, File file, String contentType, String charset, String fileName) { + public FilePart(String name, File file, String contentType, Charset charset, String fileName) { this(name, file, contentType, charset, fileName, null); } - public FilePart(String name, File file, String contentType, String charset, String fileName, String contentId) { + public FilePart(String name, File file, String contentType, Charset charset, String fileName, String contentId) { this(name, file, contentType, charset, fileName, contentId, null); } - public FilePart(String name, File file, String contentType, String charset, String fileName, String contentId, String transferEncoding) { + public FilePart(String name, File file, String contentType, Charset charset, String fileName, String contentId, String transferEncoding) { super(name, contentType, charset, contentId, transferEncoding); if (file == null) throw new NullPointerException("file"); diff --git a/api/src/main/java/org/asynchttpclient/multipart/Part.java b/api/src/main/java/org/asynchttpclient/multipart/Part.java index 66758c7df8..4a5a2d90b8 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/Part.java +++ b/api/src/main/java/org/asynchttpclient/multipart/Part.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.channels.WritableByteChannel; +import java.nio.charset.Charset; public interface Part { @@ -89,7 +90,7 @@ public interface Part { * * @return the character encoding, or null to exclude the character encoding header */ - String getCharSet(); + Charset getCharset(); /** * Return the transfer encoding of this part. diff --git a/api/src/main/java/org/asynchttpclient/multipart/PartBase.java b/api/src/main/java/org/asynchttpclient/multipart/PartBase.java index 6273fce8ed..a241df72a9 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/PartBase.java +++ b/api/src/main/java/org/asynchttpclient/multipart/PartBase.java @@ -16,6 +16,7 @@ import java.io.IOException; import java.io.OutputStream; +import java.nio.charset.Charset; public abstract class PartBase implements Part { @@ -32,7 +33,7 @@ public abstract class PartBase implements Part { /** * The charset (part of Content-Type header) */ - private final String charSet; + private final Charset charset; /** * The Content-Transfer-Encoding header value. @@ -54,14 +55,14 @@ public abstract class PartBase implements Part { * * @param name The name of the part, or null * @param contentType The content type, or null - * @param charSet The character encoding, or null + * @param charset The character encoding, or null * @param contentId The content id, or null * @param transferEncoding The transfer encoding, or null */ - public PartBase(String name, String contentType, String charSet, String contentId, String transferEncoding) { + public PartBase(String name, String contentType, Charset charset, String contentId, String transferEncoding) { this.name = name; this.contentType = contentType; - this.charSet = charSet; + this.charset = charset; this.contentId = contentId; this.transferEncoding = transferEncoding; } @@ -89,10 +90,10 @@ protected void visitContentTypeHeader(PartVisitor visitor) throws IOException { visitor.withBytes(CRLF_BYTES); visitor.withBytes(CONTENT_TYPE_BYTES); visitor.withBytes(contentType.getBytes(US_ASCII)); - String charSet = getCharSet(); + Charset charSet = getCharset(); if (charSet != null) { visitor.withBytes(CHARSET_BYTES); - visitor.withBytes(charSet.getBytes(US_ASCII)); + visitor.withBytes(charset.name().getBytes(US_ASCII)); } } } @@ -186,7 +187,7 @@ public String toString() { .append(getClass().getSimpleName())// .append(" name=").append(getName())// .append(" contentType=").append(getContentType())// - .append(" charset=").append(getCharSet())// + .append(" charset=").append(getCharset())// .append(" tranferEncoding=").append(getTransferEncoding())// .append(" contentId=").append(getContentId())// .append(" dispositionType=").append(getDispositionType())// @@ -204,8 +205,8 @@ public String getContentType() { } @Override - public String getCharSet() { - return this.charSet; + public Charset getCharset() { + return this.charset; } @Override diff --git a/api/src/main/java/org/asynchttpclient/multipart/StringPart.java b/api/src/main/java/org/asynchttpclient/multipart/StringPart.java index 9ed500a561..eb6b90b329 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/StringPart.java +++ b/api/src/main/java/org/asynchttpclient/multipart/StringPart.java @@ -42,11 +42,11 @@ public class StringPart extends PartBase { */ private final byte[] content; - private static Charset charsetOrDefault(String charset) { - return charset == null ? DEFAULT_CHARSET : Charset.forName(charset); + private static Charset charsetOrDefault(Charset charset) { + return charset == null ? DEFAULT_CHARSET : charset; } - public StringPart(String name, String value, String charset) { + public StringPart(String name, String value, Charset charset) { this(name, value, charset, null); } @@ -62,9 +62,9 @@ public StringPart(String name, String value, String charset) { * @param contentId * the content id */ - public StringPart(String name, String value, String charset, String contentId) { + public StringPart(String name, String value, Charset charset, String contentId) { - super(name, DEFAULT_CONTENT_TYPE, charsetOrDefault(charset).name(), DEFAULT_TRANSFER_ENCODING, contentId); + super(name, DEFAULT_CONTENT_TYPE, charsetOrDefault(charset), DEFAULT_TRANSFER_ENCODING, contentId); if (value == null) throw new NullPointerException("value"); if (value.indexOf(0) != -1) diff --git a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java index 301074edc6..b4ae251628 100755 --- a/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java +++ b/api/src/test/java/org/asynchttpclient/async/AsyncProvidersBasicTest.java @@ -659,7 +659,7 @@ public void asyncDoPostMultiPartTest() throws Exception { try { final CountDownLatch l = new CountDownLatch(1); - Part p = new StringPart("foo", "bar", StandardCharsets.UTF_8.name()); + Part p = new StringPart("foo", "bar", StandardCharsets.UTF_8); client.preparePost(getTargetUrl()).addBodyPart(p).execute(new AsyncCompletionHandlerAdapter() { diff --git a/api/src/test/java/org/asynchttpclient/async/FastUnauthorizedUploadTest.java b/api/src/test/java/org/asynchttpclient/async/FastUnauthorizedUploadTest.java index 18011253cc..59ab429241 100644 --- a/api/src/test/java/org/asynchttpclient/async/FastUnauthorizedUploadTest.java +++ b/api/src/test/java/org/asynchttpclient/async/FastUnauthorizedUploadTest.java @@ -53,7 +53,7 @@ public void testUnauthorizedWhileUploading() throws Exception { AsyncHttpClient client = getAsyncHttpClient(null); try { - Response response = client.preparePut(getTargetUrl()).addBodyPart(new FilePart("test", file, "application/octet-stream", StandardCharsets.UTF_8.name())).execute() + Response response = client.preparePut(getTargetUrl()).addBodyPart(new FilePart("test", file, "application/octet-stream", StandardCharsets.UTF_8)).execute() .get(); assertEquals(response.getStatusCode(), 401); } finally { diff --git a/api/src/test/java/org/asynchttpclient/async/FilePartLargeFileTest.java b/api/src/test/java/org/asynchttpclient/async/FilePartLargeFileTest.java index 9783fc4fcf..b2f378f9c7 100644 --- a/api/src/test/java/org/asynchttpclient/async/FilePartLargeFileTest.java +++ b/api/src/test/java/org/asynchttpclient/async/FilePartLargeFileTest.java @@ -64,7 +64,7 @@ public void handle(String target, Request baseRequest, HttpServletRequest req, H public void testPutImageFile() throws Exception { AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeout(100 * 6000).build()); try { - Response response = client.preparePut(getTargetUrl()).addBodyPart(new FilePart("test", LARGE_IMAGE_FILE, "application/octet-stream", StandardCharsets.UTF_8.name())).execute().get(); + Response response = client.preparePut(getTargetUrl()).addBodyPart(new FilePart("test", LARGE_IMAGE_FILE, "application/octet-stream", StandardCharsets.UTF_8)).execute().get(); assertEquals(response.getStatusCode(), 200); } finally { client.close(); @@ -77,7 +77,7 @@ public void testPutLargeTextFile() throws Exception { AsyncHttpClient client = getAsyncHttpClient(null); try { - Response response = client.preparePut(getTargetUrl()).addBodyPart(new FilePart("test", file, "application/octet-stream", StandardCharsets.UTF_8.name())).execute().get(); + Response response = client.preparePut(getTargetUrl()).addBodyPart(new FilePart("test", file, "application/octet-stream", StandardCharsets.UTF_8)).execute().get(); assertEquals(response.getStatusCode(), 200); } finally { client.close(); diff --git a/api/src/test/java/org/asynchttpclient/async/MultipartUploadTest.java b/api/src/test/java/org/asynchttpclient/async/MultipartUploadTest.java index 1222e4408d..ad063a9782 100644 --- a/api/src/test/java/org/asynchttpclient/async/MultipartUploadTest.java +++ b/api/src/test/java/org/asynchttpclient/async/MultipartUploadTest.java @@ -174,16 +174,16 @@ public void testSendingSmallFilesAndByteArray() { RequestBuilder builder = new RequestBuilder("POST"); builder.setUrl("http://localhost/" + ":" + port1 + "/upload/bob"); - builder.addBodyPart(new FilePart("file1", testResource1File, "text/plain", StandardCharsets.UTF_8.name())); + builder.addBodyPart(new FilePart("file1", testResource1File, "text/plain", StandardCharsets.UTF_8)); builder.addBodyPart(new FilePart("file2", testResource2File, "application/x-gzip", null)); - builder.addBodyPart(new StringPart("Name", "Dominic", StandardCharsets.UTF_8.name())); - builder.addBodyPart(new FilePart("file3", testResource3File, "text/plain", StandardCharsets.UTF_8.name())); + builder.addBodyPart(new StringPart("Name", "Dominic", StandardCharsets.UTF_8)); + builder.addBodyPart(new FilePart("file3", testResource3File, "text/plain", StandardCharsets.UTF_8)); - builder.addBodyPart(new StringPart("Age", "3", AsyncHttpProviderUtils.DEFAULT_CHARSET.name())); - builder.addBodyPart(new StringPart("Height", "shrimplike", AsyncHttpProviderUtils.DEFAULT_CHARSET.name())); - builder.addBodyPart(new StringPart("Hair", "ridiculous", AsyncHttpProviderUtils.DEFAULT_CHARSET.name())); + builder.addBodyPart(new StringPart("Age", "3", AsyncHttpProviderUtils.DEFAULT_CHARSET)); + builder.addBodyPart(new StringPart("Height", "shrimplike", AsyncHttpProviderUtils.DEFAULT_CHARSET)); + builder.addBodyPart(new StringPart("Hair", "ridiculous", AsyncHttpProviderUtils.DEFAULT_CHARSET)); - builder.addBodyPart(new ByteArrayPart("file4", expectedContents.getBytes(StandardCharsets.UTF_8), "text/plain", StandardCharsets.UTF_8.name(), "bytearray.txt")); + builder.addBodyPart(new ByteArrayPart("file4", expectedContents.getBytes(StandardCharsets.UTF_8), "text/plain", StandardCharsets.UTF_8, "bytearray.txt")); Request r = builder.build(); diff --git a/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java b/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java index a7f4615795..59b4caa6ae 100644 --- a/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java +++ b/api/src/test/java/org/asynchttpclient/async/SimpleAsyncHttpClientTest.java @@ -307,7 +307,7 @@ public void testCloseMasterInvalidDerived() throws Exception { public void testMultiPartPut() throws Exception { SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setUrl(getTargetUrl() + "/multipart").build(); try { - Response response = client.put(new ByteArrayPart("baPart", "testMultiPart".getBytes(StandardCharsets.UTF_8), "application/test", StandardCharsets.UTF_8.name(), "fileName")).get(); + Response response = client.put(new ByteArrayPart("baPart", "testMultiPart".getBytes(StandardCharsets.UTF_8), "application/test", StandardCharsets.UTF_8, "fileName")).get(); String body = response.getResponseBody(); String contentType = response.getHeader("X-Content-Type"); @@ -331,7 +331,7 @@ public void testMultiPartPut() throws Exception { public void testMultiPartPost() throws Exception { SimpleAsyncHttpClient client = new SimpleAsyncHttpClient.Builder().setProviderClass(getProviderClass()).setUrl(getTargetUrl() + "/multipart").build(); try { - Response response = client.post(new ByteArrayPart("baPart", "testMultiPart".getBytes(StandardCharsets.UTF_8), "application/test", StandardCharsets.UTF_8.name(), "fileName")).get(); + Response response = client.post(new ByteArrayPart("baPart", "testMultiPart".getBytes(StandardCharsets.UTF_8), "application/test", StandardCharsets.UTF_8, "fileName")).get(); String body = response.getResponseBody(); String contentType = response.getHeader("X-Content-Type"); diff --git a/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java b/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java index 9de0bfa50f..40aa2db636 100644 --- a/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java +++ b/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java @@ -12,9 +12,10 @@ */ package org.asynchttpclient.multipart; +import static org.asynchttpclient.util.StandardCharsets.UTF_8; + import org.asynchttpclient.Body; import org.asynchttpclient.FluentCaseInsensitiveStringsMap; -import org.asynchttpclient.util.StandardCharsets; import org.testng.Assert; import org.testng.annotations.Test; @@ -37,10 +38,10 @@ public void testBasics() { parts.add(new FilePart("filePart", testFile)); // add a byte array - parts.add(new ByteArrayPart("baPart", "testMultiPart".getBytes(StandardCharsets.UTF_8), "application/test", StandardCharsets.UTF_8.name(), "fileName")); + parts.add(new ByteArrayPart("baPart", "testMultiPart".getBytes(UTF_8), "application/test", UTF_8, "fileName")); // add a string - parts.add(new StringPart("stringPart", "testString", StandardCharsets.UTF_8.name())); + parts.add(new StringPart("stringPart", "testString", UTF_8)); compareContentLength(parts); } From 54950d775882c72dce5e64cfc5d2fddd08e7390e Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Mon, 28 Jul 2014 13:32:08 +0200 Subject: [PATCH 0131/2020] minor clean up --- .../providers/netty/ws/NettyWebSocket.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java index 2a87ac293b..2c2940b168 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java @@ -196,29 +196,28 @@ public void onTextFragment(String message, boolean last) { } } - for (WebSocketListener l : listeners) { - if (l instanceof WebSocketTextListener) { + for (WebSocketListener listener : listeners) { + if (listener instanceof WebSocketTextListener) { + WebSocketTextListener textListener = (WebSocketTextListener) listener; try { if (!last) { - WebSocketTextListener.class.cast(l).onFragment(message, last); + textListener.onFragment(message, last); } else { if (textBuffer.length() > 0) { - WebSocketTextListener.class.cast(l).onFragment(message, last); - - WebSocketTextListener.class.cast(l).onMessage(textBuffer.append(message).toString()); + textListener.onFragment(message, last); + textListener.onMessage(textBuffer.append(message).toString()); } else { - WebSocketTextListener.class.cast(l).onMessage(message); + textListener.onMessage(message); } } } catch (Exception ex) { - l.onError(ex); + listener.onError(ex); } } } - if (last) { + if (last) textBuffer.setLength(0); - } } public void onError(Throwable t) { From 6807f5b146314fa09007706e2808178a8f396918 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 29 Jul 2014 11:58:32 +0200 Subject: [PATCH 0132/2020] minor clean up --- .../providers/netty/handler/WebSocketProtocol.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 e8e0917338..fbacb05374 100755 --- 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 @@ -86,12 +86,10 @@ public void handle(Channel channel, NettyResponseFuture future, Object e) thr 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(Locale.ENGLISH)); - } - - boolean validConnection = c != null && c.equalsIgnoreCase(HttpHeaders.Values.UPGRADE); + String connection = response.headers().get(HttpHeaders.Names.CONNECTION); + if (connection == null) + connection = response.headers().get(HttpHeaders.Names.CONNECTION.toLowerCase(Locale.ENGLISH)); + boolean validConnection = HttpHeaders.Values.UPGRADE.equalsIgnoreCase(connection); status = new NettyResponseStatus(future.getURI(), config, response); final boolean statusReceived = handler.onStatusReceived(status) == STATE.UPGRADE; From 4996b444ea42e3bdfd5f7fb9d650c5d00e99cc0e Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 29 Jul 2014 12:19:17 +0200 Subject: [PATCH 0133/2020] RandomAccessBody.transferTo count is actually unused (Long.MAX_VALUE) --- api/src/main/java/org/asynchttpclient/Body.java | 1 + .../main/java/org/asynchttpclient/RandomAccessBody.java | 4 +--- .../org/asynchttpclient/generators/FileBodyGenerator.java | 8 +++----- .../java/org/asynchttpclient/multipart/MultipartBody.java | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/Body.java b/api/src/main/java/org/asynchttpclient/Body.java index 5a39306b2d..9ee63021da 100644 --- a/api/src/main/java/org/asynchttpclient/Body.java +++ b/api/src/main/java/org/asynchttpclient/Body.java @@ -36,6 +36,7 @@ public interface Body extends Closeable { * @return The non-negative number of bytes actually read or {@code -1} if the body has been read completely. * @throws IOException If the chunk could not be read. */ + // FIXME introduce a visitor pattern so that Netty can pass a pooled buffer long read(ByteBuffer buffer) throws IOException; /** diff --git a/api/src/main/java/org/asynchttpclient/RandomAccessBody.java b/api/src/main/java/org/asynchttpclient/RandomAccessBody.java index 6cdc699485..fa74e9e684 100644 --- a/api/src/main/java/org/asynchttpclient/RandomAccessBody.java +++ b/api/src/main/java/org/asynchttpclient/RandomAccessBody.java @@ -26,13 +26,11 @@ public interface RandomAccessBody extends Body { * * @param position * The zero-based byte index from which to start the transfer, must not be negative. - * @param count - * The maximum number of bytes to transfer, must not be negative. * @param target * The destination channel to transfer the body chunk to, must not be {@code null}. * @return The non-negative number of bytes actually transferred. * @throws IOException * If the body chunk could not be transferred. */ - long transferTo(long position, long count, WritableByteChannel target) throws IOException; + long transferTo(long position, WritableByteChannel target) throws IOException; } diff --git a/api/src/main/java/org/asynchttpclient/generators/FileBodyGenerator.java b/api/src/main/java/org/asynchttpclient/generators/FileBodyGenerator.java index 007a993206..dd97ec217b 100644 --- a/api/src/main/java/org/asynchttpclient/generators/FileBodyGenerator.java +++ b/api/src/main/java/org/asynchttpclient/generators/FileBodyGenerator.java @@ -25,6 +25,7 @@ /** * Creates a request body from the contents of a file. */ +//Not used by Netty public class FileBodyGenerator implements BodyGenerator { private final File file; @@ -89,11 +90,8 @@ public long read(ByteBuffer buffer) throws IOException { return channel.read(buffer); } - public long transferTo(long position, long count, WritableByteChannel target) throws IOException { - if (count > length) { - count = length; - } - return channel.transferTo(position, count, target); + public long transferTo(long position, WritableByteChannel target) throws IOException { + return channel.transferTo(position, length, target); } public void close() throws IOException { diff --git a/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java b/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java index 1515409364..1b732b9390 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java +++ b/api/src/main/java/org/asynchttpclient/multipart/MultipartBody.java @@ -69,7 +69,7 @@ public String getContentType() { } // RandomAccessBody API, suited for HTTP but not for HTTPS - public long transferTo(long position, long count, WritableByteChannel target) throws IOException { + public long transferTo(long position, WritableByteChannel target) throws IOException { long overallLength = 0; From 6fd6367156fc8f129e1c50395eb626a05131cf77 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 29 Jul 2014 12:22:01 +0200 Subject: [PATCH 0134/2020] Remove useless synchronized --- .../multipart/MultipartUtils.java | 78 +++++++++---------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/multipart/MultipartUtils.java b/api/src/main/java/org/asynchttpclient/multipart/MultipartUtils.java index b546d1f7ba..d1f3ace5dc 100644 --- a/api/src/main/java/org/asynchttpclient/multipart/MultipartUtils.java +++ b/api/src/main/java/org/asynchttpclient/multipart/MultipartUtils.java @@ -110,50 +110,48 @@ public static long writeBytesToChannel(WritableByteChannel target, byte[] bytes) int written = 0; int maxSpin = 0; - synchronized (bytes) { - ByteBuffer message = ByteBuffer.wrap(bytes); - - if (target instanceof SocketChannel) { - final Selector selector = Selector.open(); - try { - final SocketChannel channel = (SocketChannel) target; - channel.register(selector, SelectionKey.OP_WRITE); - - while (written < bytes.length) { - selector.select(1000); - maxSpin++; - final Set selectedKeys = selector.selectedKeys(); - - for (SelectionKey key : selectedKeys) { - if (key.isWritable()) { - written += target.write(message); - maxSpin = 0; - } - } - if (maxSpin >= 10) { - throw new IOException("Unable to write on channel " + target); + ByteBuffer message = ByteBuffer.wrap(bytes); + + if (target instanceof SocketChannel) { + final Selector selector = Selector.open(); + try { + final SocketChannel channel = (SocketChannel) target; + channel.register(selector, SelectionKey.OP_WRITE); + + while (written < bytes.length) { + selector.select(1000); + maxSpin++; + final Set selectedKeys = selector.selectedKeys(); + + for (SelectionKey key : selectedKeys) { + if (key.isWritable()) { + written += target.write(message); + maxSpin = 0; } } - } finally { - selector.close(); + if (maxSpin >= 10) { + throw new IOException("Unable to write on channel " + target); + } } - } else { - while ((target.isOpen()) && (written < bytes.length)) { - long nWrite = target.write(message); - written += nWrite; - if (nWrite == 0 && maxSpin++ < 10) { - LOGGER.info("Waiting for writing..."); - try { - bytes.wait(1000); - } catch (InterruptedException e) { - LOGGER.trace(e.getMessage(), e); - } - } else { - if (maxSpin >= 10) { - throw new IOException("Unable to write on channel " + target); - } - maxSpin = 0; + } finally { + selector.close(); + } + } else { + while ((target.isOpen()) && (written < bytes.length)) { + long nWrite = target.write(message); + written += nWrite; + if (nWrite == 0 && maxSpin++ < 10) { + LOGGER.info("Waiting for writing..."); + try { + bytes.wait(1000); + } catch (InterruptedException e) { + LOGGER.trace(e.getMessage(), e); + } + } else { + if (maxSpin >= 10) { + throw new IOException("Unable to write on channel " + target); } + maxSpin = 0; } } } From f22ad62066cfd1bf3d7415251af2c2732db34acf Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 29 Jul 2014 12:23:36 +0200 Subject: [PATCH 0135/2020] Fix build --- .../providers/netty/request/body/BodyFileRegion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java index 49fabeef29..010625f95b 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java @@ -52,7 +52,7 @@ public long transfered() { @Override public long transferTo(WritableByteChannel target, long position) throws IOException { - long written = body.transferTo(position, Long.MAX_VALUE, target); + long written = body.transferTo(position, target); if (written > 0) { transfered += written; } From b5af568f378e5b037f74610787eb957bf0bfd162 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 29 Jul 2014 12:37:44 +0200 Subject: [PATCH 0136/2020] Only set Content-Length is strictly positive --- .../providers/netty/request/NettyRequestFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java index 2b26b06e2e..fa971cf010 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java @@ -281,7 +281,7 @@ public NettyRequest newNettyRequest(Request request, UriComponents uri, boolean } if (body != null) { - if (body.getContentLength() >= 0) + if (body.getContentLength() > 0) httpRequest.headers().set(HttpHeaders.Names.CONTENT_LENGTH, body.getContentLength()); else httpRequest.headers().set(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); From 613a83a3b6285bcacb0be27cb98222ec070ec40a Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 29 Jul 2014 13:43:53 +0200 Subject: [PATCH 0137/2020] closeSilently --- .../main/java/org/asynchttpclient/Body.java | 7 ---- .../org/asynchttpclient/BodyConsumer.java | 9 +----- .../SimpleAsyncHttpClient.java | 32 ++++++++----------- .../ResumableRandomAccessFileListener.java | 14 +++----- .../PropertiesBasedResumableProcessor.java | 17 ++++------ .../org/asynchttpclient/util/MiscUtils.java | 11 ++++++- .../multipart/MultipartBodyTest.java | 2 -- .../netty/request/body/BodyFileRegion.java | 10 +++--- .../netty/request/body/NettyBodyBody.java | 29 +++++++---------- .../netty/request/body/NettyFileBody.java | 19 ++--------- .../request/body/NettyInputStreamBody.java | 8 ++--- 11 files changed, 56 insertions(+), 102 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/Body.java b/api/src/main/java/org/asynchttpclient/Body.java index 9ee63021da..422e365e2f 100644 --- a/api/src/main/java/org/asynchttpclient/Body.java +++ b/api/src/main/java/org/asynchttpclient/Body.java @@ -38,11 +38,4 @@ public interface Body extends Closeable { */ // FIXME introduce a visitor pattern so that Netty can pass a pooled buffer long read(ByteBuffer buffer) throws IOException; - - /** - * Releases any resources associated with this body. - * - * @throws IOException - */ - void close() throws IOException; } diff --git a/api/src/main/java/org/asynchttpclient/BodyConsumer.java b/api/src/main/java/org/asynchttpclient/BodyConsumer.java index 0e93652bb1..190c586ae7 100644 --- a/api/src/main/java/org/asynchttpclient/BodyConsumer.java +++ b/api/src/main/java/org/asynchttpclient/BodyConsumer.java @@ -25,15 +25,8 @@ public interface BodyConsumer extends Closeable { /** * Consume the received bytes. * - * @param byteBuffer a {@link ByteBuffer} represntation of the response's chunk. + * @param byteBuffer a {@link ByteBuffer} representation of the response's chunk. * @throws IOException */ void consume(ByteBuffer byteBuffer) throws IOException; - - /** - * Invoked when all the response bytes has been processed. - * - * @throws IOException - */ - void close() throws IOException; } diff --git a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java index 25b9be4075..e90cee8f6b 100644 --- a/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java +++ b/api/src/main/java/org/asynchttpclient/SimpleAsyncHttpClient.java @@ -12,17 +12,7 @@ */ package org.asynchttpclient; -import org.asynchttpclient.cookie.Cookie; -import org.asynchttpclient.multipart.Part; -import org.asynchttpclient.resumable.ResumableAsyncHandler; -import org.asynchttpclient.resumable.ResumableIOExceptionFilter; -import org.asynchttpclient.simple.HeaderMap; -import org.asynchttpclient.simple.SimpleAHCTransferListener; -import org.asynchttpclient.uri.UriComponents; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.net.ssl.SSLContext; +import static org.asynchttpclient.util.MiscUtils.closeSilently; import java.io.Closeable; import java.io.IOException; @@ -32,6 +22,16 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; +import javax.net.ssl.SSLContext; + +import org.asynchttpclient.cookie.Cookie; +import org.asynchttpclient.multipart.Part; +import org.asynchttpclient.resumable.ResumableAsyncHandler; +import org.asynchttpclient.resumable.ResumableIOExceptionFilter; +import org.asynchttpclient.simple.HeaderMap; +import org.asynchttpclient.simple.SimpleAHCTransferListener; +import org.asynchttpclient.uri.UriComponents; + /** * Simple implementation of {@link AsyncHttpClient} and it's related builders ({@link AsyncHttpClientConfig}, * {@link Realm}, {@link ProxyServer} and {@link AsyncHandler}. You can @@ -64,7 +64,6 @@ */ public class SimpleAsyncHttpClient implements Closeable { - private final static Logger logger = LoggerFactory.getLogger(SimpleAsyncHttpClient.class); private final AsyncHttpClientConfig config; private final RequestBuilder requestBuilder; private AsyncHttpClient asyncHttpClient; @@ -778,13 +777,8 @@ public Response onCompleted(Response response) throws Exception { } private void closeConsumer() { - try { - if (bodyConsumer != null) { - bodyConsumer.close(); - } - } catch (IOException ex) { - logger.warn("Unable to close a BodyConsumer {}", bodyConsumer); - } + if (bodyConsumer != null) + closeSilently(bodyConsumer); } @Override diff --git a/api/src/main/java/org/asynchttpclient/extra/ResumableRandomAccessFileListener.java b/api/src/main/java/org/asynchttpclient/extra/ResumableRandomAccessFileListener.java index e8a63a0f6c..7b0dd64b95 100644 --- a/api/src/main/java/org/asynchttpclient/extra/ResumableRandomAccessFileListener.java +++ b/api/src/main/java/org/asynchttpclient/extra/ResumableRandomAccessFileListener.java @@ -12,6 +12,8 @@ */ package org.asynchttpclient.extra; +import static org.asynchttpclient.util.MiscUtils.closeSilently; + import org.asynchttpclient.resumable.ResumableListener; import java.io.IOException; @@ -52,13 +54,7 @@ public void onBytesReceived(ByteBuffer buffer) throws IOException { * {@inheritDoc} */ public void onAllBytesReceived() { - if (file != null) { - try { - file.close(); - } catch (IOException e) { - ; - } - } + closeSilently(file); } /** @@ -68,9 +64,7 @@ public long length() { try { return file.length(); } catch (IOException e) { - ; + return 0; } - return 0; } - } diff --git a/api/src/main/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcessor.java b/api/src/main/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcessor.java index 9f6959f608..030a95fb7f 100644 --- a/api/src/main/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcessor.java +++ b/api/src/main/java/org/asynchttpclient/resumable/PropertiesBasedResumableProcessor.java @@ -12,18 +12,19 @@ */ package org.asynchttpclient.resumable; -import org.asynchttpclient.util.StandardCharsets; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static org.asynchttpclient.util.MiscUtils.closeSilently; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.io.IOException; import java.util.Map; import java.util.Scanner; import java.util.concurrent.ConcurrentHashMap; +import org.asynchttpclient.util.StandardCharsets; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * A {@link org.asynchttpclient.resumable.ResumableAsyncHandler.ResumableProcessor} which use a properties file * to store the download index information. @@ -81,12 +82,8 @@ public void save(Map map) { } catch (Throwable e) { log.warn(e.getMessage(), e); } finally { - if (os != null) { - try { - os.close(); - } catch (IOException ignored) { - } - } + if (os != null) + closeSilently(os); } } diff --git a/api/src/main/java/org/asynchttpclient/util/MiscUtils.java b/api/src/main/java/org/asynchttpclient/util/MiscUtils.java index db508f8513..1943f9939b 100644 --- a/api/src/main/java/org/asynchttpclient/util/MiscUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/MiscUtils.java @@ -12,6 +12,8 @@ */ package org.asynchttpclient.util; +import java.io.Closeable; +import java.io.IOException; import java.util.Collection; import java.util.Map; @@ -46,6 +48,13 @@ public static boolean getBoolean(String systemPropName, boolean defaultValue) { } public static T withDefault(T value, T defaults) { - return value != null? value : value; + return value != null ? value : value; + } + + public static void closeSilently(Closeable closeable) { + try { + closeable.close(); + } catch (IOException e) { + } } } diff --git a/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java b/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java index 40aa2db636..3eeb05bbe6 100644 --- a/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java +++ b/api/src/test/java/org/asynchttpclient/multipart/MultipartBodyTest.java @@ -62,8 +62,6 @@ private static File getTestfile() { private static void compareContentLength(final List parts) { Assert.assertNotNull(parts); // get expected values - - // get real bytes final Body multipartBody = MultipartUtils.newMultipartBody(parts, new FluentCaseInsensitiveStringsMap()); final long expectedContentLength = multipartBody.getContentLength(); try { diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java index 010625f95b..b1d02275e4 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/BodyFileRegion.java @@ -13,6 +13,9 @@ */ package org.asynchttpclient.providers.netty.request.body; +import static org.asynchttpclient.util.MiscUtils.closeSilently; + + import org.asynchttpclient.RandomAccessBody; import io.netty.channel.FileRegion; @@ -20,7 +23,6 @@ import java.io.IOException; import java.nio.channels.WritableByteChannel; - /** * Adapts a {@link RandomAccessBody} to Netty's {@link FileRegion}. */ @@ -61,10 +63,6 @@ public long transferTo(WritableByteChannel target, long position) throws IOExcep @Override protected void deallocate() { - try { - body.close(); - } catch (IOException e) { - // we tried - } + closeSilently(body); } } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java index 94adb45b88..96957591ab 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyBodyBody.java @@ -13,6 +13,15 @@ */ package org.asynchttpclient.providers.netty.request.body; +import static org.asynchttpclient.util.MiscUtils.closeSilently; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelProgressiveFuture; +import io.netty.handler.codec.http.LastHttpContent; +import io.netty.handler.stream.ChunkedWriteHandler; + +import java.io.IOException; + import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.Body; import org.asynchttpclient.BodyGenerator; @@ -22,21 +31,9 @@ import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.request.ProgressListener; import org.asynchttpclient.providers.netty.request.body.FeedableBodyGenerator.FeedListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelProgressiveFuture; -import io.netty.handler.codec.http.LastHttpContent; -import io.netty.handler.stream.ChunkedWriteHandler; - -import java.io.IOException; public class NettyBodyBody implements NettyBody { - private static final Logger LOGGER = LoggerFactory.getLogger(NettyBodyBody.class); - private final Body body; private final NettyAsyncHttpProviderConfig nettyConfig; @@ -63,7 +60,7 @@ public String getContentType() { public void write(final Channel channel, NettyResponseFuture future, AsyncHttpClientConfig config) throws IOException { Object msg; - if (!ChannelManager.isSslHandlerConfigured(channel.pipeline()) && body instanceof RandomAccessBody && !nettyConfig.isDisableZeroCopy()) { + if (body instanceof RandomAccessBody && !ChannelManager.isSslHandlerConfigured(channel.pipeline()) && !nettyConfig.isDisableZeroCopy()) { msg = new BodyFileRegion((RandomAccessBody) body); } else { @@ -83,11 +80,7 @@ public void onContentAdded() { writeFuture.addListener(new ProgressListener(config, future.getAsyncHandler(), future, false, getContentLength()) { public void operationComplete(ChannelProgressiveFuture cf) { - try { - body.close(); - } catch (IOException e) { - LOGGER.warn("Failed to close request body: {}", e.getMessage(), e); - } + closeSilently(body); super.operationComplete(cf); } }); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyFileBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyFileBody.java index 7b5234e5cd..44a5349a7e 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyFileBody.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyFileBody.java @@ -13,6 +13,7 @@ */ package org.asynchttpclient.providers.netty.request.body; +import static org.asynchttpclient.util.MiscUtils.closeSilently; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelProgressiveFuture; @@ -30,13 +31,9 @@ import org.asynchttpclient.providers.netty.channel.ChannelManager; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.request.ProgressListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class NettyFileBody implements NettyBody { - private static final Logger LOGGER = LoggerFactory.getLogger(NettyFileBody.class); - private final File file; private final long offset; private final long length; @@ -88,23 +85,13 @@ public void write(Channel channel, NettyResponseFuture future, AsyncHttpClien } writeFuture.addListener(new ProgressListener(config, future.getAsyncHandler(), future, false, getContentLength()) { public void operationComplete(ChannelProgressiveFuture cf) { - try { - // FIXME probably useless in Netty 4 - raf.close(); - } catch (IOException e) { - LOGGER.warn("Failed to close request body: {}", e.getMessage(), e); - } + closeSilently(raf); super.operationComplete(cf); } }); channel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); } catch (IOException ex) { - if (raf != null) { - try { - raf.close(); - } catch (IOException e) { - } - } + closeSilently(raf); throw ex; } } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyInputStreamBody.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyInputStreamBody.java index 4412cd9bf4..9f7d841996 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyInputStreamBody.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/body/NettyInputStreamBody.java @@ -13,6 +13,8 @@ */ package org.asynchttpclient.providers.netty.request.body; +import static org.asynchttpclient.util.MiscUtils.closeSilently; + import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; import org.asynchttpclient.providers.netty.request.ProgressListener; @@ -69,11 +71,7 @@ public void write(Channel channel, NettyResponseFuture future, AsyncHttpClien channel.write(new ChunkedStream(is), channel.newProgressivePromise()).addListener( new ProgressListener(config, future.getAsyncHandler(), future, false, getContentLength()) { public void operationComplete(ChannelProgressiveFuture cf) { - try { - is.close(); - } catch (IOException e) { - LOGGER.warn("Failed to close request body: {}", e.getMessage(), e); - } + closeSilently(is); super.operationComplete(cf); } }); From f822b0a68601684dbb36e40c5bef1e2a9774d68f Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 29 Jul 2014 14:06:50 +0200 Subject: [PATCH 0138/2020] minor clean up --- .../providers/netty/ws/NettyWebSocket.java | 58 +++++++++---------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java index 2c2940b168..a9f6eefe63 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java @@ -34,7 +34,8 @@ import java.util.concurrent.ConcurrentLinkedQueue; public class NettyWebSocket implements WebSocket { - private final static Logger logger = LoggerFactory.getLogger(NettyWebSocket.class); + + private static final Logger LOGGER = LoggerFactory.getLogger(NettyWebSocket.class); private final Channel channel; private final ConcurrentLinkedQueue listeners = new ConcurrentLinkedQueue(); @@ -150,29 +151,28 @@ public void onBinaryFragment(byte[] message, boolean last) { if (byteBuffer.size() > maxBufferSize) { byteBuffer.reset(); - Exception e = new Exception("Exceeded Netty Web Socket maximum buffer size of " + getMaxBufferSize()); + Exception e = new Exception("Exceeded Netty Web Socket maximum buffer size of " + maxBufferSize); onError(e); - this.close(); + close(); return; } } - for (WebSocketListener l : listeners) { - if (l instanceof WebSocketByteListener) { + for (WebSocketListener listener : listeners) { + if (listener instanceof WebSocketByteListener) { + WebSocketByteListener byteListener = (WebSocketByteListener) listener; try { if (!last) { - WebSocketByteListener.class.cast(l).onFragment(message, last); + byteListener.onFragment(message, last); + } else if (byteBuffer.size() > 0) { + byteBuffer.write(message); + byteListener.onFragment(message, last); + byteListener.onMessage(byteBuffer.toByteArray()); } else { - if (byteBuffer.size() > 0) { - byteBuffer.write(message); - WebSocketByteListener.class.cast(l).onFragment(message, last); - WebSocketByteListener.class.cast(l).onMessage(byteBuffer.toByteArray()); - } else { - WebSocketByteListener.class.cast(l).onMessage(message); - } + byteListener.onMessage(message); } } catch (Exception ex) { - l.onError(ex); + listener.onError(ex); } } } @@ -189,9 +189,9 @@ public void onTextFragment(String message, boolean last) { if (textBuffer.length() > maxBufferSize) { textBuffer.setLength(0); - Exception e = new Exception("Exceeded Netty Web Socket maximum buffer size of " + getMaxBufferSize()); + Exception e = new Exception("Exceeded Netty Web Socket maximum buffer size of " + maxBufferSize); onError(e); - this.close(); + close(); return; } } @@ -202,13 +202,11 @@ public void onTextFragment(String message, boolean last) { try { if (!last) { textListener.onFragment(message, last); + } else if (textBuffer.length() > 0) { + textListener.onFragment(message, last); + textListener.onMessage(textBuffer.append(message).toString()); } else { - if (textBuffer.length() > 0) { - textListener.onFragment(message, last); - textListener.onMessage(textBuffer.append(message).toString()); - } else { - textListener.onMessage(message); - } + textListener.onMessage(message); } } catch (Exception ex) { listener.onError(ex); @@ -221,11 +219,11 @@ public void onTextFragment(String message, boolean last) { } public void onError(Throwable t) { - for (WebSocketListener l : listeners) { + for (WebSocketListener listener : listeners) { try { - l.onError(t); + listener.onError(t); } catch (Throwable t2) { - logger.error("", t2); + LOGGER.error("", t2); } } @@ -236,14 +234,14 @@ public void onClose() { } public void onClose(int code, String reason) { - for (WebSocketListener l : listeners) { + for (WebSocketListener listener : listeners) { try { - if (l instanceof WebSocketCloseCodeReasonListener) { - WebSocketCloseCodeReasonListener.class.cast(l).onClose(this, code, reason); + if (listener instanceof WebSocketCloseCodeReasonListener) { + WebSocketCloseCodeReasonListener.class.cast(listener).onClose(this, code, reason); } - l.onClose(this); + listener.onClose(this); } catch (Throwable t) { - l.onError(t); + listener.onError(t); } } } From d7db2b79b8f9aeb2f4fbe01c3d69a62a07aead21 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 29 Jul 2014 14:19:37 +0200 Subject: [PATCH 0139/2020] minor clean up --- .../websocket/WebSocketUpgradeHandler.java | 87 +++++-------------- .../providers/netty/ws/NettyWebSocket.java | 7 +- 2 files changed, 24 insertions(+), 70 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketUpgradeHandler.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketUpgradeHandler.java index 69e7664d76..bbf13b0167 100644 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketUpgradeHandler.java +++ b/api/src/main/java/org/asynchttpclient/websocket/WebSocketUpgradeHandler.java @@ -27,22 +27,13 @@ public class WebSocketUpgradeHandler implements UpgradeHandler, AsyncHandler { private WebSocket webSocket; - private final ConcurrentLinkedQueue l; - @SuppressWarnings("unused") - private final String protocol; - @SuppressWarnings("unused") - private final long maxByteSize; - @SuppressWarnings("unused") - private final long maxTextSize; + private final ConcurrentLinkedQueue listeners; private final AtomicBoolean ok = new AtomicBoolean(false); private final AtomicBoolean onSuccessCalled = new AtomicBoolean(false); private int status; - private WebSocketUpgradeHandler(Builder b) { - l = b.l; - protocol = b.protocol; - maxByteSize = b.maxByteSize; - maxTextSize = b.maxTextSize; + public WebSocketUpgradeHandler(ConcurrentLinkedQueue listeners) { + this.listeners = listeners; } /** @@ -93,8 +84,9 @@ public final STATE onHeadersReceived(HttpResponseHeaders headers) throws Excepti public final WebSocket onCompleted() throws Exception { if (status != 101) { - for (WebSocketListener w : l) { - w.onError(new IllegalStateException(String.format("Invalid Status Code %d", status))); + IllegalStateException e = new IllegalStateException("Invalid Status Code " + status); + for (WebSocketListener listener : listeners) { + listener.onError(e); } return null; } @@ -111,9 +103,9 @@ public final WebSocket onCompleted() throws Exception { @Override public final void onSuccess(WebSocket webSocket) { this.webSocket = webSocket; - for (WebSocketListener w : l) { - webSocket.addWebSocketListener(w); - w.onOpen(webSocket); + for (WebSocketListener listener : listeners) { + webSocket.addWebSocketListener(listener); + listener.onOpen(webSocket); } ok.set(true); } @@ -123,11 +115,11 @@ public final void onSuccess(WebSocket webSocket) { */ @Override public final void onFailure(Throwable t) { - for (WebSocketListener w : l) { + for (WebSocketListener listener : listeners) { if (!ok.get() && webSocket != null) { - webSocket.addWebSocketListener(w); + webSocket.addWebSocketListener(listener); } - w.onError(t); + listener.onError(t); } } @@ -136,13 +128,13 @@ public final void onClose(WebSocket webSocket, int status, String reasonPhrase) if (this.webSocket == null) this.webSocket = webSocket; - for (WebSocketListener w : l) { + for (WebSocketListener listener : listeners) { if (webSocket != null) { - webSocket.addWebSocketListener(w); + webSocket.addWebSocketListener(listener); } - w.onClose(webSocket); - if (w instanceof WebSocketCloseCodeReasonListener) { - WebSocketCloseCodeReasonListener.class.cast(w).onClose(webSocket, status, reasonPhrase); + listener.onClose(webSocket); + if (listener instanceof WebSocketCloseCodeReasonListener) { + WebSocketCloseCodeReasonListener.class.cast(listener).onClose(webSocket, status, reasonPhrase); } } } @@ -151,10 +143,8 @@ public final void onClose(WebSocket webSocket, int status, String reasonPhrase) * Build a {@link WebSocketUpgradeHandler} */ public final static class Builder { - private ConcurrentLinkedQueue l = new ConcurrentLinkedQueue(); - private String protocol = ""; - private long maxByteSize = 8192; - private long maxTextSize = 8192; + + private ConcurrentLinkedQueue listeners = new ConcurrentLinkedQueue(); /** * Add a {@link WebSocketListener} that will be added to the {@link WebSocket} @@ -163,7 +153,7 @@ public final static class Builder { * @return this */ public Builder addWebSocketListener(WebSocketListener listener) { - l.add(listener); + listeners.add(listener); return this; } @@ -174,40 +164,7 @@ public Builder addWebSocketListener(WebSocketListener listener) { * @return this */ public Builder removeWebSocketListener(WebSocketListener listener) { - l.remove(listener); - return this; - } - - /** - * Set the WebSocket protocol. - * - * @param protocol the WebSocket protocol. - * @return this - */ - public Builder setProtocol(String protocol) { - this.protocol = protocol; - return this; - } - - /** - * Set the max size of the WebSocket byte message that will be sent. - * - * @param maxByteSize max size of the WebSocket byte message - * @return this - */ - public Builder setMaxByteSize(long maxByteSize) { - this.maxByteSize = maxByteSize; - return this; - } - - /** - * Set the max size of the WebSocket text message that will be sent. - * - * @param maxTextSize max size of the WebSocket byte message - * @return this - */ - public Builder setMaxTextSize(long maxTextSize) { - this.maxTextSize = maxTextSize; + listeners.remove(listener); return this; } @@ -217,7 +174,7 @@ public Builder setMaxTextSize(long maxTextSize) { * @return a {@link WebSocketUpgradeHandler} */ public WebSocketUpgradeHandler build() { - return new WebSocketUpgradeHandler(this); + return new WebSocketUpgradeHandler(listeners); } } } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java index a9f6eefe63..eaadbf589e 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java @@ -103,11 +103,8 @@ public int getMaxBufferSize() { return maxBufferSize; } - public void setMaxBufferSize(int bufferSize) { - maxBufferSize = bufferSize; - - if (maxBufferSize < 8192) - maxBufferSize = 8192; + public void setMaxBufferSize(int maxBufferSize) { + this.maxBufferSize = Math.max(maxBufferSize, 8192); } @Override From 55d6f217fe87cc3ce84613ea62ed92202da52f98 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Tue, 29 Jul 2014 14:42:26 +0200 Subject: [PATCH 0140/2020] WebSocketUpgradeHandler doesn't need to be threadsafe --- .../websocket/WebSocketUpgradeHandler.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketUpgradeHandler.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketUpgradeHandler.java index bbf13b0167..c8fef8a02f 100644 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketUpgradeHandler.java +++ b/api/src/main/java/org/asynchttpclient/websocket/WebSocketUpgradeHandler.java @@ -12,27 +12,28 @@ */ package org.asynchttpclient.websocket; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.HttpResponseBodyPart; import org.asynchttpclient.HttpResponseHeaders; import org.asynchttpclient.HttpResponseStatus; import org.asynchttpclient.UpgradeHandler; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicBoolean; - /** * An {@link AsyncHandler} which is able to execute WebSocket upgrade. Use the Builder for configuring WebSocket options. */ public class WebSocketUpgradeHandler implements UpgradeHandler, AsyncHandler { private WebSocket webSocket; - private final ConcurrentLinkedQueue listeners; + private final List listeners; private final AtomicBoolean ok = new AtomicBoolean(false); private final AtomicBoolean onSuccessCalled = new AtomicBoolean(false); private int status; - public WebSocketUpgradeHandler(ConcurrentLinkedQueue listeners) { + public WebSocketUpgradeHandler(List listeners) { this.listeners = listeners; } @@ -144,7 +145,7 @@ public final void onClose(WebSocket webSocket, int status, String reasonPhrase) */ public final static class Builder { - private ConcurrentLinkedQueue listeners = new ConcurrentLinkedQueue(); + private List listeners = new ArrayList(); /** * Add a {@link WebSocketListener} that will be added to the {@link WebSocket} From e67c8625e57034de8948f5e3c30ddf40bae2dc96 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 30 Jul 2014 01:58:12 +0200 Subject: [PATCH 0141/2020] Turn NettyWebSocket into an abstract class and introduce a Factory, close #656 --- .../websocket/WebSocketUpgradeHandler.java | 6 +- .../netty/NettyAsyncHttpProviderConfig.java | 46 +++-- .../netty/handler/WebSocketProtocol.java | 7 +- .../netty/ws/DefaultNettyWebSocket.java | 124 ++++++++++++++ .../providers/netty/ws/NettyWebSocket.java | 162 ++++-------------- 5 files changed, 202 insertions(+), 143 deletions(-) create mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/DefaultNettyWebSocket.java diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketUpgradeHandler.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketUpgradeHandler.java index c8fef8a02f..b612c9b0ac 100644 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketUpgradeHandler.java +++ b/api/src/main/java/org/asynchttpclient/websocket/WebSocketUpgradeHandler.java @@ -30,7 +30,7 @@ public class WebSocketUpgradeHandler implements UpgradeHandler, Async private WebSocket webSocket; private final List listeners; private final AtomicBoolean ok = new AtomicBoolean(false); - private final AtomicBoolean onSuccessCalled = new AtomicBoolean(false); + private boolean onSuccessCalled; private int status; public WebSocketUpgradeHandler(List listeners) { @@ -46,7 +46,9 @@ public final void onThrowable(Throwable t) { } public boolean touchSuccess() { - return onSuccessCalled.getAndSet(true); + boolean prev = onSuccessCalled; + onSuccessCalled = true; + return prev; } /** 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 36659292ad..95eed7889f 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java @@ -13,13 +13,6 @@ */ package org.asynchttpclient.providers.netty; -import org.asynchttpclient.AsyncHttpProviderConfig; -import org.asynchttpclient.SSLEngineFactory; -import org.asynchttpclient.providers.netty.channel.pool.ChannelPool; -import org.asynchttpclient.providers.netty.response.EagerNettyResponseBodyPart; -import org.asynchttpclient.providers.netty.response.LazyNettyResponseBodyPart; -import org.asynchttpclient.providers.netty.response.NettyResponseBodyPart; - import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelOption; @@ -30,6 +23,15 @@ import java.util.Map; import java.util.Set; +import org.asynchttpclient.AsyncHttpProviderConfig; +import org.asynchttpclient.SSLEngineFactory; +import org.asynchttpclient.providers.netty.channel.pool.ChannelPool; +import org.asynchttpclient.providers.netty.response.EagerNettyResponseBodyPart; +import org.asynchttpclient.providers.netty.response.LazyNettyResponseBodyPart; +import org.asynchttpclient.providers.netty.response.NettyResponseBodyPart; +import org.asynchttpclient.providers.netty.ws.DefaultNettyWebSocket; +import org.asynchttpclient.providers.netty.ws.NettyWebSocket; + /** * This class can be used to pass Netty's internal configuration options. See * Netty documentation for more information. @@ -114,6 +116,18 @@ public NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { } } + public static interface NettyWebSocketFactory { + NettyWebSocket newNettyWebSocket(Channel channel); + } + + public class DefaultNettyWebSocketFactory implements NettyWebSocketFactory { + + @Override + public NettyWebSocket newNettyWebSocket(Channel channel) { + return new DefaultNettyWebSocket(channel); + } + } + /** * Allow configuring the Netty's event loop. */ @@ -142,7 +156,7 @@ public NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { private Timer nettyTimer; - private long handshakeTimeoutInMillis; + private long handshakeTimeout; private SSLEngineFactory sslEngineFactory; @@ -151,6 +165,8 @@ public NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { */ private int chunkedFileChunkSize = 8192; + private NettyWebSocketFactory nettyWebSocketFactory = new DefaultNettyWebSocketFactory(); + public EventLoopGroup getEventLoopGroup() { return eventLoopGroup; } @@ -248,11 +264,11 @@ public void setNettyTimer(Timer nettyTimer) { } public long getHandshakeTimeout() { - return handshakeTimeoutInMillis; + return handshakeTimeout; } - public void setHandshakeTimeoutInMillis(long handshakeTimeoutInMillis) { - this.handshakeTimeoutInMillis = handshakeTimeoutInMillis; + public void setHandshakeTimeout(long handshakeTimeout) { + this.handshakeTimeout = handshakeTimeout; } public SSLEngineFactory getSslEngineFactory() { @@ -270,4 +286,12 @@ public int getChunkedFileChunkSize() { public void setChunkedFileChunkSize(int chunkedFileChunkSize) { this.chunkedFileChunkSize = chunkedFileChunkSize; } + + public NettyWebSocketFactory getNettyWebSocketFactory() { + return nettyWebSocketFactory; + } + + public void setNettyWebSocketFactory(NettyWebSocketFactory nettyWebSocketFactory) { + this.nettyWebSocketFactory = nettyWebSocketFactory; + } } 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 fbacb05374..bdedadd68c 100755 --- 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 @@ -42,7 +42,6 @@ import org.asynchttpclient.providers.netty.response.NettyResponseHeaders; import org.asynchttpclient.providers.netty.response.NettyResponseStatus; import org.asynchttpclient.providers.netty.ws.NettyWebSocket; -import org.asynchttpclient.util.StandardCharsets; import org.asynchttpclient.websocket.WebSocketUpgradeHandler; public final class WebSocketProtocol extends Protocol { @@ -59,7 +58,7 @@ public WebSocketProtocol(ChannelManager channelManager,// private void invokeOnSucces(Channel channel, WebSocketUpgradeHandler h) { if (!h.touchSuccess()) { try { - h.onSuccess(new NettyWebSocket(channel)); + h.onSuccess(nettyConfig.getNettyWebSocketFactory().newNettyWebSocket(channel)); } catch (Exception ex) { logger.warn("onSuccess unexpected exception", ex); } @@ -140,9 +139,9 @@ public void handle(Channel channel, NettyResponseFuture future, Object e) thr handler.onBodyPartReceived(rp); if (frame instanceof BinaryWebSocketFrame) { - webSocket.onBinaryFragment(rp.getBodyPartBytes(), frame.isFinalFragment()); + webSocket.onBinaryFragment(rp); } else { - webSocket.onTextFragment(buf.toString(StandardCharsets.UTF_8), frame.isFinalFragment()); + webSocket.onTextFragment(rp); } } finally { buf.release(); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/DefaultNettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/DefaultNettyWebSocket.java new file mode 100644 index 0000000000..11b8cfd133 --- /dev/null +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/DefaultNettyWebSocket.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2014 AsyncHttpClient Project. 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.ws; + + +import io.netty.channel.Channel; + +import java.io.ByteArrayOutputStream; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.util.StandardCharsets; +import org.asynchttpclient.websocket.WebSocketByteListener; +import org.asynchttpclient.websocket.WebSocketListener; +import org.asynchttpclient.websocket.WebSocketTextListener; + +public class DefaultNettyWebSocket extends NettyWebSocket { + + private final StringBuilder textBuffer = new StringBuilder(); + private final ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); + + public DefaultNettyWebSocket(Channel channel) { + super(channel, new ConcurrentLinkedQueue()); + } + + public void onBinaryFragment(HttpResponseBodyPart part) { + + boolean last = part.isLast(); + byte[] message = part.getBodyPartBytes(); + + if (!last) { + try { + byteBuffer.write(message); + } catch (Exception ex) { + byteBuffer.reset(); + onError(ex); + return; + } + + if (byteBuffer.size() > maxBufferSize) { + byteBuffer.reset(); + Exception e = new Exception("Exceeded Netty Web Socket maximum buffer size of " + maxBufferSize); + onError(e); + close(); + return; + } + } + + for (WebSocketListener listener : listeners) { + if (listener instanceof WebSocketByteListener) { + WebSocketByteListener byteListener = (WebSocketByteListener) listener; + try { + if (!last) { + byteListener.onFragment(message, last); + } else if (byteBuffer.size() > 0) { + byteBuffer.write(message); + byteListener.onFragment(message, last); + byteListener.onMessage(byteBuffer.toByteArray()); + } else { + byteListener.onMessage(message); + } + } catch (Exception ex) { + listener.onError(ex); + } + } + } + + if (last) { + byteBuffer.reset(); + } + } + + public void onTextFragment(HttpResponseBodyPart part) { + + boolean last = part.isLast(); + // FIXME this is so wrong! there's a chance the fragment is not valid UTF-8 because a char is truncated + String message = new String(part.getBodyPartBytes(), StandardCharsets.UTF_8); + + if (!last) { + textBuffer.append(message); + + if (textBuffer.length() > maxBufferSize) { + textBuffer.setLength(0); + Exception e = new Exception("Exceeded Netty Web Socket maximum buffer size of " + maxBufferSize); + onError(e); + close(); + return; + } + } + + for (WebSocketListener listener : listeners) { + if (listener instanceof WebSocketTextListener) { + WebSocketTextListener textlistener = (WebSocketTextListener) listener; + try { + if (!last) { + textlistener.onFragment(message, last); + } else if (textBuffer.length() > 0) { + textlistener.onFragment(message, last); + textlistener.onMessage(textBuffer.append(message).toString()); + } else { + textlistener.onMessage(message); + } + } catch (Exception ex) { + listener.onError(ex); + } + } + } + + if (last) { + textBuffer.setLength(0); + } + } +} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java index eaadbf589e..c0d2a8d977 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java @@ -14,43 +14,39 @@ package org.asynchttpclient.providers.netty.ws; import static io.netty.buffer.Unpooled.wrappedBuffer; - -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.slf4j.Logger; -import org.slf4j.LoggerFactory; - import io.netty.channel.Channel; +import io.netty.channel.ChannelFutureListener; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; -import java.io.ByteArrayOutputStream; -import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.Collection; + +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.websocket.WebSocket; +import org.asynchttpclient.websocket.WebSocketCloseCodeReasonListener; +import org.asynchttpclient.websocket.WebSocketListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -public class NettyWebSocket implements WebSocket { +public abstract class NettyWebSocket implements WebSocket { private static final Logger LOGGER = LoggerFactory.getLogger(NettyWebSocket.class); - private final Channel channel; - private final ConcurrentLinkedQueue listeners = new ConcurrentLinkedQueue(); - - private final StringBuilder textBuffer = new StringBuilder(); - private final ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); - private int maxBufferSize = 128000000; + protected final Channel channel; + protected final Collection listeners; + protected int maxBufferSize = 128000000; - public NettyWebSocket(Channel channel) { + public NettyWebSocket(Channel channel, Collection listeners) { this.channel = channel; + this.listeners = listeners; } @Override public WebSocket sendMessage(byte[] message) { - channel.writeAndFlush(new BinaryWebSocketFrame(wrappedBuffer(message))); + channel.write(new BinaryWebSocketFrame(wrappedBuffer(message))); return this; } @@ -66,7 +62,7 @@ public WebSocket stream(byte[] fragment, int offset, int len, boolean last) { @Override public WebSocket sendTextMessage(String message) { - channel.writeAndFlush(new TextWebSocketFrame(message)); + channel.write(new TextWebSocketFrame(message)); return this; } @@ -77,13 +73,13 @@ public WebSocket streamText(String fragment, boolean last) { @Override public WebSocket sendPing(byte[] payload) { - channel.writeAndFlush(new PingWebSocketFrame(wrappedBuffer(payload))); + channel.write(new PingWebSocketFrame(wrappedBuffer(payload))); return this; } @Override public WebSocket sendPong(byte[] payload) { - channel.writeAndFlush(new PongWebSocketFrame(wrappedBuffer(payload))); + channel.write(new PongWebSocketFrame(wrappedBuffer(payload))); return this; } @@ -102,11 +98,11 @@ public WebSocket removeWebSocketListener(WebSocketListener l) { public int getMaxBufferSize() { return maxBufferSize; } - + public void setMaxBufferSize(int maxBufferSize) { this.maxBufferSize = Math.max(maxBufferSize, 8192); } - + @Override public boolean isOpen() { return channel.isOpen(); @@ -114,105 +110,16 @@ public boolean isOpen() { @Override public void close() { - onClose(); - listeners.clear(); - try { - channel.writeAndFlush(new CloseWebSocketFrame()); - channel.closeFuture().awaitUninterruptibly(); - } finally { - channel.close(); + if (channel.isOpen()) { + onClose(); + listeners.clear(); + channel.write(new CloseWebSocketFrame()).addListener(ChannelFutureListener.CLOSE); } } public void close(int statusCode, String reason) { onClose(statusCode, reason); listeners.clear(); - try { - channel.writeAndFlush(new CloseWebSocketFrame()); - channel.closeFuture().awaitUninterruptibly(); - } finally { - channel.close(); - } - } - - public void onBinaryFragment(byte[] message, boolean last) { - - if (!last) { - try { - byteBuffer.write(message); - } catch (Exception ex) { - byteBuffer.reset(); - onError(ex); - return; - } - - if (byteBuffer.size() > maxBufferSize) { - byteBuffer.reset(); - Exception e = new Exception("Exceeded Netty Web Socket maximum buffer size of " + maxBufferSize); - onError(e); - close(); - return; - } - } - - for (WebSocketListener listener : listeners) { - if (listener instanceof WebSocketByteListener) { - WebSocketByteListener byteListener = (WebSocketByteListener) listener; - try { - if (!last) { - byteListener.onFragment(message, last); - } else if (byteBuffer.size() > 0) { - byteBuffer.write(message); - byteListener.onFragment(message, last); - byteListener.onMessage(byteBuffer.toByteArray()); - } else { - byteListener.onMessage(message); - } - } catch (Exception ex) { - listener.onError(ex); - } - } - } - - if (last) { - byteBuffer.reset(); - } - } - - public void onTextFragment(String message, boolean last) { - - if (!last) { - textBuffer.append(message); - - if (textBuffer.length() > maxBufferSize) { - textBuffer.setLength(0); - Exception e = new Exception("Exceeded Netty Web Socket maximum buffer size of " + maxBufferSize); - onError(e); - close(); - return; - } - } - - for (WebSocketListener listener : listeners) { - if (listener instanceof WebSocketTextListener) { - WebSocketTextListener textListener = (WebSocketTextListener) listener; - try { - if (!last) { - textListener.onFragment(message, last); - } else if (textBuffer.length() > 0) { - textListener.onFragment(message, last); - textListener.onMessage(textBuffer.append(message).toString()); - } else { - textListener.onMessage(message); - } - } catch (Exception ex) { - listener.onError(ex); - } - } - } - - if (last) - textBuffer.setLength(0); } public void onError(Throwable t) { @@ -222,29 +129,32 @@ public void onError(Throwable t) { } catch (Throwable t2) { LOGGER.error("", t2); } - } } - public void onClose() { + protected void onClose() { onClose(1000, "Normal closure; the connection successfully completed whatever purpose for which it was created."); } public void onClose(int code, String reason) { - for (WebSocketListener listener : listeners) { + for (WebSocketListener l : listeners) { try { - if (listener instanceof WebSocketCloseCodeReasonListener) { - WebSocketCloseCodeReasonListener.class.cast(listener).onClose(this, code, reason); + if (l instanceof WebSocketCloseCodeReasonListener) { + WebSocketCloseCodeReasonListener.class.cast(l).onClose(this, code, reason); } - listener.onClose(this); + l.onClose(this); } catch (Throwable t) { - listener.onError(t); + l.onError(t); } } } @Override public String toString() { - return "NettyWebSocket{" + "channel=" + channel + '}'; + return "NettyWebSocket{channel=" + channel + '}'; } + + public abstract void onBinaryFragment(HttpResponseBodyPart part); + + public abstract void onTextFragment(HttpResponseBodyPart part); } From 920694d95d2a553f12c95b5cb84d0c8f6551926d Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 30 Jul 2014 11:54:46 +0200 Subject: [PATCH 0142/2020] Better log --- .../providers/netty/request/NettyConnectListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java index b6d4ee27a7..571803a658 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyConnectListener.java @@ -72,7 +72,7 @@ private void abortChannelPreemption(String poolKey) { private void writeRequest(Channel channel) { - LOGGER.debug("\nNon cached request \n{}\n\nusing Channel \n{}\n", future.getNettyRequest(), channel); + LOGGER.debug("Request using non cached Channel '{}':\n{}\n", channel, future.getNettyRequest().getHttpRequest()); if (future.isDone()) { abortChannelPreemption(poolKey); From d6503ff1160debae0c7bfcc633776ffd5dcdf6d2 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 30 Jul 2014 11:55:04 +0200 Subject: [PATCH 0143/2020] Remove duplicate log --- .../asynchttpclient/providers/netty/handler/HttpProtocol.java | 3 --- 1 file changed, 3 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 98be1659cb..531c3c6246 100755 --- 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 @@ -47,7 +47,6 @@ import org.asynchttpclient.providers.netty.channel.ChannelManager; import org.asynchttpclient.providers.netty.channel.Channels; import org.asynchttpclient.providers.netty.future.NettyResponseFuture; -import org.asynchttpclient.providers.netty.request.NettyRequest; import org.asynchttpclient.providers.netty.request.NettyRequestSender; import org.asynchttpclient.providers.netty.response.NettyResponseBodyPart; import org.asynchttpclient.providers.netty.response.NettyResponseHeaders; @@ -402,12 +401,10 @@ public void handle(final Channel channel, final NettyResponseFuture future, f return; } - NettyRequest nettyRequest = future.getNettyRequest(); AsyncHandler handler = future.getAsyncHandler(); try { if (e instanceof HttpResponse) { HttpResponse response = (HttpResponse) e; - logger.debug("\n\nRequest {}\n\nResponse {}\n", nettyRequest.getHttpRequest(), response); // FIXME why do we buffer the response? I don't remember... future.setPendingResponse(response); return; From 148f202ff0f7e48a1cd5d8b81318f95fa782a60f Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 30 Jul 2014 12:14:40 +0200 Subject: [PATCH 0144/2020] WebSocket messaged have to be flushed --- .../providers/netty/ws/NettyWebSocket.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java index c0d2a8d977..97068be924 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java @@ -46,7 +46,7 @@ public NettyWebSocket(Channel channel, Collection listeners) @Override public WebSocket sendMessage(byte[] message) { - channel.write(new BinaryWebSocketFrame(wrappedBuffer(message))); + channel.writeAndFlush(new BinaryWebSocketFrame(wrappedBuffer(message))); return this; } @@ -62,7 +62,7 @@ public WebSocket stream(byte[] fragment, int offset, int len, boolean last) { @Override public WebSocket sendTextMessage(String message) { - channel.write(new TextWebSocketFrame(message)); + channel.writeAndFlush(new TextWebSocketFrame(message)); return this; } @@ -73,13 +73,13 @@ public WebSocket streamText(String fragment, boolean last) { @Override public WebSocket sendPing(byte[] payload) { - channel.write(new PingWebSocketFrame(wrappedBuffer(payload))); + channel.writeAndFlush(new PingWebSocketFrame(wrappedBuffer(payload))); return this; } @Override public WebSocket sendPong(byte[] payload) { - channel.write(new PongWebSocketFrame(wrappedBuffer(payload))); + channel.writeAndFlush(new PongWebSocketFrame(wrappedBuffer(payload))); return this; } From 056127bbf40eff00970c523b63fea0b3e2ee8ea5 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 30 Jul 2014 12:15:50 +0200 Subject: [PATCH 0145/2020] Ensured Netty provider uses CONNECT when proxying ws, port #657 on master --- .../AsyncHttpClientConfig.java | 28 +++++++-------- .../AsyncHttpClientConfigBean.java | 2 +- .../AsyncHttpClientConfigDefaults.java | 6 ++-- .../util/AsyncHttpProviderUtils.java | 17 +++------ .../websocket/ProxyTunnellingTest.java | 36 +++++++++++-------- .../filters/AsyncHttpClientFilter.java | 8 +++-- .../providers/netty/handler/HttpProtocol.java | 2 +- .../netty/handler/WebSocketProtocol.java | 8 +++-- .../netty/request/NettyRequestFactory.java | 11 +++--- .../netty/request/NettyRequestSender.java | 3 +- .../providers/netty/util/HttpUtils.java | 4 +++ 11 files changed, 68 insertions(+), 57 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java index 8f1aefbf30..2a14d03f32 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -91,7 +91,7 @@ public class AsyncHttpClientConfig { protected boolean strict302Handling; protected ProxyServerSelector proxyServerSelector; - protected boolean useRelativeURIsWithSSLProxies; + protected boolean useRelativeURIsWithConnectProxies; protected boolean compressionEnabled; protected String userAgent; @@ -133,7 +133,7 @@ private AsyncHttpClientConfig(int connectionTimeout,// boolean strict302Handling, // ExecutorService applicationThreadPool,// ProxyServerSelector proxyServerSelector, // - boolean useRelativeURIsWithSSLProxies, // + boolean useRelativeURIsWithConnectProxies, // boolean compressionEnabled, // String userAgent,// Realm realm,// @@ -167,7 +167,7 @@ private AsyncHttpClientConfig(int connectionTimeout,// this.removeQueryParamOnRedirect = removeQueryParamOnRedirect; this.strict302Handling = strict302Handling; this.proxyServerSelector = proxyServerSelector; - this.useRelativeURIsWithSSLProxies = useRelativeURIsWithSSLProxies; + this.useRelativeURIsWithConnectProxies = useRelativeURIsWithConnectProxies; this.compressionEnabled = compressionEnabled; this.userAgent = userAgent; this.applicationThreadPool = applicationThreadPool == null ? Executors.newCachedThreadPool() : applicationThreadPool; @@ -493,13 +493,13 @@ public boolean isStrict302Handling() { } /** - * @returntrue if AHC should use relative URIs instead of absolute ones when talking with a SSL proxy, - * otherwise false. + * @returntrue if AHC should use relative URIs instead of absolute ones when talking with a SSL proxy + * or WebSocket proxy, otherwise false. * - * @since 1.7.12 + * @since 1.8.13 */ - public boolean isUseRelativeURIsWithSSLProxies() { - return useRelativeURIsWithSSLProxies; + public boolean isUseRelativeURIsWithConnectProxies() { + return useRelativeURIsWithConnectProxies; } /** @@ -548,7 +548,7 @@ public static class Builder { private ProxyServerSelector proxyServerSelector = null; private boolean useProxySelector = defaultUseProxySelector(); private boolean useProxyProperties = defaultUseProxyProperties(); - private boolean useRelativeURIsWithSSLProxies = defaultUseRelativeURIsWithSSLProxies(); + private boolean useRelativeURIsWithConnectProxies = defaultUseRelativeURIsWithConnectProxies(); private boolean compressionEnabled = defaultCompressionEnabled(); private String userAgent = defaultUserAgent(); private ExecutorService applicationThreadPool; @@ -962,15 +962,15 @@ public Builder setConnectionTTL(int connectionTTL) { } /** - * Configures this AHC instance to use relative URIs instead of absolute ones when talking with a SSL proxy. + * Configures this AHC instance to use relative URIs instead of absolute ones when talking with a SSL or WebSocket proxy. * - * @param useRelativeURIsWithSSLProxies + * @param useRelativeURIsWithConnectProxies * @return this * * @since 1.7.2 */ - public Builder setUseRelativeURIsWithSSLProxies(boolean useRelativeURIsWithSSLProxies) { - this.useRelativeURIsWithSSLProxies = useRelativeURIsWithSSLProxies; + public Builder setUseRelativeURIsWithConnectProxies(boolean useRelativeURIsWithConnectProxies) { + this.useRelativeURIsWithConnectProxies = useRelativeURIsWithConnectProxies; return this; } @@ -1116,7 +1116,7 @@ else if (hostnameVerifier == null) strict302Handling, // applicationThreadPool, // proxyServerSelector, // - useRelativeURIsWithSSLProxies, // + useRelativeURIsWithConnectProxies, // compressionEnabled, // userAgent,// realm,// diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java index 150187cea6..0bb496f28e 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigBean.java @@ -58,7 +58,7 @@ void configureDefaults() { compressionEnabled = defaultCompressionEnabled(); userAgent = defaultUserAgent(); allowPoolingConnections = defaultAllowPoolingConnections(); - useRelativeURIsWithSSLProxies = defaultUseRelativeURIsWithSSLProxies(); + useRelativeURIsWithConnectProxies = defaultUseRelativeURIsWithConnectProxies(); maxRequestRetry = defaultMaxRequestRetry(); ioThreadMultiplier = defaultIoThreadMultiplier(); allowPoolingSslConnections = defaultAllowPoolingSslConnections(); diff --git a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java index 36991bfa2d..6733cd5c10 100644 --- a/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java +++ b/api/src/main/java/org/asynchttpclient/AsyncHttpClientConfigDefaults.java @@ -66,7 +66,7 @@ public static boolean defaultCompressionEnabled() { } public static String defaultUserAgent() { - return System.getProperty(ASYNC_CLIENT + "userAgent", "NING/1.0"); + return System.getProperty(ASYNC_CLIENT + "userAgent", "AHC/2.0"); } public static int defaultIoThreadMultiplier() { @@ -89,8 +89,8 @@ public static boolean defaultAllowPoolingConnections() { return getBoolean(ASYNC_CLIENT + "allowPoolingConnections", true); } - public static boolean defaultUseRelativeURIsWithSSLProxies() { - return getBoolean(ASYNC_CLIENT + "useRelativeURIsWithSSLProxies", true); + public static boolean defaultUseRelativeURIsWithConnectProxies() { + return getBoolean(ASYNC_CLIENT + "useRelativeURIsWithConnectProxies", true); } public static int defaultMaxRequestRetry() { diff --git a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java index d0066c2adf..46371cb8ac 100644 --- a/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/AsyncHttpProviderUtils.java @@ -14,17 +14,16 @@ import static org.asynchttpclient.util.MiscUtils.isNonEmpty; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.AsyncHttpProvider; -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.Request; -import org.asynchttpclient.uri.UriComponents; - import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.List; +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.Request; +import org.asynchttpclient.uri.UriComponents; + /** * {@link org.asynchttpclient.AsyncHttpProvider} common utilities. */ @@ -113,12 +112,6 @@ public final static String getNonEmptyPath(UriComponents uri) { return isNonEmpty(uri.getPath()) ? uri.getPath() : "/"; } - public static String constructUserAgent(Class httpProvider, AsyncHttpClientConfig config) { - return new StringBuilder("AHC (").append(httpProvider.getSimpleName()).append(" - ").append(System.getProperty("os.name")) - .append(" - ").append(System.getProperty("os.version")).append(" - ").append(System.getProperty("java.version")) - .append(" - ").append(Runtime.getRuntime().availableProcessors()).append(" core(s))").toString(); - } - public static String parseCharset(String contentType) { for (String part : contentType.split(";")) { if (part.trim().startsWith("charset=")) { diff --git a/api/src/test/java/org/asynchttpclient/websocket/ProxyTunnellingTest.java b/api/src/test/java/org/asynchttpclient/websocket/ProxyTunnellingTest.java index cdb6e38cad..1879d19302 100644 --- a/api/src/test/java/org/asynchttpclient/websocket/ProxyTunnellingTest.java +++ b/api/src/test/java/org/asynchttpclient/websocket/ProxyTunnellingTest.java @@ -17,6 +17,9 @@ import static org.asynchttpclient.async.util.TestUtils.newJettyHttpsServer; import static org.testng.Assert.assertEquals; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClientConfig; import org.asynchttpclient.ProxyServer; @@ -24,13 +27,9 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.websocket.server.WebSocketHandler; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; +import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; - /** * Proxy usage tests. */ @@ -38,8 +37,7 @@ public abstract class ProxyTunnellingTest extends AbstractBasicTest { private Server server2; - @BeforeClass(alwaysRun = true) - public void setUpGlobal() throws Exception { + public void setUpServers(boolean targetHttps) throws Exception { port1 = findFreePort(); server = newJettyHttpServer(port1); server.setHandler(new ConnectHandler()); @@ -47,7 +45,7 @@ public void setUpGlobal() throws Exception { port2 = findFreePort(); - server2 = newJettyHttpsServer(port2); + server2 = targetHttps ? newJettyHttpsServer(port2) : newJettyHttpServer(port2); server2.setHandler(getWebSocketHandler()); server2.start(); @@ -64,27 +62,37 @@ public void configure(WebSocketServletFactory factory) { }; } - @AfterClass(alwaysRun = true) + @AfterMethod(alwaysRun = true) public void tearDownGlobal() throws Exception { server.stop(); server2.stop(); } - protected String getTargetUrl() { - return String.format("wss://127.0.0.1:%d/", port2); + @Test(timeOut = 60000) + public void echoWSText() throws Exception { + runTest(false); } @Test(timeOut = 60000) - public void echoText() throws Exception { + public void echoWSSText() throws Exception { + runTest(true); + } + + private void runTest(boolean secure) throws Exception { + + setUpServers(secure); + + String targetUrl = String.format("%s://127.0.0.1:%d/", secure ? "wss" : "ws", port2); - ProxyServer ps = new ProxyServer(ProxyServer.Protocol.HTTPS, "127.0.0.1", port1); + // CONNECT happens over HTTP, not HTTPS + ProxyServer ps = new ProxyServer(ProxyServer.Protocol.HTTP, "127.0.0.1", port1); AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().setProxyServer(ps).setAcceptAnyCertificate(true).build(); AsyncHttpClient asyncHttpClient = getAsyncHttpClient(config); try { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference text = new AtomicReference(""); - WebSocket websocket = asyncHttpClient.prepareGet(getTargetUrl()).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketTextListener() { + WebSocket websocket = asyncHttpClient.prepareGet(targetUrl).execute(new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketTextListener() { @Override public void onMessage(String message) { 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 7d7f002699..e1d4c8f60f 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 @@ -210,6 +210,7 @@ private boolean sendAsGrizzlyRequest( final Request request = httpTxContext.getRequest(); final UriComponents uri = request.getURI(); boolean secure = Utils.isSecure(uri); + boolean isWebSocket = isWSRequest(httpTxContext.getRequestUri()); // 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 @@ -219,7 +220,8 @@ private boolean sendAsGrizzlyRequest( return true; } - if (isUpgradeRequest(httpTxContext.getHandler()) && isWSRequest(httpTxContext.getRequestUri())) { + + if (isUpgradeRequest(httpTxContext.getHandler()) && isWebSocket) { httpTxContext.setWSRequest(true); convertToUpgradeRequest(httpTxContext); } @@ -238,8 +240,10 @@ private boolean sendAsGrizzlyRequest( if (method == Method.CONNECT) { final int port = uri.getPort(); requestPacket.setRequestURI(uri.getHost() + ':' + (port == -1 ? 443 : port)); - } else { + } else if ((secure || isWebSocket) && config.isUseRelativeURIsWithConnectProxies()) { requestPacket.setRequestURI(getNonEmptyPath(uri)); + } else { + requestPacket.setRequestURI(uri.toUrl()); } final BodyHandler bodyHandler = isPayloadAllowed(method) ? 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 531c3c6246..29770ecc63 100755 --- 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 @@ -405,7 +405,7 @@ public void handle(final Channel channel, final NettyResponseFuture future, f try { if (e instanceof HttpResponse) { HttpResponse response = (HttpResponse) e; - // FIXME why do we buffer the response? I don't remember... + // we buffer the response until we get the LastHttpContent future.setPendingResponse(response); return; 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 bdedadd68c..371c943206 100755 --- 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 @@ -72,6 +72,12 @@ public void handle(Channel channel, NettyResponseFuture future, Object e) thr if (e instanceof HttpResponse) { HttpResponse response = (HttpResponse) e; + // we buffer the response until we get the LastHttpContent + future.setPendingResponse(response); + + } else if (e instanceof LastHttpContent) { + HttpResponse response = future.getPendingResponse(); + future.setPendingResponse(null); HttpResponseStatus status = new NettyResponseStatus(future.getURI(), config, response); HttpResponseHeaders responseHeaders = new NettyResponseHeaders(response.headers()); @@ -151,8 +157,6 @@ public void handle(Channel channel, NettyResponseFuture future, Object e) thr } 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); } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java index fa971cf010..bd2850be64 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java @@ -16,9 +16,9 @@ import static org.asynchttpclient.providers.netty.util.HttpUtils.isNTLM; import static org.asynchttpclient.providers.netty.util.HttpUtils.isSecure; import static org.asynchttpclient.providers.netty.util.HttpUtils.isWebSocket; +import static org.asynchttpclient.providers.netty.util.HttpUtils.useProxyConnect; import static org.asynchttpclient.providers.netty.ws.WebSocketUtils.getKey; import static org.asynchttpclient.util.AsyncHttpProviderUtils.DEFAULT_CHARSET; -import static org.asynchttpclient.util.AsyncHttpProviderUtils.constructUserAgent; import static org.asynchttpclient.util.AsyncHttpProviderUtils.getAuthority; import static org.asynchttpclient.util.AsyncHttpProviderUtils.getNonEmptyPath; import static org.asynchttpclient.util.AsyncHttpProviderUtils.keepAliveHeaderValue; @@ -49,7 +49,6 @@ import org.asynchttpclient.generators.InputStreamBodyGenerator; import org.asynchttpclient.ntlm.NTLMEngine; import org.asynchttpclient.ntlm.NTLMEngineException; -import org.asynchttpclient.providers.netty.NettyAsyncHttpProvider; import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; import org.asynchttpclient.providers.netty.request.body.NettyBody; import org.asynchttpclient.providers.netty.request.body.NettyBodyBody; @@ -77,7 +76,7 @@ private String requestUri(UriComponents uri, ProxyServer proxyServer, HttpMethod if (method == HttpMethod.CONNECT) return getAuthority(uri); - else if (proxyServer != null && !(isSecure(uri) && config.isUseRelativeURIsWithSSLProxies())) + else if (proxyServer != null && !(useProxyConnect(uri) && config.isUseRelativeURIsWithConnectProxies())) return uri.toString(); else { @@ -322,10 +321,8 @@ public NettyRequest newNettyRequest(Request request, UriComponents uri, boolean httpRequest.headers().set(HttpHeaders.Names.ACCEPT, "*/*"); // Add default user agent - if (!httpRequest.headers().contains(HttpHeaders.Names.USER_AGENT)) { - String userAgent = config.getUserAgent() != null ? config.getUserAgent() : constructUserAgent(NettyAsyncHttpProvider.class, config); - httpRequest.headers().set(HttpHeaders.Names.USER_AGENT, userAgent); - } + if (!httpRequest.headers().contains(HttpHeaders.Names.USER_AGENT) && config.getUserAgent() != null) + httpRequest.headers().set(HttpHeaders.Names.USER_AGENT, config.getUserAgent()); return nettyRequest; } 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 68da7ab765..75a3def312 100755 --- 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 @@ -15,6 +15,7 @@ import static org.asynchttpclient.providers.netty.util.HttpUtils.WEBSOCKET; import static org.asynchttpclient.providers.netty.util.HttpUtils.isSecure; +import static org.asynchttpclient.providers.netty.util.HttpUtils.useProxyConnect; import static org.asynchttpclient.util.AsyncHttpProviderUtils.getDefaultPort; import static org.asynchttpclient.util.AsyncHttpProviderUtils.requestTimeout; import static org.asynchttpclient.util.ProxyUtils.avoidProxy; @@ -100,7 +101,7 @@ public ListenableFuture sendRequest(final Request request,// boolean resultOfAConnect = future != null && future.getNettyRequest() != null && future.getNettyRequest().getHttpRequest().getMethod() == HttpMethod.CONNECT; boolean useProxy = proxyServer != null && !resultOfAConnect; - if (useProxy && isSecure(uri)) + if (useProxy && useProxyConnect(uri)) // SSL proxy, have to handle CONNECT if (future != null && future.isConnectAllowed()) // CONNECT forced diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtils.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtils.java index e13b7ee51b..baac5bddbd 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtils.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/util/HttpUtils.java @@ -43,4 +43,8 @@ public static boolean isSecure(String scheme) { public static boolean isSecure(UriComponents uri) { return isSecure(uri.getScheme()); } + + public static boolean useProxyConnect(UriComponents uri) { + return isSecure(uri) || isWebSocket(uri.getScheme()); + } } From 2c38d0148108c4f1aae609a54f14bc8aa6e78818 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 30 Jul 2014 15:02:53 +0200 Subject: [PATCH 0146/2020] Release 1.8.13 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fab0cd307c..2bb2b19ad0 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.8.12 + 1.8.13 ``` From 0e4aa68fb9917f60b88e114b41eb9f3b97b4babe Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 30 Jul 2014 15:59:10 +0200 Subject: [PATCH 0147/2020] Extract Fragment listening concern into dedicated interfaces, close #655 --- .../websocket/DefaultWebSocketListener.java | 14 -- .../WebSocketByteFragmentListener.java | 26 +++ .../websocket/WebSocketByteListener.java | 9 +- .../WebSocketTextFragmentListener.java | 26 +++ .../websocket/WebSocketTextListener.java | 8 - .../websocket/ByteMessageTest.java | 12 -- .../websocket/CloseCodeReasonMessageTest.java | 8 - .../websocket/ProxyTunnellingTest.java | 4 - .../websocket/TextMessageTest.java | 24 --- .../AHCWebSocketListenerAdapter.java | 8 - .../netty/NettyAsyncHttpProviderConfig.java | 3 +- .../netty/ws/DefaultNettyWebSocket.java | 124 ------------ .../providers/netty/ws/NettyWebSocket.java | 186 +++++++++++++++--- 13 files changed, 217 insertions(+), 235 deletions(-) create mode 100644 api/src/main/java/org/asynchttpclient/websocket/WebSocketByteFragmentListener.java create mode 100644 api/src/main/java/org/asynchttpclient/websocket/WebSocketTextFragmentListener.java delete mode 100644 providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/DefaultNettyWebSocket.java diff --git a/api/src/main/java/org/asynchttpclient/websocket/DefaultWebSocketListener.java b/api/src/main/java/org/asynchttpclient/websocket/DefaultWebSocketListener.java index 864bb32df8..7e465acffa 100644 --- a/api/src/main/java/org/asynchttpclient/websocket/DefaultWebSocketListener.java +++ b/api/src/main/java/org/asynchttpclient/websocket/DefaultWebSocketListener.java @@ -33,13 +33,6 @@ public class DefaultWebSocketListener implements WebSocketByteListener, WebSocke public void onMessage(byte[] message) { } - /** - * {@inheritDoc} - */ - @Override - public void onFragment(byte[] fragment, boolean last) { - } - // -------------------------------------- Methods from WebSocketPingListener /** @@ -67,13 +60,6 @@ public void onPong(byte[] message) { public void onMessage(String message) { } - /** - * {@inheritDoc} - */ - @Override - public void onFragment(String fragment, boolean last) { - } - // ------------------------------------------ Methods from WebSocketListener /** diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteFragmentListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteFragmentListener.java new file mode 100644 index 0000000000..9988115b25 --- /dev/null +++ b/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteFragmentListener.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014 AsyncHttpClient Project. 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.websocket; + +import org.asynchttpclient.HttpResponseBodyPart; + +/** + * Invoked when WebSocket binary fragments are received. + * + * @param fragment text fragment + */ +public interface WebSocketByteFragmentListener extends WebSocketListener { + + void onFragment(HttpResponseBodyPart fragment); +} diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteListener.java index 04dd4075ec..99fb4cede9 100644 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteListener.java +++ b/api/src/main/java/org/asynchttpclient/websocket/WebSocketByteListener.java @@ -19,15 +19,8 @@ public interface WebSocketByteListener extends WebSocketListener { /** * Invoked when bytes are available. + * * @param message a byte array. */ void onMessage(byte[] message); - - /** - * Invoked when bytes of a fragmented message are available. - * - * @param fragment byte[] fragment. - * @param last if this fragment is the last in the series. - */ - void onFragment(byte[] fragment, boolean last); } diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextFragmentListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextFragmentListener.java new file mode 100644 index 0000000000..a71f9364b4 --- /dev/null +++ b/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextFragmentListener.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014 AsyncHttpClient Project. 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.websocket; + +import org.asynchttpclient.HttpResponseBodyPart; + +/** + * Invoked when WebSocket text fragments are received. + * + * @param fragment text fragment + */ +public interface WebSocketTextFragmentListener extends WebSocketListener { + + void onFragment(HttpResponseBodyPart fragment); +} diff --git a/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextListener.java b/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextListener.java index 2ea133b1ec..7f8242c74d 100644 --- a/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextListener.java +++ b/api/src/main/java/org/asynchttpclient/websocket/WebSocketTextListener.java @@ -22,12 +22,4 @@ public interface WebSocketTextListener extends WebSocketListener { * @param message a {@link String} message */ void onMessage(String message); - - /** - * Invoked when WebSocket text fragments are received. - * - * @param fragment text fragment - * @param last if this fragment is the last of the series. - */ - void onFragment(String fragment, boolean last); } diff --git a/api/src/test/java/org/asynchttpclient/websocket/ByteMessageTest.java b/api/src/test/java/org/asynchttpclient/websocket/ByteMessageTest.java index 4272b3ac48..d1078047ec 100644 --- a/api/src/test/java/org/asynchttpclient/websocket/ByteMessageTest.java +++ b/api/src/test/java/org/asynchttpclient/websocket/ByteMessageTest.java @@ -64,9 +64,6 @@ public void onMessage(byte[] message) { latch.countDown(); } - @Override - public void onFragment(byte[] fragment, boolean last) { - } }).build()).get(); websocket.sendMessage("ECHO".getBytes()); @@ -115,9 +112,6 @@ public void onMessage(byte[] message) { latch.countDown(); } - @Override - public void onFragment(byte[] fragment, boolean last) { - } }).build()).get(); websocket.sendMessage("ECHO".getBytes()).sendMessage("ECHO".getBytes()); @@ -167,9 +161,6 @@ public void onMessage(byte[] message) { latch.countDown(); } - @Override - public void onFragment(byte[] fragment, boolean last) { - } }).build()).get(); latch.await(); @@ -215,9 +206,6 @@ public void onMessage(byte[] message) { latch.countDown(); } - @Override - public void onFragment(byte[] fragment, boolean last) { - } }).build()).get(); websocket.stream("ECHO".getBytes(), false); websocket.stream("ECHO".getBytes(), true); diff --git a/api/src/test/java/org/asynchttpclient/websocket/CloseCodeReasonMessageTest.java b/api/src/test/java/org/asynchttpclient/websocket/CloseCodeReasonMessageTest.java index d5640b10ef..5ab3ca228d 100644 --- a/api/src/test/java/org/asynchttpclient/websocket/CloseCodeReasonMessageTest.java +++ b/api/src/test/java/org/asynchttpclient/websocket/CloseCodeReasonMessageTest.java @@ -115,10 +115,6 @@ public void wrongStatusCode() throws Throwable { public void onMessage(String message) { } - @Override - public void onFragment(String fragment, boolean last) { - } - @Override public void onOpen(WebSocket websocket) { } @@ -155,10 +151,6 @@ public void wrongProtocolCode() throws Throwable { public void onMessage(String message) { } - @Override - public void onFragment(String fragment, boolean last) { - } - @Override public void onOpen(WebSocket websocket) { } diff --git a/api/src/test/java/org/asynchttpclient/websocket/ProxyTunnellingTest.java b/api/src/test/java/org/asynchttpclient/websocket/ProxyTunnellingTest.java index 1879d19302..55c54970e4 100644 --- a/api/src/test/java/org/asynchttpclient/websocket/ProxyTunnellingTest.java +++ b/api/src/test/java/org/asynchttpclient/websocket/ProxyTunnellingTest.java @@ -100,10 +100,6 @@ public void onMessage(String message) { latch.countDown(); } - @Override - public void onFragment(String fragment, boolean last) { - } - @Override public void onOpen(WebSocket websocket) { } diff --git a/api/src/test/java/org/asynchttpclient/websocket/TextMessageTest.java b/api/src/test/java/org/asynchttpclient/websocket/TextMessageTest.java index 2ec3c20e3d..31d740c5ad 100644 --- a/api/src/test/java/org/asynchttpclient/websocket/TextMessageTest.java +++ b/api/src/test/java/org/asynchttpclient/websocket/TextMessageTest.java @@ -184,10 +184,6 @@ public void onMessage(String message) { latch.countDown(); } - @Override - public void onFragment(String fragment, boolean last) { - } - @Override public void onOpen(WebSocket websocket) { } @@ -228,10 +224,6 @@ public void onMessage(String message) { latch.countDown(); } - @Override - public void onFragment(String fragment, boolean last) { - } - @Override public void onOpen(WebSocket websocket) { } @@ -254,10 +246,6 @@ public void onMessage(String message) { latch.countDown(); } - @Override - public void onFragment(String fragment, boolean last) { - } - @Override public void onOpen(WebSocket websocket) { } @@ -298,10 +286,6 @@ public void onMessage(String message) { latch.countDown(); } - @Override - public void onFragment(String fragment, boolean last) { - } - @Override public void onOpen(WebSocket websocket) { websocket.sendTextMessage("ECHO").sendTextMessage("ECHO"); @@ -340,10 +324,6 @@ public void onMessage(String message) { latch.countDown(); } - @Override - public void onFragment(String fragment, boolean last) { - } - @Override public void onOpen(WebSocket websocket) { } @@ -386,10 +366,6 @@ public void onMessage(String message) { textLatch.countDown(); } - @Override - public void onFragment(String fragment, boolean last) { - } - @Override public void onOpen(WebSocket websocket) { } diff --git a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/websocket/AHCWebSocketListenerAdapter.java b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/websocket/AHCWebSocketListenerAdapter.java index 1a2f2122b2..1d2dd1291c 100644 --- a/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/websocket/AHCWebSocketListenerAdapter.java +++ b/providers/grizzly/src/main/java/org/asynchttpclient/providers/grizzly/websocket/AHCWebSocketListenerAdapter.java @@ -128,10 +128,6 @@ public void onFragment(org.glassfish.grizzly.websockets.WebSocket webSocket, Str } } } - } else { - if (ahcListener instanceof WebSocketTextListener) { - WebSocketTextListener.class.cast(ahcListener).onFragment(s, last); - } } } catch (Throwable e) { ahcListener.onError(e); @@ -152,10 +148,6 @@ public void onFragment(org.glassfish.grizzly.websockets.WebSocket webSocket, byt } } } - } else { - if (ahcListener instanceof WebSocketByteListener) { - WebSocketByteListener.class.cast(ahcListener).onFragment(bytes, last); - } } } catch (Throwable e) { ahcListener.onError(e); 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 95eed7889f..38aa908fa9 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java @@ -29,7 +29,6 @@ import org.asynchttpclient.providers.netty.response.EagerNettyResponseBodyPart; import org.asynchttpclient.providers.netty.response.LazyNettyResponseBodyPart; import org.asynchttpclient.providers.netty.response.NettyResponseBodyPart; -import org.asynchttpclient.providers.netty.ws.DefaultNettyWebSocket; import org.asynchttpclient.providers.netty.ws.NettyWebSocket; /** @@ -124,7 +123,7 @@ public class DefaultNettyWebSocketFactory implements NettyWebSocketFactory { @Override public NettyWebSocket newNettyWebSocket(Channel channel) { - return new DefaultNettyWebSocket(channel); + return new NettyWebSocket(channel); } } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/DefaultNettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/DefaultNettyWebSocket.java deleted file mode 100644 index 11b8cfd133..0000000000 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/DefaultNettyWebSocket.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2014 AsyncHttpClient Project. 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.ws; - - -import io.netty.channel.Channel; - -import java.io.ByteArrayOutputStream; -import java.util.concurrent.ConcurrentLinkedQueue; - -import org.asynchttpclient.HttpResponseBodyPart; -import org.asynchttpclient.util.StandardCharsets; -import org.asynchttpclient.websocket.WebSocketByteListener; -import org.asynchttpclient.websocket.WebSocketListener; -import org.asynchttpclient.websocket.WebSocketTextListener; - -public class DefaultNettyWebSocket extends NettyWebSocket { - - private final StringBuilder textBuffer = new StringBuilder(); - private final ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); - - public DefaultNettyWebSocket(Channel channel) { - super(channel, new ConcurrentLinkedQueue()); - } - - public void onBinaryFragment(HttpResponseBodyPart part) { - - boolean last = part.isLast(); - byte[] message = part.getBodyPartBytes(); - - if (!last) { - try { - byteBuffer.write(message); - } catch (Exception ex) { - byteBuffer.reset(); - onError(ex); - return; - } - - if (byteBuffer.size() > maxBufferSize) { - byteBuffer.reset(); - Exception e = new Exception("Exceeded Netty Web Socket maximum buffer size of " + maxBufferSize); - onError(e); - close(); - return; - } - } - - for (WebSocketListener listener : listeners) { - if (listener instanceof WebSocketByteListener) { - WebSocketByteListener byteListener = (WebSocketByteListener) listener; - try { - if (!last) { - byteListener.onFragment(message, last); - } else if (byteBuffer.size() > 0) { - byteBuffer.write(message); - byteListener.onFragment(message, last); - byteListener.onMessage(byteBuffer.toByteArray()); - } else { - byteListener.onMessage(message); - } - } catch (Exception ex) { - listener.onError(ex); - } - } - } - - if (last) { - byteBuffer.reset(); - } - } - - public void onTextFragment(HttpResponseBodyPart part) { - - boolean last = part.isLast(); - // FIXME this is so wrong! there's a chance the fragment is not valid UTF-8 because a char is truncated - String message = new String(part.getBodyPartBytes(), StandardCharsets.UTF_8); - - if (!last) { - textBuffer.append(message); - - if (textBuffer.length() > maxBufferSize) { - textBuffer.setLength(0); - Exception e = new Exception("Exceeded Netty Web Socket maximum buffer size of " + maxBufferSize); - onError(e); - close(); - return; - } - } - - for (WebSocketListener listener : listeners) { - if (listener instanceof WebSocketTextListener) { - WebSocketTextListener textlistener = (WebSocketTextListener) listener; - try { - if (!last) { - textlistener.onFragment(message, last); - } else if (textBuffer.length() > 0) { - textlistener.onFragment(message, last); - textlistener.onMessage(textBuffer.append(message).toString()); - } else { - textlistener.onMessage(message); - } - } catch (Exception ex) { - listener.onError(ex); - } - } - } - - if (last) { - textBuffer.setLength(0); - } - } -} diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java index 97068be924..34bc1b1c33 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java @@ -14,6 +14,7 @@ package org.asynchttpclient.providers.netty.ws; import static io.netty.buffer.Unpooled.wrappedBuffer; +import static org.asynchttpclient.util.StandardCharsets.UTF_8; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; @@ -22,22 +23,40 @@ import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.providers.netty.response.NettyResponseBodyPart; import org.asynchttpclient.websocket.WebSocket; +import org.asynchttpclient.websocket.WebSocketByteFragmentListener; +import org.asynchttpclient.websocket.WebSocketByteListener; import org.asynchttpclient.websocket.WebSocketCloseCodeReasonListener; import org.asynchttpclient.websocket.WebSocketListener; +import org.asynchttpclient.websocket.WebSocketTextFragmentListener; +import org.asynchttpclient.websocket.WebSocketTextListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public abstract class NettyWebSocket implements WebSocket { +public class NettyWebSocket implements WebSocket { private static final Logger LOGGER = LoggerFactory.getLogger(NettyWebSocket.class); protected final Channel channel; protected final Collection listeners; protected int maxBufferSize = 128000000; + private int bufferSize; + private List _fragments; + private volatile boolean interestedInByteMessages; + private volatile boolean interestedInTextMessages; + + public NettyWebSocket(Channel channel) { + this(channel, new ConcurrentLinkedQueue()); + } public NettyWebSocket(Channel channel, Collection listeners) { this.channel = channel; @@ -46,7 +65,7 @@ public NettyWebSocket(Channel channel, Collection listeners) @Override public WebSocket sendMessage(byte[] message) { - channel.writeAndFlush(new BinaryWebSocketFrame(wrappedBuffer(message))); + channel.write(new BinaryWebSocketFrame(wrappedBuffer(message))); return this; } @@ -62,7 +81,7 @@ public WebSocket stream(byte[] fragment, int offset, int len, boolean last) { @Override public WebSocket sendTextMessage(String message) { - channel.writeAndFlush(new TextWebSocketFrame(message)); + channel.write(new TextWebSocketFrame(message)); return this; } @@ -73,36 +92,24 @@ public WebSocket streamText(String fragment, boolean last) { @Override public WebSocket sendPing(byte[] payload) { - channel.writeAndFlush(new PingWebSocketFrame(wrappedBuffer(payload))); + channel.write(new PingWebSocketFrame(wrappedBuffer(payload))); return this; } @Override public WebSocket sendPong(byte[] payload) { - channel.writeAndFlush(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); + channel.write(new PongWebSocketFrame(wrappedBuffer(payload))); return this; } public int getMaxBufferSize() { return maxBufferSize; } - + public void setMaxBufferSize(int maxBufferSize) { this.maxBufferSize = Math.max(maxBufferSize, 8192); } - + @Override public boolean isOpen() { return channel.isOpen(); @@ -153,8 +160,141 @@ public void onClose(int code, String reason) { public String toString() { return "NettyWebSocket{channel=" + channel + '}'; } - - public abstract void onBinaryFragment(HttpResponseBodyPart part); - - public abstract void onTextFragment(HttpResponseBodyPart part); + + private boolean hasWebSocketByteListener() { + for (WebSocketListener listener : listeners) { + if (listener instanceof WebSocketByteListener) + return true; + } + return false; + } + + private boolean hasWebSocketTextListener() { + for (WebSocketListener listener : listeners) { + if (listener instanceof WebSocketTextListener) + return true; + } + return false; + } + + @Override + public WebSocket addWebSocketListener(WebSocketListener l) { + listeners.add(l); + if (l instanceof WebSocketByteListener) + interestedInByteMessages = true; + else if (l instanceof WebSocketTextListener) + interestedInTextMessages = true; + return this; + } + + @Override + public WebSocket removeWebSocketListener(WebSocketListener l) { + listeners.remove(l); + + if (l instanceof WebSocketByteListener) + interestedInByteMessages = hasWebSocketByteListener(); + else if (l instanceof WebSocketTextListener) + interestedInTextMessages = hasWebSocketTextListener(); + + return this; + } + + private List fragments() { + if (_fragments == null) + _fragments = new ArrayList(2); + return _fragments; + } + + private void bufferFragment(byte[] buffer) { + bufferSize += buffer.length; + if (bufferSize > maxBufferSize) { + onError(new Exception("Exceeded Netty Web Socket maximum buffer size of " + maxBufferSize)); + reset(); + close(); + } else { + fragments().add(buffer); + } + } + + private void reset() { + fragments().clear(); + bufferSize = 0; + } + + private void notifyByteListeners(byte[] message) { + for (WebSocketListener listener : listeners) { + if (listener instanceof WebSocketByteListener) + WebSocketByteListener.class.cast(listener).onMessage(message); + } + } + + private void notifyTextListeners(byte[] bytes) { + String message = new String(bytes, UTF_8); + for (WebSocketListener listener : listeners) { + if (listener instanceof WebSocketTextListener) + WebSocketTextListener.class.cast(listener).onMessage(message); + } + } + + public void onBinaryFragment(HttpResponseBodyPart part) { + + for (WebSocketListener listener : listeners) { + if (listener instanceof WebSocketByteFragmentListener) + WebSocketByteFragmentListener.class.cast(listener).onFragment(part); + } + + if (interestedInByteMessages) { + byte[] fragment = NettyResponseBodyPart.class.cast(part).getBodyPartBytes(); + + if (part.isLast()) { + if (bufferSize == 0) { + notifyByteListeners(fragment); + + } else { + bufferFragment(fragment); + notifyByteListeners(fragmentsBytes()); + } + + reset(); + + } else + bufferFragment(fragment); + } + } + + private byte[] fragmentsBytes() { + ByteArrayOutputStream os = new ByteArrayOutputStream(bufferSize); + for (byte[] bytes : _fragments) + try { + os.write(bytes); + } catch (IOException e) { + // yeah, right + } + return os.toByteArray(); + } + + public void onTextFragment(HttpResponseBodyPart part) { + for (WebSocketListener listener : listeners) { + if (listener instanceof WebSocketTextFragmentListener) + WebSocketTextFragmentListener.class.cast(listener).onFragment(part); + } + + if (interestedInTextMessages) { + byte[] fragment = NettyResponseBodyPart.class.cast(part).getBodyPartBytes(); + + if (part.isLast()) { + if (bufferSize == 0) { + notifyTextListeners(fragment); + + } else { + bufferFragment(fragment); + notifyTextListeners(fragmentsBytes()); + } + + reset(); + + } else + bufferFragment(fragment); + } + } } From 5a6c1ea9f1d6a35211f320c345cafd6a3a610597 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 30 Jul 2014 16:00:25 +0200 Subject: [PATCH 0148/2020] Regression: writeAndFlush --- .../providers/netty/ws/NettyWebSocket.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java index 34bc1b1c33..98789d0906 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java @@ -65,7 +65,7 @@ public NettyWebSocket(Channel channel, Collection listeners) @Override public WebSocket sendMessage(byte[] message) { - channel.write(new BinaryWebSocketFrame(wrappedBuffer(message))); + channel.writeAndFlush(new BinaryWebSocketFrame(wrappedBuffer(message))); return this; } @@ -81,7 +81,7 @@ public WebSocket stream(byte[] fragment, int offset, int len, boolean last) { @Override public WebSocket sendTextMessage(String message) { - channel.write(new TextWebSocketFrame(message)); + channel.writeAndFlush(new TextWebSocketFrame(message)); return this; } @@ -92,13 +92,13 @@ public WebSocket streamText(String fragment, boolean last) { @Override public WebSocket sendPing(byte[] payload) { - channel.write(new PingWebSocketFrame(wrappedBuffer(payload))); + channel.writeAndFlush(new PingWebSocketFrame(wrappedBuffer(payload))); return this; } @Override public WebSocket sendPong(byte[] payload) { - channel.write(new PongWebSocketFrame(wrappedBuffer(payload))); + channel.writeAndFlush(new PongWebSocketFrame(wrappedBuffer(payload))); return this; } @@ -120,7 +120,7 @@ public void close() { if (channel.isOpen()) { onClose(); listeners.clear(); - channel.write(new CloseWebSocketFrame()).addListener(ChannelFutureListener.CLOSE); + channel.writeAndFlush(new CloseWebSocketFrame()).addListener(ChannelFutureListener.CLOSE); } } From ce539ee0954329b92bff5e2237bf1836cb526524 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 30 Jul 2014 16:03:43 +0200 Subject: [PATCH 0149/2020] Netty websocket streaming, close #518 --- .../providers/netty/ws/NettyWebSocket.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java index 98789d0906..88357866bd 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java @@ -71,12 +71,14 @@ public WebSocket sendMessage(byte[] message) { @Override public WebSocket stream(byte[] fragment, boolean last) { - throw new UnsupportedOperationException("Streaming currently only supported by the Grizzly provider."); + channel.writeAndFlush(new BinaryWebSocketFrame(last, 0, wrappedBuffer(fragment))); + return this; } @Override public WebSocket stream(byte[] fragment, int offset, int len, boolean last) { - throw new UnsupportedOperationException("Streaming currently only supported by the Grizzly provider."); + channel.writeAndFlush(new BinaryWebSocketFrame(last, 0, wrappedBuffer(fragment, offset, len))); + return this; } @Override @@ -87,7 +89,8 @@ public WebSocket sendTextMessage(String message) { @Override public WebSocket streamText(String fragment, boolean last) { - throw new UnsupportedOperationException("Streaming currently only supported by the Grizzly provider."); + channel.writeAndFlush(new TextWebSocketFrame(last, 0, fragment)); + return this; } @Override From 7a067549012c4474a1a01c8c9beaf45c471962bb Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 30 Jul 2014 16:28:38 +0200 Subject: [PATCH 0150/2020] Notify Pings and Pongs, close #517 --- .../netty/handler/WebSocketProtocol.java | 25 ++++++++++++------- .../providers/netty/ws/NettyWebSocket.java | 18 +++++++++++++ 2 files changed, 34 insertions(+), 9 deletions(-) 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 371c943206..7dfbbe0690 100755 --- 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 @@ -22,6 +22,9 @@ 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.PingWebSocketFrame; +import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame; import java.io.IOException; @@ -74,7 +77,7 @@ public void handle(Channel channel, NettyResponseFuture future, Object e) thr HttpResponse response = (HttpResponse) e; // we buffer the response until we get the LastHttpContent future.setPendingResponse(response); - + } else if (e instanceof LastHttpContent) { HttpResponse response = future.getPendingResponse(); future.setPendingResponse(null); @@ -115,8 +118,7 @@ public void handle(Channel channel, NettyResponseFuture future, Object e) thr } String accept = response.headers().get(HttpHeaders.Names.SEC_WEBSOCKET_ACCEPT); - String key = getAcceptKey(future.getNettyRequest().getHttpRequest().headers() - .get(HttpHeaders.Names.SEC_WEBSOCKET_KEY)); + String key = getAcceptKey(future.getNettyRequest().getHttpRequest().headers().get(HttpHeaders.Names.SEC_WEBSOCKET_KEY)); if (accept == null || !accept.equals(key)) { requestSender.abort(future, new IOException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, key))); } @@ -141,13 +143,17 @@ public void handle(Channel channel, NettyResponseFuture future, Object e) thr ByteBuf buf = frame.content(); if (buf != null && buf.readableBytes() > 0) { try { - NettyResponseBodyPart rp = nettyConfig.getBodyPartFactory().newResponseBodyPart(buf, frame.isFinalFragment()); - handler.onBodyPartReceived(rp); + NettyResponseBodyPart part = nettyConfig.getBodyPartFactory().newResponseBodyPart(buf, frame.isFinalFragment()); + handler.onBodyPartReceived(part); if (frame instanceof BinaryWebSocketFrame) { - webSocket.onBinaryFragment(rp); - } else { - webSocket.onTextFragment(rp); + webSocket.onBinaryFragment(part); + } else if (frame instanceof TextWebSocketFrame) { + webSocket.onTextFragment(part); + } else if (frame instanceof PingWebSocketFrame) { + webSocket.onPing(part); + } else if (frame instanceof PongWebSocketFrame) { + webSocket.onPong(part); } } finally { buf.release(); @@ -196,7 +202,8 @@ public void onClose(Channel channel) { WebSocketUpgradeHandler h = WebSocketUpgradeHandler.class.cast(nettyResponse.getAsyncHandler()); NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted()); - // FIXME How could this test not succeed, we just checked above that attribute is a NettyResponseFuture???? + // FIXME How could this test not succeed, we just checked above that + // attribute is a NettyResponseFuture???? logger.trace("Connection was closed abnormally (that is, with no close frame being sent)."); if (attribute != DiscardEvent.INSTANCE && webSocket != null) webSocket.close(1006, "Connection was closed abnormally (that is, with no close frame being sent)."); diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java index 88357866bd..08e14e6183 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java @@ -37,6 +37,8 @@ import org.asynchttpclient.websocket.WebSocketByteListener; import org.asynchttpclient.websocket.WebSocketCloseCodeReasonListener; import org.asynchttpclient.websocket.WebSocketListener; +import org.asynchttpclient.websocket.WebSocketPingListener; +import org.asynchttpclient.websocket.WebSocketPongListener; import org.asynchttpclient.websocket.WebSocketTextFragmentListener; import org.asynchttpclient.websocket.WebSocketTextListener; import org.slf4j.Logger; @@ -300,4 +302,20 @@ public void onTextFragment(HttpResponseBodyPart part) { bufferFragment(fragment); } } + + public void onPing(HttpResponseBodyPart part) { + for (WebSocketListener listener : listeners) { + if (listener instanceof WebSocketPingListener) + // bytes are cached in the part + WebSocketPingListener.class.cast(listener).onPing(part.getBodyPartBytes()); + } + } + + public void onPong(HttpResponseBodyPart part) { + for (WebSocketListener listener : listeners) { + if (listener instanceof WebSocketPongListener) + // bytes are cached in the part + WebSocketPongListener.class.cast(listener).onPong(part.getBodyPartBytes()); + } + } } From 4e8d62e1a80890d42810799e580681a0c8e6faa1 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Wed, 30 Jul 2014 16:41:47 +0200 Subject: [PATCH 0151/2020] Have a config parameter for websocket max buffer size, close #658 --- .../netty/NettyAsyncHttpProviderConfig.java | 24 ++++++++++++------- .../netty/handler/WebSocketProtocol.java | 2 +- .../providers/netty/ws/NettyWebSocket.java | 18 +++++--------- 3 files changed, 23 insertions(+), 21 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 38aa908fa9..2e105d2031 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/NettyAsyncHttpProviderConfig.java @@ -43,10 +43,8 @@ public class NettyAsyncHttpProviderConfig implements AsyncHttpProviderConfig name, Object value) { @@ -116,17 +114,17 @@ public NettyResponseBodyPart newResponseBodyPart(ByteBuf buf, boolean last) { } public static interface NettyWebSocketFactory { - NettyWebSocket newNettyWebSocket(Channel channel); + NettyWebSocket newNettyWebSocket(Channel channel, NettyAsyncHttpProviderConfig nettyConfig); } public class DefaultNettyWebSocketFactory implements NettyWebSocketFactory { @Override - public NettyWebSocket newNettyWebSocket(Channel channel) { - return new NettyWebSocket(channel); + public NettyWebSocket newNettyWebSocket(Channel channel, NettyAsyncHttpProviderConfig nettyConfig) { + return new NettyWebSocket(channel, nettyConfig); } } - + /** * Allow configuring the Netty's event loop. */ @@ -166,6 +164,8 @@ public NettyWebSocket newNettyWebSocket(Channel channel) { private NettyWebSocketFactory nettyWebSocketFactory = new DefaultNettyWebSocketFactory(); + private int webSocketMaxBufferSize = 128000000; + public EventLoopGroup getEventLoopGroup() { return eventLoopGroup; } @@ -293,4 +293,12 @@ public NettyWebSocketFactory getNettyWebSocketFactory() { public void setNettyWebSocketFactory(NettyWebSocketFactory nettyWebSocketFactory) { this.nettyWebSocketFactory = nettyWebSocketFactory; } + + public int getWebSocketMaxBufferSize() { + return webSocketMaxBufferSize; + } + + public void setWebSocketMaxBufferSize(int webSocketMaxBufferSize) { + this.webSocketMaxBufferSize = webSocketMaxBufferSize; + } } 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 7dfbbe0690..117330693d 100755 --- 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 @@ -61,7 +61,7 @@ public WebSocketProtocol(ChannelManager channelManager,// private void invokeOnSucces(Channel channel, WebSocketUpgradeHandler h) { if (!h.touchSuccess()) { try { - h.onSuccess(nettyConfig.getNettyWebSocketFactory().newNettyWebSocket(channel)); + h.onSuccess(nettyConfig.getNettyWebSocketFactory().newNettyWebSocket(channel, nettyConfig)); } catch (Exception ex) { logger.warn("onSuccess unexpected exception", ex); } diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java index 08e14e6183..d93e3ef8dc 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java @@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.providers.netty.NettyAsyncHttpProviderConfig; import org.asynchttpclient.providers.netty.response.NettyResponseBodyPart; import org.asynchttpclient.websocket.WebSocket; import org.asynchttpclient.websocket.WebSocketByteFragmentListener; @@ -50,19 +51,20 @@ public class NettyWebSocket implements WebSocket { protected final Channel channel; protected final Collection listeners; - protected int maxBufferSize = 128000000; + protected final int maxBufferSize; private int bufferSize; private List _fragments; private volatile boolean interestedInByteMessages; private volatile boolean interestedInTextMessages; - public NettyWebSocket(Channel channel) { - this(channel, new ConcurrentLinkedQueue()); + public NettyWebSocket(Channel channel, NettyAsyncHttpProviderConfig nettyConfig) { + this(channel, nettyConfig, new ConcurrentLinkedQueue()); } - public NettyWebSocket(Channel channel, Collection listeners) { + public NettyWebSocket(Channel channel, NettyAsyncHttpProviderConfig nettyConfig, Collection listeners) { this.channel = channel; this.listeners = listeners; + maxBufferSize = nettyConfig.getWebSocketMaxBufferSize(); } @Override @@ -107,14 +109,6 @@ public WebSocket sendPong(byte[] payload) { return this; } - public int getMaxBufferSize() { - return maxBufferSize; - } - - public void setMaxBufferSize(int maxBufferSize) { - this.maxBufferSize = Math.max(maxBufferSize, 8192); - } - @Override public boolean isOpen() { return channel.isOpen(); From e1da1c6c8b77b01cc96fbdb01c3ce63eb1fa664e Mon Sep 17 00:00:00 2001 From: oleksiys Date: Fri, 1 Aug 2014 16:13:14 -0700 Subject: [PATCH 0152/2020] [master] + don't add another Host header if it's already set --- .../filters/AsyncHttpClientFilter.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) 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 e1d4c8f60f..7d80c95d99 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 @@ -420,15 +420,18 @@ private static FilterChainContext obtainProtocolChainContext(final FilterChainCo return newFilterChainContext; } - private static void addHostHeader(final Request request, final UriComponents uri, final HttpRequestPacket requestPacket) { - String host = request.getVirtualHost(); - if (host != null) { - requestPacket.addHeader(Header.Host, host); - } else { - if (uri.getPort() == -1) { - requestPacket.addHeader(Header.Host, uri.getHost()); + private static void addHostHeader(final Request request, + final UriComponents uri, final HttpRequestPacket requestPacket) { + if (!request.getHeaders().containsKey(Header.Host.toString())) { + String host = request.getVirtualHost(); + if (host != null) { + requestPacket.addHeader(Header.Host, host); } else { - requestPacket.addHeader(Header.Host, uri.getHost() + ':' + uri.getPort()); + if (uri.getPort() == -1) { + requestPacket.addHeader(Header.Host, uri.getHost()); + } else { + requestPacket.addHeader(Header.Host, uri.getHost() + ':' + uri.getPort()); + } } } } From 7d285b5ed15439c376c7ba3c2aa3c89db80274eb Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Sat, 23 Aug 2014 07:46:20 +0200 Subject: [PATCH 0153/2020] Upgrade Netty 4.0.23.Final, close #667 --- 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 bc9e908b63..57510c4cfc 100644 --- a/providers/netty/pom.xml +++ b/providers/netty/pom.xml @@ -39,7 +39,7 @@ io.netty netty-all - 4.0.20.Final + 4.0.23.Final org.javassist From 550ce1cc02628dadcd5f976c50fce80548064579 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Sat, 23 Aug 2014 07:48:21 +0200 Subject: [PATCH 0154/2020] Fix MiscUtils: closeSilently should check for nullity, close #662 --- .../main/java/org/asynchttpclient/util/MiscUtils.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/util/MiscUtils.java b/api/src/main/java/org/asynchttpclient/util/MiscUtils.java index 1943f9939b..55b5ba12cd 100644 --- a/api/src/main/java/org/asynchttpclient/util/MiscUtils.java +++ b/api/src/main/java/org/asynchttpclient/util/MiscUtils.java @@ -52,9 +52,10 @@ public static T withDefault(T value, T defaults) { } public static void closeSilently(Closeable closeable) { - try { - closeable.close(); - } catch (IOException e) { - } + if (closeable != null) + try { + closeable.close(); + } catch (IOException e) { + } } } From e33026d481671313fd4c1d35a51c9aa38371e09f Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Sat, 23 Aug 2014 13:33:47 +0200 Subject: [PATCH 0155/2020] Append char instead of String --- .../providers/netty/request/NettyRequestFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java index bd2850be64..0e24e441a2 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java @@ -189,9 +189,9 @@ private byte[] computeBodyFromParams(List params, Charset bodyCharset) { StringBuilder sb = new StringBuilder(); for (Param param : params) { UTF8UrlEncoder.appendEncoded(sb, param.getName()); - sb.append("="); + sb.append('='); UTF8UrlEncoder.appendEncoded(sb, param.getValue()); - sb.append("&"); + sb.append('&'); } sb.setLength(sb.length() - 1); return sb.toString().getBytes(bodyCharset); From 3014f512f2764ceb5b7893684e278fc536867927 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Sat, 23 Aug 2014 13:34:37 +0200 Subject: [PATCH 0156/2020] 0 means empty file, not chunked --- .../providers/netty/request/NettyRequestFactory.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java index 0e24e441a2..62cf4e89bb 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java @@ -280,10 +280,10 @@ public NettyRequest newNettyRequest(Request request, UriComponents uri, boolean } if (body != null) { - if (body.getContentLength() > 0) - httpRequest.headers().set(HttpHeaders.Names.CONTENT_LENGTH, body.getContentLength()); - else + if (body.getContentLength() < 0) httpRequest.headers().set(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); + else + httpRequest.headers().set(HttpHeaders.Names.CONTENT_LENGTH, body.getContentLength()); if (body.getContentType() != null) httpRequest.headers().set(HttpHeaders.Names.CONTENT_TYPE, body.getContentType()); From babb5be9c7dda2adc2fbf646dff952b48f8802ae Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Sat, 23 Aug 2014 13:36:40 +0200 Subject: [PATCH 0157/2020] Don't use replace but addAfter+remove so that first frame is not lost, close #471 --- .../providers/netty/channel/ChannelManager.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java index 6fb3b03f3b..43d8aa9bd2 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/ChannelManager.java @@ -385,8 +385,10 @@ public void upgradeProtocol(ChannelPipeline pipeline, String scheme, String host else pipeline.addFirst(HTTP_HANDLER, newHttpClientCodec()); - if (isWebSocket(scheme)) - pipeline.replace(HTTP_PROCESSOR, WS_PROCESSOR, wsProcessor); + if (isWebSocket(scheme)) { + pipeline.addAfter(HTTP_PROCESSOR, WS_PROCESSOR, wsProcessor); + pipeline.remove(HTTP_PROCESSOR); + } } public String getPoolKey(NettyResponseFuture future) { @@ -416,7 +418,8 @@ public Bootstrap getBootstrap(UriComponents uri, boolean useProxy, boolean useSS } public void upgradePipelineForWebSockets(ChannelPipeline pipeline) { - pipeline.replace(HTTP_HANDLER, WS_ENCODER_HANDLER, new WebSocket08FrameEncoder(true)); + pipeline.addAfter(HTTP_HANDLER, WS_ENCODER_HANDLER, new WebSocket08FrameEncoder(true)); + pipeline.remove(HTTP_HANDLER); pipeline.addBefore(WS_PROCESSOR, WS_DECODER_HANDLER, new WebSocket08FrameDecoder(false, false, 10 * 1024)); } From 429cc63fc92ae720e0de31df4f77f8b202952869 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Sat, 23 Aug 2014 13:38:55 +0200 Subject: [PATCH 0158/2020] minor clean up --- .../netty/request/NettyRequestFactory.java | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java index 62cf4e89bb..ac3368d5c3 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/request/NettyRequestFactory.java @@ -266,63 +266,65 @@ public NettyRequest newNettyRequest(Request request, UriComponents uri, boolean nettyRequest = new NettyRequest(httpRequest, body); } + HttpHeaders headers = httpRequest.headers(); + if (method != HttpMethod.CONNECT) { // assign headers as configured on request for (Entry> header : request.getHeaders()) { - httpRequest.headers().set(header.getKey(), header.getValue()); + headers.set(header.getKey(), header.getValue()); } if (isNonEmpty(request.getCookies())) - httpRequest.headers().set(HttpHeaders.Names.COOKIE, CookieEncoder.encode(request.getCookies())); + headers.set(HttpHeaders.Names.COOKIE, CookieEncoder.encode(request.getCookies())); if (config.isCompressionEnabled()) - httpRequest.headers().set(HttpHeaders.Names.ACCEPT_ENCODING, GZIP_DEFLATE); + headers.set(HttpHeaders.Names.ACCEPT_ENCODING, GZIP_DEFLATE); } if (body != null) { if (body.getContentLength() < 0) - httpRequest.headers().set(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); + headers.set(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED); else - httpRequest.headers().set(HttpHeaders.Names.CONTENT_LENGTH, body.getContentLength()); + headers.set(HttpHeaders.Names.CONTENT_LENGTH, body.getContentLength()); if (body.getContentType() != null) - httpRequest.headers().set(HttpHeaders.Names.CONTENT_TYPE, body.getContentType()); + headers.set(HttpHeaders.Names.CONTENT_TYPE, body.getContentType()); } // connection header and friends boolean webSocket = isWebSocket(uri.getScheme()); if (method != HttpMethod.CONNECT && webSocket) { - httpRequest.headers().set(HttpHeaders.Names.UPGRADE, HttpHeaders.Values.WEBSOCKET); - httpRequest.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.UPGRADE); - httpRequest.headers().set(HttpHeaders.Names.ORIGIN, "http://" + uri.getHost() + ":" + (uri.getPort() == -1 ? isSecure(uri.getScheme()) ? 443 : 80 : uri.getPort())); - httpRequest.headers().set(HttpHeaders.Names.SEC_WEBSOCKET_KEY, getKey()); - httpRequest.headers().set(HttpHeaders.Names.SEC_WEBSOCKET_VERSION, "13"); - - } else if (!httpRequest.headers().contains(HttpHeaders.Names.CONNECTION)) { - httpRequest.headers().set(HttpHeaders.Names.CONNECTION, keepAliveHeaderValue(config)); + headers.set(HttpHeaders.Names.UPGRADE, HttpHeaders.Values.WEBSOCKET)// + .set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.UPGRADE)// + .set(HttpHeaders.Names.ORIGIN, "http://" + uri.getHost() + ":" + (uri.getPort() == -1 ? isSecure(uri.getScheme()) ? 443 : 80 : uri.getPort()))// + .set(HttpHeaders.Names.SEC_WEBSOCKET_KEY, getKey())// + .set(HttpHeaders.Names.SEC_WEBSOCKET_VERSION, "13"); + + } else if (!headers.contains(HttpHeaders.Names.CONNECTION)) { + headers.set(HttpHeaders.Names.CONNECTION, keepAliveHeaderValue(config)); } String hostHeader = hostHeader(request, uri); if (hostHeader != null) - httpRequest.headers().set(HttpHeaders.Names.HOST, hostHeader); + headers.set(HttpHeaders.Names.HOST, hostHeader); Realm realm = request.getRealm() != null ? request.getRealm() : config.getRealm(); String authorizationHeader = authorizationHeader(request, uri, proxyServer, realm); if (authorizationHeader != null) // don't override authorization but append - httpRequest.headers().add(HttpHeaders.Names.AUTHORIZATION, authorizationHeader); + headers.add(HttpHeaders.Names.AUTHORIZATION, authorizationHeader); String proxyAuthorizationHeader = proxyAuthorizationHeader(request, proxyServer, method); if (proxyAuthorizationHeader != null) - httpRequest.headers().set(HttpHeaders.Names.PROXY_AUTHORIZATION, proxyAuthorizationHeader); + headers.set(HttpHeaders.Names.PROXY_AUTHORIZATION, proxyAuthorizationHeader); // Add default accept headers - if (!httpRequest.headers().contains(HttpHeaders.Names.ACCEPT)) - httpRequest.headers().set(HttpHeaders.Names.ACCEPT, "*/*"); + if (!headers.contains(HttpHeaders.Names.ACCEPT)) + headers.set(HttpHeaders.Names.ACCEPT, "*/*"); // Add default user agent - if (!httpRequest.headers().contains(HttpHeaders.Names.USER_AGENT) && config.getUserAgent() != null) - httpRequest.headers().set(HttpHeaders.Names.USER_AGENT, config.getUserAgent()); + if (!headers.contains(HttpHeaders.Names.USER_AGENT) && config.getUserAgent() != null) + headers.set(HttpHeaders.Names.USER_AGENT, config.getUserAgent()); return nettyRequest; } From b01e94ffa0b4295ede2950736b5e132bce45f789 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Sat, 23 Aug 2014 13:58:04 +0200 Subject: [PATCH 0159/2020] Target JDK7, use getHostString instead of getHostName, close #672 --- .../asynchttpclient/providers/netty/channel/SslInitializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java index 31819ee86b..0a938b1f5f 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/channel/SslInitializer.java @@ -41,7 +41,7 @@ public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, Sock throws Exception { InetSocketAddress remoteInetSocketAddress = (InetSocketAddress) remoteAddress; - String peerHost = remoteInetSocketAddress.getHostName(); + String peerHost = remoteInetSocketAddress.getHostString(); int peerPort = remoteInetSocketAddress.getPort(); SslHandler sslHandler = channelManager.createSslHandler(peerHost, peerPort); From 6918cced9415327adf2d636d66d84bb8d604c6fc Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Sat, 23 Aug 2014 14:12:35 +0200 Subject: [PATCH 0160/2020] Do not block the WebSocket from receiving bytes or text fragments, close #660 --- .../providers/netty/ws/NettyWebSocket.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java index d93e3ef8dc..3cdb818a50 100755 --- a/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java +++ b/providers/netty/src/main/java/org/asynchttpclient/providers/netty/ws/NettyWebSocket.java @@ -179,10 +179,8 @@ private boolean hasWebSocketTextListener() { @Override public WebSocket addWebSocketListener(WebSocketListener l) { listeners.add(l); - if (l instanceof WebSocketByteListener) - interestedInByteMessages = true; - else if (l instanceof WebSocketTextListener) - interestedInTextMessages = true; + interestedInByteMessages = interestedInByteMessages || l instanceof WebSocketByteListener; + interestedInTextMessages = interestedInTextMessages || l instanceof WebSocketTextListener; return this; } @@ -192,7 +190,7 @@ public WebSocket removeWebSocketListener(WebSocketListener l) { if (l instanceof WebSocketByteListener) interestedInByteMessages = hasWebSocketByteListener(); - else if (l instanceof WebSocketTextListener) + if (l instanceof WebSocketTextListener) interestedInTextMessages = hasWebSocketTextListener(); return this; From 21660bae880b13b4909a2e018648e3e9723ef2e2 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Sat, 23 Aug 2014 14:15:08 +0200 Subject: [PATCH 0161/2020] Wrong javadoc, close #666 --- api/src/main/java/org/asynchttpclient/Request.java | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/main/java/org/asynchttpclient/Request.java b/api/src/main/java/org/asynchttpclient/Request.java index 597931456f..e292e8b5e8 100644 --- a/api/src/main/java/org/asynchttpclient/Request.java +++ b/api/src/main/java/org/asynchttpclient/Request.java @@ -34,7 +34,6 @@ * .setPassword(admin) * .setRealmName("MyRealm") * .setScheme(Realm.AuthScheme.DIGEST).build()); - * r.execute(); * */ public interface Request { From b0ec7416912eeb0ca835bdead850f38c7e6e8851 Mon Sep 17 00:00:00 2001 From: Stephane Landelle Date: Sat, 23 Aug 2014 15:04:53 +0200 Subject: [PATCH 0162/2020] Add handler callbacks, close #673 --- .../AsyncHandlerExtensions.java | 24 ++++++++++++++--- .../netty/request/NettyConnectListener.java | 27 ++++++++++--------- .../netty/request/NettyRequestSender.java | 19 +++++++++---- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/api/src/main/java/org/asynchttpclient/AsyncHandlerExtensions.java b/api/src/main/java/org/asynchttpclient/AsyncHandlerExtensions.java index fd4a5bf79e..1d81570b0f 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: *