hc-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ol...@apache.org
Subject [14/17] httpcomponents-client git commit: Moved classes and renamed packages (no functional changes)
Date Sat, 02 Sep 2017 15:28:14 GMT
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultRedirectStrategy.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultRedirectStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultRedirectStrategy.java
new file mode 100644
index 0000000..75c3cda
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultRedirectStrategy.java
@@ -0,0 +1,179 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.impl;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Locale;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.hc.client5.http.CircularRedirectException;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.protocol.RedirectLocations;
+import org.apache.hc.client5.http.protocol.RedirectStrategy;
+import org.apache.hc.client5.http.utils.URIUtils;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.net.URIBuilder;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.TextUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Default implementation of {@link RedirectStrategy}. This strategy honors the restrictions
+ * on automatic redirection of unsafe methods such as POST, PUT and DELETE imposed by
+ * the HTTP specification. Non safe methods will be redirected as GET in response to
+ * status code {@link HttpStatus#SC_MOVED_PERMANENTLY}, {@link HttpStatus#SC_MOVED_TEMPORARILY}
+ * and {@link HttpStatus#SC_SEE_OTHER}.
+ *
+ * @since 4.1
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+public class DefaultRedirectStrategy implements RedirectStrategy {
+
+    private final Logger log = LogManager.getLogger(getClass());
+
+    public static final DefaultRedirectStrategy INSTANCE = new DefaultRedirectStrategy();
+
+    private final ConcurrentMap<String, Boolean> safeMethods;
+
+    public DefaultRedirectStrategy(final String... safeMethods) {
+        super();
+        this.safeMethods = new ConcurrentHashMap<>();
+        for (final String safeMethod: safeMethods) {
+            this.safeMethods.put(safeMethod.toUpperCase(Locale.ROOT), Boolean.TRUE);
+        }
+    }
+
+    public DefaultRedirectStrategy() {
+        this("GET", "HEAD", "OPTIONS", "TRACE");
+    }
+
+    @Override
+    public boolean isRedirected(
+            final HttpRequest request,
+            final HttpResponse response,
+            final HttpContext context) throws ProtocolException {
+        Args.notNull(request, "HTTP request");
+        Args.notNull(response, "HTTP response");
+
+        if (!response.containsHeader(HttpHeaders.LOCATION)) {
+            return false;
+        }
+        final int statusCode = response.getCode();
+        switch (statusCode) {
+            case HttpStatus.SC_MOVED_PERMANENTLY:
+            case HttpStatus.SC_MOVED_TEMPORARILY:
+            case HttpStatus.SC_SEE_OTHER:
+            case HttpStatus.SC_TEMPORARY_REDIRECT:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public URI getLocationURI(
+            final HttpRequest request,
+            final HttpResponse response,
+            final HttpContext context) throws HttpException {
+        Args.notNull(request, "HTTP request");
+        Args.notNull(response, "HTTP response");
+        Args.notNull(context, "HTTP context");
+
+        final HttpClientContext clientContext = HttpClientContext.adapt(context);
+
+        //get the location header to find out where to redirect to
+        final Header locationHeader = response.getFirstHeader("location");
+        if (locationHeader == null) {
+            throw new HttpException("Redirect location is missing");
+        }
+        final String location = locationHeader.getValue();
+        if (this.log.isDebugEnabled()) {
+            this.log.debug("Redirect requested to location '" + location + "'");
+        }
+
+        final RequestConfig config = clientContext.getRequestConfig();
+
+        URI uri = createLocationURI(location);
+        try {
+            if (!uri.isAbsolute()) {
+                // Resolve location URI
+                uri = URIUtils.resolve(request.getUri(), uri);
+            }
+        } catch (final URISyntaxException ex) {
+            throw new ProtocolException(ex.getMessage(), ex);
+        }
+
+        RedirectLocations redirectLocations = (RedirectLocations) clientContext.getAttribute(
+                HttpClientContext.REDIRECT_LOCATIONS);
+        if (redirectLocations == null) {
+            redirectLocations = new RedirectLocations();
+            context.setAttribute(HttpClientContext.REDIRECT_LOCATIONS, redirectLocations);
+        }
+        if (!config.isCircularRedirectsAllowed()) {
+            if (redirectLocations.contains(uri)) {
+                throw new CircularRedirectException("Circular redirect to '" + uri + "'");
+            }
+        }
+        redirectLocations.add(uri);
+        return uri;
+    }
+
+    /**
+     * @since 4.1
+     */
+    protected URI createLocationURI(final String location) throws ProtocolException {
+        try {
+            final URIBuilder b = new URIBuilder(new URI(location).normalize());
+            final String host = b.getHost();
+            if (host != null) {
+                b.setHost(host.toLowerCase(Locale.ROOT));
+            }
+            final String path = b.getPath();
+            if (TextUtils.isEmpty(path)) {
+                b.setPath("/");
+            }
+            return b.build();
+        } catch (final URISyntaxException ex) {
+            throw new ProtocolException("Invalid redirect URI: " + location, ex);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultServiceUnavailableRetryStrategy.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultServiceUnavailableRetryStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultServiceUnavailableRetryStrategy.java
new file mode 100644
index 0000000..b5b0cc2
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultServiceUnavailableRetryStrategy.java
@@ -0,0 +1,100 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.impl;
+
+import java.util.Date;
+
+import org.apache.hc.client5.http.ServiceUnavailableRetryStrategy;
+import org.apache.hc.client5.http.utils.DateUtils;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.Args;
+
+/**
+ * Default implementation of the {@link ServiceUnavailableRetryStrategy} interface.
+ * that retries {@code 503} (Service Unavailable) responses for a fixed number of times
+ * at a fixed interval.
+ *
+ * @since 4.2
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+public class DefaultServiceUnavailableRetryStrategy implements ServiceUnavailableRetryStrategy {
+
+    /**
+     * Maximum number of allowed retries if the server responds with a HTTP code
+     * in our retry code list. Default value is 1.
+     */
+    private final int maxRetries;
+
+    /**
+     * Retry interval between subsequent requests, in milliseconds. Default
+     * value is 1 second.
+     */
+    private final long defaultRetryInterval;
+
+    public DefaultServiceUnavailableRetryStrategy(final int maxRetries, final int defaultRetryInterval) {
+        super();
+        Args.positive(maxRetries, "Max retries");
+        Args.positive(defaultRetryInterval, "Retry interval");
+        this.maxRetries = maxRetries;
+        this.defaultRetryInterval = defaultRetryInterval;
+    }
+
+    public DefaultServiceUnavailableRetryStrategy() {
+        this(1, 1000);
+    }
+
+    @Override
+    public boolean retryRequest(final HttpResponse response, final int executionCount, final HttpContext context) {
+        return executionCount <= maxRetries && response.getCode() == HttpStatus.SC_SERVICE_UNAVAILABLE;
+    }
+
+    @Override
+    public long getRetryInterval(final HttpResponse response, final HttpContext context) {
+        final Header header = response.getFirstHeader(HttpHeaders.RETRY_AFTER);
+        if (header != null) {
+            final String value = header.getValue();
+            try {
+                return Long.parseLong(value) * 1000;
+            } catch (final NumberFormatException ignore) {
+                final Date date = DateUtils.parseDate(value);
+                if (date != null) {
+                    final long n = date.getTime() - System.currentTimeMillis();
+                    return n > 0 ? n : 0;
+                }
+            }
+        }
+        return this.defaultRetryInterval;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultUserTokenHandler.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultUserTokenHandler.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultUserTokenHandler.java
index c3be5bd..481d915 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultUserTokenHandler.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultUserTokenHandler.java
@@ -31,10 +31,10 @@ import java.security.Principal;
 import javax.net.ssl.SSLSession;
 
 import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.UserTokenHandler;
 import org.apache.hc.client5.http.auth.AuthExchange;
 import org.apache.hc.client5.http.auth.AuthScheme;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.client5.http.protocol.UserTokenHandler;
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.http.protocol.HttpContext;

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/NoopUserTokenHandler.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/NoopUserTokenHandler.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/NoopUserTokenHandler.java
index 7c3b0f8..332796e 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/NoopUserTokenHandler.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/NoopUserTokenHandler.java
@@ -27,7 +27,7 @@
 package org.apache.hc.client5.http.impl;
 
 import org.apache.hc.client5.http.HttpRoute;
-import org.apache.hc.client5.http.protocol.UserTokenHandler;
+import org.apache.hc.client5.http.UserTokenHandler;
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.http.protocol.HttpContext;

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/TunnelRefusedException.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/TunnelRefusedException.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/TunnelRefusedException.java
new file mode 100644
index 0000000..188dbfc
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/TunnelRefusedException.java
@@ -0,0 +1,52 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.impl;
+
+import org.apache.hc.core5.http.HttpException;
+
+/**
+ * Signals that the tunnel request was rejected by the proxy host.
+ *
+ * @since 4.0
+ */
+public class TunnelRefusedException extends HttpException {
+
+    private static final long serialVersionUID = -8646722842745617323L;
+
+    private final String responseMesage;
+
+    public TunnelRefusedException(final String message, final String responseMesage) {
+        super(message);
+        this.responseMesage = responseMesage;
+    }
+
+    public String getResponseMessage() {
+        return this.responseMesage;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java
index c2dea36..e10b3c0 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java
@@ -30,6 +30,7 @@ package org.apache.hc.client5.http.impl.async;
 import java.io.IOException;
 import java.io.InterruptedIOException;
 
+import org.apache.hc.client5.http.AuthenticationStrategy;
 import org.apache.hc.client5.http.HttpRoute;
 import org.apache.hc.client5.http.RouteTracker;
 import org.apache.hc.client5.http.async.AsyncExecCallback;
@@ -39,10 +40,9 @@ import org.apache.hc.client5.http.async.AsyncExecRuntime;
 import org.apache.hc.client5.http.auth.AuthExchange;
 import org.apache.hc.client5.http.auth.ChallengeType;
 import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.TunnelRefusedException;
 import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
 import org.apache.hc.client5.http.impl.routing.BasicRouteDirector;
-import org.apache.hc.client5.http.impl.sync.TunnelRefusedException;
-import org.apache.hc.client5.http.protocol.AuthenticationStrategy;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.routing.HttpRouteDirector;
 import org.apache.hc.core5.annotation.Contract;

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncMainClientExec.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncMainClientExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncMainClientExec.java
index f015d62..d3f7fde 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncMainClientExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncMainClientExec.java
@@ -34,12 +34,12 @@ import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
 import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.UserTokenHandler;
 import org.apache.hc.client5.http.async.AsyncExecCallback;
 import org.apache.hc.client5.http.async.AsyncExecChain;
 import org.apache.hc.client5.http.async.AsyncExecChainHandler;
 import org.apache.hc.client5.http.async.AsyncExecRuntime;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.client5.http.protocol.UserTokenHandler;
 import org.apache.hc.core5.http.EntityDetails;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpException;

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java
index bf20005..decf267 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java
@@ -32,6 +32,7 @@ import java.net.URISyntaxException;
 import java.util.Iterator;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import org.apache.hc.client5.http.AuthenticationStrategy;
 import org.apache.hc.client5.http.HttpRoute;
 import org.apache.hc.client5.http.StandardMethods;
 import org.apache.hc.client5.http.async.AsyncExecCallback;
@@ -42,10 +43,9 @@ import org.apache.hc.client5.http.auth.AuthExchange;
 import org.apache.hc.client5.http.auth.ChallengeType;
 import org.apache.hc.client5.http.auth.CredentialsProvider;
 import org.apache.hc.client5.http.auth.CredentialsStore;
-import org.apache.hc.client5.http.impl.AuthSupport;
 import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.AuthSupport;
 import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
-import org.apache.hc.client5.http.protocol.AuthenticationStrategy;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.utils.URIUtils;
 import org.apache.hc.core5.annotation.Contract;

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncRedirectExec.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncRedirectExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncRedirectExec.java
index 9cbb234..0f13a07 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncRedirectExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncRedirectExec.java
@@ -31,6 +31,7 @@ import java.net.URI;
 import java.util.List;
 
 import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.RedirectException;
 import org.apache.hc.client5.http.StandardMethods;
 import org.apache.hc.client5.http.async.AsyncExecCallback;
 import org.apache.hc.client5.http.async.AsyncExecChain;
@@ -39,7 +40,6 @@ import org.apache.hc.client5.http.auth.AuthExchange;
 import org.apache.hc.client5.http.auth.AuthScheme;
 import org.apache.hc.client5.http.config.RequestConfig;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.client5.http.protocol.RedirectException;
 import org.apache.hc.client5.http.protocol.RedirectStrategy;
 import org.apache.hc.client5.http.routing.HttpRoutePlanner;
 import org.apache.hc.client5.http.utils.URIUtils;

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncRetryExec.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncRetryExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncRetryExec.java
index e6cd13d..162915e 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncRetryExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncRetryExec.java
@@ -28,13 +28,13 @@ package org.apache.hc.client5.http.impl.async;
 
 import java.io.IOException;
 
+import org.apache.hc.client5.http.HttpRequestRetryHandler;
 import org.apache.hc.client5.http.HttpRoute;
 import org.apache.hc.client5.http.async.AsyncExecCallback;
 import org.apache.hc.client5.http.async.AsyncExecChain;
 import org.apache.hc.client5.http.async.AsyncExecChainHandler;
 import org.apache.hc.client5.http.impl.ExecSupport;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.client5.http.sync.HttpRequestRetryHandler;
 import org.apache.hc.core5.http.EntityDetails;
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpRequest;

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
index 61b6abb..514b2d0 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
@@ -38,9 +38,12 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.ThreadFactory;
 
+import org.apache.hc.client5.http.AuthenticationStrategy;
 import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
+import org.apache.hc.client5.http.HttpRequestRetryHandler;
 import org.apache.hc.client5.http.SchemePortResolver;
 import org.apache.hc.client5.http.SystemDefaultDnsResolver;
+import org.apache.hc.client5.http.UserTokenHandler;
 import org.apache.hc.client5.http.async.AsyncExecChainHandler;
 import org.apache.hc.client5.http.auth.AuthSchemeProvider;
 import org.apache.hc.client5.http.auth.CredentialsProvider;
@@ -49,11 +52,17 @@ import org.apache.hc.client5.http.config.RequestConfig;
 import org.apache.hc.client5.http.cookie.BasicCookieStore;
 import org.apache.hc.client5.http.cookie.CookieSpecProvider;
 import org.apache.hc.client5.http.cookie.CookieStore;
+import org.apache.hc.client5.http.impl.ChainElements;
+import org.apache.hc.client5.http.impl.CookieSpecRegistries;
+import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
 import org.apache.hc.client5.http.impl.DefaultConnectionKeepAliveStrategy;
+import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryHandler;
+import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
 import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
 import org.apache.hc.client5.http.impl.DefaultUserTokenHandler;
 import org.apache.hc.client5.http.impl.IdleConnectionEvictor;
 import org.apache.hc.client5.http.impl.NoopUserTokenHandler;
+import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
 import org.apache.hc.client5.http.impl.auth.BasicSchemeFactory;
 import org.apache.hc.client5.http.impl.auth.CredSspSchemeFactory;
 import org.apache.hc.client5.http.impl.auth.DigestSchemeFactory;
@@ -62,26 +71,17 @@ import org.apache.hc.client5.http.impl.auth.NTLMSchemeFactory;
 import org.apache.hc.client5.http.impl.auth.SPNegoSchemeFactory;
 import org.apache.hc.client5.http.impl.auth.SystemDefaultCredentialsProvider;
 import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
-import org.apache.hc.client5.http.impl.protocol.DefaultAuthenticationStrategy;
-import org.apache.hc.client5.http.impl.protocol.DefaultRedirectStrategy;
 import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
 import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
 import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
-import org.apache.hc.client5.http.impl.sync.BasicCredentialsProvider;
-import org.apache.hc.client5.http.impl.sync.ChainElements;
-import org.apache.hc.client5.http.impl.sync.CookieSpecRegistries;
-import org.apache.hc.client5.http.impl.sync.DefaultHttpRequestRetryHandler;
 import org.apache.hc.client5.http.nio.AsyncClientConnectionManager;
-import org.apache.hc.client5.http.protocol.AuthenticationStrategy;
 import org.apache.hc.client5.http.protocol.RedirectStrategy;
 import org.apache.hc.client5.http.protocol.RequestAddCookies;
 import org.apache.hc.client5.http.protocol.RequestAuthCache;
 import org.apache.hc.client5.http.protocol.RequestDefaultHeaders;
 import org.apache.hc.client5.http.protocol.RequestExpectContinue;
 import org.apache.hc.client5.http.protocol.ResponseProcessCookies;
-import org.apache.hc.client5.http.protocol.UserTokenHandler;
 import org.apache.hc.client5.http.routing.HttpRoutePlanner;
-import org.apache.hc.client5.http.sync.HttpRequestRetryHandler;
 import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.concurrent.DefaultThreadFactory;
 import org.apache.hc.core5.function.Callback;

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicCredentialsProvider.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicCredentialsProvider.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicCredentialsProvider.java
new file mode 100644
index 0000000..26465ca
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/BasicCredentialsProvider.java
@@ -0,0 +1,115 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.impl.auth;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.Credentials;
+import org.apache.hc.client5.http.auth.CredentialsStore;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.Args;
+
+/**
+ * Default implementation of {@link CredentialsStore}.
+ *
+ * @since 4.0
+ */
+@Contract(threading = ThreadingBehavior.SAFE)
+public class BasicCredentialsProvider implements CredentialsStore {
+
+    private final ConcurrentHashMap<AuthScope, Credentials> credMap;
+
+    /**
+     * Default constructor.
+     */
+    public BasicCredentialsProvider() {
+        super();
+        this.credMap = new ConcurrentHashMap<>();
+    }
+
+    @Override
+    public void setCredentials(
+            final AuthScope authscope,
+            final Credentials credentials) {
+        Args.notNull(authscope, "Authentication scope");
+        credMap.put(authscope, credentials);
+    }
+
+    /**
+     * Find matching {@link Credentials credentials} for the given authentication scope.
+     *
+     * @param map the credentials hash map
+     * @param authscope the {@link AuthScope authentication scope}
+     * @return the credentials
+     *
+     */
+    private static Credentials matchCredentials(
+            final Map<AuthScope, Credentials> map,
+            final AuthScope authscope) {
+        // see if we get a direct hit
+        Credentials creds = map.get(authscope);
+        if (creds == null) {
+            // Nope.
+            // Do a full scan
+            int bestMatchFactor  = -1;
+            AuthScope bestMatch  = null;
+            for (final AuthScope current: map.keySet()) {
+                final int factor = authscope.match(current);
+                if (factor > bestMatchFactor) {
+                    bestMatchFactor = factor;
+                    bestMatch = current;
+                }
+            }
+            if (bestMatch != null) {
+                creds = map.get(bestMatch);
+            }
+        }
+        return creds;
+    }
+
+    @Override
+    public Credentials getCredentials(final AuthScope authscope,
+                                      final HttpContext httpContext) {
+        Args.notNull(authscope, "Authentication scope");
+        return matchCredentials(this.credMap, authscope);
+    }
+
+    @Override
+    public void clear() {
+        this.credMap.clear();
+    }
+
+    @Override
+    public String toString() {
+        return credMap.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/HttpAuthenticator.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/HttpAuthenticator.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/HttpAuthenticator.java
index c475c4a..98d2d43 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/HttpAuthenticator.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/HttpAuthenticator.java
@@ -34,6 +34,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Queue;
 
+import org.apache.hc.client5.http.AuthenticationStrategy;
 import org.apache.hc.client5.http.auth.AuthCache;
 import org.apache.hc.client5.http.auth.AuthChallenge;
 import org.apache.hc.client5.http.auth.AuthExchange;
@@ -43,7 +44,6 @@ import org.apache.hc.client5.http.auth.ChallengeType;
 import org.apache.hc.client5.http.auth.CredentialsProvider;
 import org.apache.hc.client5.http.auth.MalformedChallengeException;
 import org.apache.hc.client5.http.config.AuthSchemes;
-import org.apache.hc.client5.http.protocol.AuthenticationStrategy;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.core5.http.FormattedHeader;
 import org.apache.hc.core5.http.Header;

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SystemDefaultCredentialsProvider.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SystemDefaultCredentialsProvider.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SystemDefaultCredentialsProvider.java
index eb43dbe..e0aa8bb 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SystemDefaultCredentialsProvider.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SystemDefaultCredentialsProvider.java
@@ -42,7 +42,6 @@ import org.apache.hc.client5.http.auth.CredentialsStore;
 import org.apache.hc.client5.http.auth.NTCredentials;
 import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
 import org.apache.hc.client5.http.config.AuthSchemes;
-import org.apache.hc.client5.http.impl.sync.BasicCredentialsProvider;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/AIMDBackoffManager.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/AIMDBackoffManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/AIMDBackoffManager.java
new file mode 100644
index 0000000..cec7453
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/AIMDBackoffManager.java
@@ -0,0 +1,166 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.impl.classic;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.classic.BackoffManager;
+import org.apache.hc.core5.pool.ConnPoolControl;
+import org.apache.hc.core5.util.Args;
+
+/**
+ * <p>The {@code AIMDBackoffManager} applies an additive increase,
+ * multiplicative decrease (AIMD) to managing a dynamic limit to
+ * the number of connections allowed to a given host. You may want
+ * to experiment with the settings for the cooldown periods and the
+ * backoff factor to get the adaptive behavior you want.</p>
+ *
+ * <p>Generally speaking, shorter cooldowns will lead to more steady-state
+ * variability but faster reaction times, while longer cooldowns
+ * will lead to more stable equilibrium behavior but slower reaction
+ * times.</p>
+ *
+ * <p>Similarly, higher backoff factors promote greater
+ * utilization of available capacity at the expense of fairness
+ * among clients. Lower backoff factors allow equal distribution of
+ * capacity among clients (fairness) to happen faster, at the
+ * expense of having more server capacity unused in the short term.</p>
+ *
+ * @since 4.2
+ */
+public class AIMDBackoffManager implements BackoffManager {
+
+    private final ConnPoolControl<HttpRoute> connPerRoute;
+    private final Clock clock;
+    private final Map<HttpRoute,Long> lastRouteProbes;
+    private final Map<HttpRoute,Long> lastRouteBackoffs;
+    private long coolDown = 5 * 1000L;
+    private double backoffFactor = 0.5;
+    private int cap = 2; // Per RFC 2616 sec 8.1.4
+
+    /**
+     * Creates an {@code AIMDBackoffManager} to manage
+     * per-host connection pool sizes represented by the
+     * given {@link ConnPoolControl}.
+     * @param connPerRoute per-host routing maximums to
+     *   be managed
+     */
+    public AIMDBackoffManager(final ConnPoolControl<HttpRoute> connPerRoute) {
+        this(connPerRoute, new SystemClock());
+    }
+
+    AIMDBackoffManager(final ConnPoolControl<HttpRoute> connPerRoute, final Clock clock) {
+        this.clock = clock;
+        this.connPerRoute = connPerRoute;
+        this.lastRouteProbes = new HashMap<>();
+        this.lastRouteBackoffs = new HashMap<>();
+    }
+
+    @Override
+    public void backOff(final HttpRoute route) {
+        synchronized(connPerRoute) {
+            final int curr = connPerRoute.getMaxPerRoute(route);
+            final Long lastUpdate = getLastUpdate(lastRouteBackoffs, route);
+            final long now = clock.getCurrentTime();
+            if (now - lastUpdate.longValue() < coolDown) {
+                return;
+            }
+            connPerRoute.setMaxPerRoute(route, getBackedOffPoolSize(curr));
+            lastRouteBackoffs.put(route, Long.valueOf(now));
+        }
+    }
+
+    private int getBackedOffPoolSize(final int curr) {
+        if (curr <= 1) {
+            return 1;
+        }
+        return (int)(Math.floor(backoffFactor * curr));
+    }
+
+    @Override
+    public void probe(final HttpRoute route) {
+        synchronized(connPerRoute) {
+            final int curr = connPerRoute.getMaxPerRoute(route);
+            final int max = (curr >= cap) ? cap : curr + 1;
+            final Long lastProbe = getLastUpdate(lastRouteProbes, route);
+            final Long lastBackoff = getLastUpdate(lastRouteBackoffs, route);
+            final long now = clock.getCurrentTime();
+            if (now - lastProbe.longValue() < coolDown || now - lastBackoff.longValue() < coolDown) {
+                return;
+            }
+            connPerRoute.setMaxPerRoute(route, max);
+            lastRouteProbes.put(route, Long.valueOf(now));
+        }
+    }
+
+    private Long getLastUpdate(final Map<HttpRoute,Long> updates, final HttpRoute route) {
+        Long lastUpdate = updates.get(route);
+        if (lastUpdate == null) {
+            lastUpdate = Long.valueOf(0L);
+        }
+        return lastUpdate;
+    }
+
+    /**
+     * Sets the factor to use when backing off; the new
+     * per-host limit will be roughly the current max times
+     * this factor. {@code Math.floor} is applied in the
+     * case of non-integer outcomes to ensure we actually
+     * decrease the pool size. Pool sizes are never decreased
+     * below 1, however. Defaults to 0.5.
+     * @param d must be between 0.0 and 1.0, exclusive.
+     */
+    public void setBackoffFactor(final double d) {
+        Args.check(d > 0.0 && d < 1.0, "Backoff factor must be 0.0 < f < 1.0");
+        backoffFactor = d;
+    }
+
+    /**
+     * Sets the amount of time, in milliseconds, to wait between
+     * adjustments in pool sizes for a given host, to allow
+     * enough time for the adjustments to take effect. Defaults
+     * to 5000L (5 seconds).
+     * @param l must be positive
+     */
+    public void setCooldownMillis(final long l) {
+        Args.positive(coolDown, "Cool down");
+        coolDown = l;
+    }
+
+    /**
+     * Sets the absolute maximum per-host connection pool size to
+     * probe up to; defaults to 2 (the default per-host max).
+     * @param cap must be &gt;= 1
+     */
+    public void setPerHostConnectionCap(final int cap) {
+        Args.positive(cap, "Per host connection cap");
+        this.cap = cap;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/AbstractHttpClientResponseHandler.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/AbstractHttpClientResponseHandler.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/AbstractHttpClientResponseHandler.java
new file mode 100644
index 0000000..9cec146
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/AbstractHttpClientResponseHandler.java
@@ -0,0 +1,79 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.impl.classic;
+
+import java.io.IOException;
+
+import org.apache.hc.client5.http.HttpResponseException;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+
+/**
+ * A generic {@link HttpClientResponseHandler} that works with the response entity
+ * for successful (2xx) responses. If the response code was &gt;= 300, the response
+ * body is consumed and an {@link HttpResponseException} is thrown.
+ * <p>
+ * If this is used with
+ * {@link org.apache.hc.client5.http.classic.HttpClient#execute(
+ * org.apache.hc.core5.http.ClassicHttpRequest, HttpClientResponseHandler)},
+ * HttpClient may handle redirects (3xx responses) internally.
+ * </p>
+ *
+ * @since 4.4
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+public abstract class AbstractHttpClientResponseHandler<T> implements HttpClientResponseHandler<T> {
+
+    /**
+     * Read the entity from the response body and pass it to the entity handler
+     * method if the response was successful (a 2xx status code). If no response
+     * body exists, this returns null. If the response was unsuccessful (&gt;= 300
+     * status code), throws an {@link HttpResponseException}.
+     */
+    @Override
+    public T handleResponse(final ClassicHttpResponse response) throws IOException {
+        final HttpEntity entity = response.getEntity();
+        if (response.getCode() >= HttpStatus.SC_REDIRECTION) {
+            EntityUtils.consume(entity);
+            throw new HttpResponseException(response.getCode(), response.getReasonPhrase());
+        }
+        return entity == null ? null : handleEntity(entity);
+    }
+
+    /**
+     * Handle the response entity and transform it into the actual response
+     * object.
+     */
+    public abstract T handleEntity(HttpEntity entity) throws IOException;
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/BackoffStrategyExec.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/BackoffStrategyExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/BackoffStrategyExec.java
new file mode 100644
index 0000000..9733b40
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/BackoffStrategyExec.java
@@ -0,0 +1,89 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.impl.classic;
+
+import java.io.IOException;
+
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.classic.BackoffManager;
+import org.apache.hc.client5.http.classic.ConnectionBackoffStrategy;
+import org.apache.hc.client5.http.classic.ExecChain;
+import org.apache.hc.client5.http.classic.ExecChainHandler;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.util.Args;
+
+/**
+ * @since 4.3
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+final class BackoffStrategyExec implements ExecChainHandler {
+
+    private final ConnectionBackoffStrategy connectionBackoffStrategy;
+    private final BackoffManager backoffManager;
+
+    public BackoffStrategyExec(
+            final ConnectionBackoffStrategy connectionBackoffStrategy,
+            final BackoffManager backoffManager) {
+        super();
+        Args.notNull(connectionBackoffStrategy, "Connection backoff strategy");
+        Args.notNull(backoffManager, "Backoff manager");
+        this.connectionBackoffStrategy = connectionBackoffStrategy;
+        this.backoffManager = backoffManager;
+    }
+
+    @Override
+    public ClassicHttpResponse execute(
+            final ClassicHttpRequest request,
+            final ExecChain.Scope scope,
+            final ExecChain chain) throws IOException, HttpException {
+        Args.notNull(request, "HTTP request");
+        Args.notNull(scope, "Scope");
+        final HttpRoute route = scope.route;
+
+        final ClassicHttpResponse response;
+        try {
+            response = chain.proceed(request, scope);
+        } catch (final IOException | HttpException ex) {
+            if (this.connectionBackoffStrategy.shouldBackoff(ex)) {
+                this.backoffManager.backOff(route);
+            }
+            throw ex;
+        }
+        if (this.connectionBackoffStrategy.shouldBackoff(response)) {
+            this.backoffManager.backOff(route);
+        } else {
+            this.backoffManager.probe(route);
+        }
+        return response;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/BasicHttpClientResponseHandler.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/BasicHttpClientResponseHandler.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/BasicHttpClientResponseHandler.java
new file mode 100644
index 0000000..189cbcd
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/BasicHttpClientResponseHandler.java
@@ -0,0 +1,75 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.impl.classic;
+
+import java.io.IOException;
+
+import org.apache.hc.client5.http.ClientProtocolException;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.ParseException;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+
+/**
+ * A {@link org.apache.hc.core5.http.io.HttpClientResponseHandler} that returns
+ * the response body as a String for successful (2xx) responses. If the response
+ * code was &gt;= 300, the response body is consumed
+ * and an {@link org.apache.hc.client5.http.HttpResponseException} is thrown.
+ * <p>
+ * If this is used with
+ * {@link org.apache.hc.client5.http.classic.HttpClient#execute(
+ *  org.apache.hc.core5.http.ClassicHttpRequest,
+ *  org.apache.hc.core5.http.io.HttpClientResponseHandler)},
+ * HttpClient may handle redirects (3xx responses) internally.
+ * </p>
+ *
+ * @since 4.0
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+public class BasicHttpClientResponseHandler extends AbstractHttpClientResponseHandler<String> {
+
+    /**
+     * Returns the entity as a body as a String.
+     */
+    @Override
+    public String handleEntity(final HttpEntity entity) throws IOException {
+        try {
+            return EntityUtils.toString(entity);
+        } catch (final ParseException ex) {
+            throw new ClientProtocolException(ex);
+        }
+    }
+
+    @Override
+    public String handleResponse(final ClassicHttpResponse response) throws IOException {
+        return super.handleResponse(response);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/Clock.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/Clock.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/Clock.java
new file mode 100644
index 0000000..06b7ff4
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/Clock.java
@@ -0,0 +1,43 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.impl.classic;
+
+/**
+ * Interface used to enable easier testing of time-related behavior.
+ *
+ * @since 4.2
+ *
+ */
+interface Clock {
+
+    /**
+     * Returns the current time, expressed as the number of
+     * milliseconds since the epoch.
+     * @return current time
+     */
+    long getCurrentTime();
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/CloseableHttpClient.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/CloseableHttpClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/CloseableHttpClient.java
new file mode 100644
index 0000000..28fa833
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/CloseableHttpClient.java
@@ -0,0 +1,242 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.impl.classic;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.apache.hc.client5.http.ClientProtocolException;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.utils.URIUtils;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.Args;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Base implementation of {@link HttpClient} that also implements {@link Closeable}.
+ *
+ * @since 4.3
+ */
+@Contract(threading = ThreadingBehavior.SAFE)
+public abstract class CloseableHttpClient implements HttpClient, Closeable {
+
+    private final Logger log = LogManager.getLogger(getClass());
+
+    protected abstract CloseableHttpResponse doExecute(HttpHost target, ClassicHttpRequest request,
+                                                     HttpContext context) throws IOException;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public CloseableHttpResponse execute(
+            final HttpHost target,
+            final ClassicHttpRequest request,
+            final HttpContext context) throws IOException {
+        return doExecute(target, request, context);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public CloseableHttpResponse execute(
+            final ClassicHttpRequest request,
+            final HttpContext context) throws IOException {
+        Args.notNull(request, "HTTP request");
+        return doExecute(determineTarget(request), request, context);
+    }
+
+    private static HttpHost determineTarget(final ClassicHttpRequest request) throws ClientProtocolException {
+        // A null target may be acceptable if there is a default target.
+        // Otherwise, the null target is detected in the director.
+        HttpHost target = null;
+        URI requestURI = null;
+        try {
+            requestURI = request.getUri();
+        } catch (final URISyntaxException ignore) {
+        }
+        if (requestURI != null && requestURI.isAbsolute()) {
+            target = URIUtils.extractHost(requestURI);
+            if (target == null) {
+                throw new ClientProtocolException("URI does not specify a valid host name: "
+                        + requestURI);
+            }
+        }
+        return target;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public CloseableHttpResponse execute(
+            final ClassicHttpRequest request) throws IOException {
+        return execute(request, (HttpContext) null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public CloseableHttpResponse execute(
+            final HttpHost target,
+            final ClassicHttpRequest request) throws IOException {
+        return doExecute(target, request, null);
+    }
+
+    /**
+     * Executes a request using the default context and processes the
+     * response using the given response handler. The content entity associated
+     * with the response is fully consumed and the underlying connection is
+     * released back to the connection manager automatically in all cases
+     * relieving individual {@link HttpClientResponseHandler}s from having to manage
+     * resource deallocation internally.
+     *
+     * @param request   the request to execute
+     * @param HttpClientResponseHandler the response handler
+     *
+     * @return  the response object as generated by the response handler.
+     * @throws IOException in case of a problem or the connection was aborted
+     * @throws ClientProtocolException in case of an http protocol error
+     */
+    @Override
+    public <T> T execute(final ClassicHttpRequest request,
+            final HttpClientResponseHandler<? extends T> HttpClientResponseHandler) throws IOException {
+        return execute(request, HttpClientResponseHandler, null);
+    }
+
+    /**
+     * Executes a request using the default context and processes the
+     * response using the given response handler. The content entity associated
+     * with the response is fully consumed and the underlying connection is
+     * released back to the connection manager automatically in all cases
+     * relieving individual {@link HttpClientResponseHandler}s from having to manage
+     * resource deallocation internally.
+     *
+     * @param request   the request to execute
+     * @param HttpClientResponseHandler the response handler
+     * @param context   the context to use for the execution, or
+     *                  {@code null} to use the default context
+     *
+     * @return  the response object as generated by the response handler.
+     * @throws IOException in case of a problem or the connection was aborted
+     * @throws ClientProtocolException in case of an http protocol error
+     */
+    @Override
+    public <T> T execute(final ClassicHttpRequest request,
+            final HttpClientResponseHandler<? extends T> HttpClientResponseHandler, final HttpContext context)
+            throws IOException {
+        final HttpHost target = determineTarget(request);
+        return execute(target, request, HttpClientResponseHandler, context);
+    }
+
+    /**
+     * Executes a request using the default context and processes the
+     * response using the given response handler. The content entity associated
+     * with the response is fully consumed and the underlying connection is
+     * released back to the connection manager automatically in all cases
+     * relieving individual {@link HttpClientResponseHandler}s from having to manage
+     * resource deallocation internally.
+     *
+     * @param target    the target host for the request.
+     *                  Implementations may accept {@code null}
+     *                  if they can still determine a route, for example
+     *                  to a default target or by inspecting the request.
+     * @param request   the request to execute
+     * @param HttpClientResponseHandler the response handler
+     *
+     * @return  the response object as generated by the response handler.
+     * @throws IOException in case of a problem or the connection was aborted
+     * @throws ClientProtocolException in case of an http protocol error
+     */
+    @Override
+    public <T> T execute(final HttpHost target, final ClassicHttpRequest request,
+            final HttpClientResponseHandler<? extends T> HttpClientResponseHandler) throws IOException {
+        return execute(target, request, HttpClientResponseHandler, null);
+    }
+
+    /**
+     * Executes a request using the default context and processes the
+     * response using the given response handler. The content entity associated
+     * with the response is fully consumed and the underlying connection is
+     * released back to the connection manager automatically in all cases
+     * relieving individual {@link HttpClientResponseHandler}s from having to manage
+     * resource deallocation internally.
+     *
+     * @param target    the target host for the request.
+     *                  Implementations may accept {@code null}
+     *                  if they can still determine a route, for example
+     *                  to a default target or by inspecting the request.
+     * @param request   the request to execute
+     * @param HttpClientResponseHandler the response handler
+     * @param context   the context to use for the execution, or
+     *                  {@code null} to use the default context
+     *
+     * @return  the response object as generated by the response handler.
+     * @throws IOException in case of a problem or the connection was aborted
+     * @throws ClientProtocolException in case of an http protocol error
+     */
+    @Override
+    public <T> T execute(final HttpHost target, final ClassicHttpRequest request,
+            final HttpClientResponseHandler<? extends T> HttpClientResponseHandler, final HttpContext context) throws IOException {
+        Args.notNull(HttpClientResponseHandler, "Response handler");
+
+        try (final CloseableHttpResponse response = execute(target, request, context)) {
+            try {
+                final T result = HttpClientResponseHandler.handleResponse(response);
+                final HttpEntity entity = response.getEntity();
+                EntityUtils.consume(entity);
+                return result;
+            } catch (final HttpException t) {
+                // Try to salvage the underlying connection in case of a protocol exception
+                final HttpEntity entity = response.getEntity();
+                try {
+                    EntityUtils.consume(entity);
+                } catch (final Exception t2) {
+                    // Log this exception. The original exception is more
+                    // important and will be thrown to the caller.
+                    this.log.warn("Error consuming content after an exception.", t2);
+                }
+                throw new ClientProtocolException(t);
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/CloseableHttpResponse.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/CloseableHttpResponse.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/CloseableHttpResponse.java
new file mode 100644
index 0000000..146b2d7
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/CloseableHttpResponse.java
@@ -0,0 +1,217 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.impl.classic;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Locale;
+
+import org.apache.hc.client5.http.classic.ExecRuntime;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.ProtocolVersion;
+import org.apache.hc.core5.util.Args;
+
+/**
+ * Backward compatibility with HttpClient 4.x.
+ *
+ * @since 4.3
+ */
+public final class CloseableHttpResponse implements ClassicHttpResponse {
+
+    private final ClassicHttpResponse response;
+    private final ExecRuntime execRuntime;
+
+    static CloseableHttpResponse adapt(final ClassicHttpResponse response) {
+        if (response == null) {
+            return null;
+        }
+        if (response instanceof CloseableHttpResponse) {
+            return (CloseableHttpResponse) response;
+        } else {
+            return new CloseableHttpResponse(response, null);
+        }
+    }
+
+    CloseableHttpResponse(final ClassicHttpResponse response, final ExecRuntime execRuntime) {
+        this.response = Args.notNull(response, "Response");
+        this.execRuntime = execRuntime;
+    }
+
+    @Override
+    public int getCode() {
+        return response.getCode();
+    }
+
+    @Override
+    public HttpEntity getEntity() {
+        return response.getEntity();
+    }
+
+    @Override
+    public boolean containsHeader(final String name) {
+        return response.containsHeader(name);
+    }
+
+    @Override
+    public void setVersion(final ProtocolVersion version) {
+        response.setVersion(version);
+    }
+
+    @Override
+    public void setCode(final int code) {
+        response.setCode(code);
+    }
+
+    @Override
+    public String getReasonPhrase() {
+        return response.getReasonPhrase();
+    }
+
+    @Override
+    public int containsHeaders(final String name) {
+        return response.containsHeaders(name);
+    }
+
+    @Override
+    public void setEntity(final HttpEntity entity) {
+        response.setEntity(entity);
+    }
+
+    @Override
+    public ProtocolVersion getVersion() {
+        return response.getVersion();
+    }
+
+    @Override
+    public void setReasonPhrase(final String reason) {
+        response.setReasonPhrase(reason);
+    }
+
+    @Override
+    public Header[] getHeaders(final String name) {
+        return response.getHeaders(name);
+    }
+
+    @Override
+    public void addHeader(final Header header) {
+        response.addHeader(header);
+    }
+
+    @Override
+    public Locale getLocale() {
+        return response.getLocale();
+    }
+
+    @Override
+    public void addHeader(final String name, final Object value) {
+        response.addHeader(name, value);
+    }
+
+    @Override
+    public void setLocale(final Locale loc) {
+        response.setLocale(loc);
+    }
+
+    @Override
+    public Header getSingleHeader(final String name) throws ProtocolException {
+        return response.getSingleHeader(name);
+    }
+
+    @Override
+    public void setHeader(final Header header) {
+        response.setHeader(header);
+    }
+
+    @Override
+    public Header getFirstHeader(final String name) {
+        return response.getFirstHeader(name);
+    }
+
+    @Override
+    public void setHeader(final String name, final Object value) {
+        response.setHeader(name, value);
+    }
+
+    @Override
+    public void setHeaders(final Header... headers) {
+        response.setHeaders(headers);
+    }
+
+    @Override
+    public void removeHeader(final Header header) {
+        response.removeHeader(header);
+    }
+
+    @Override
+    public void removeHeaders(final String name) {
+        response.removeHeaders(name);
+    }
+
+    @Override
+    public Header getLastHeader(final String name) {
+        return response.getLastHeader(name);
+    }
+
+    @Override
+    public Header[] getAllHeaders() {
+        return response.getAllHeaders();
+    }
+
+    @Override
+    public Iterator<Header> headerIterator() {
+        return response.headerIterator();
+    }
+
+    @Override
+    public Iterator<Header> headerIterator(final String name) {
+        return response.headerIterator(name);
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (execRuntime != null) {
+            try {
+                response.close();
+                execRuntime.disconnect();
+            } finally {
+                execRuntime.discardConnection();
+            }
+        } else {
+            response.close();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return response.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ConnectExec.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ConnectExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ConnectExec.java
new file mode 100644
index 0000000..1fb5900
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ConnectExec.java
@@ -0,0 +1,274 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.impl.classic;
+
+import java.io.IOException;
+
+import org.apache.hc.client5.http.AuthenticationStrategy;
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.RouteTracker;
+import org.apache.hc.client5.http.auth.AuthExchange;
+import org.apache.hc.client5.http.auth.ChallengeType;
+import org.apache.hc.client5.http.classic.ExecChain;
+import org.apache.hc.client5.http.classic.ExecChainHandler;
+import org.apache.hc.client5.http.classic.ExecRuntime;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.TunnelRefusedException;
+import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
+import org.apache.hc.client5.http.impl.routing.BasicRouteDirector;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.routing.HttpRouteDirector;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ConnectionReuseStrategy;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
+import org.apache.hc.core5.http.message.StatusLine;
+import org.apache.hc.core5.http.protocol.HttpProcessor;
+import org.apache.hc.core5.util.Args;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Request executor in the HTTP request execution chain
+ * that is responsible for establishing connection to the target
+ * origin server as specified by the current route.
+ *
+ * @since 5.0
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
+public final class ConnectExec implements ExecChainHandler {
+
+    private final Logger log = LogManager.getLogger(getClass());
+
+    private final ConnectionReuseStrategy reuseStrategy;
+    private final HttpProcessor proxyHttpProcessor;
+    private final AuthenticationStrategy proxyAuthStrategy;
+    private final HttpAuthenticator authenticator;
+    private final HttpRouteDirector routeDirector;
+
+    public ConnectExec(
+            final ConnectionReuseStrategy reuseStrategy,
+            final HttpProcessor proxyHttpProcessor,
+            final AuthenticationStrategy proxyAuthStrategy) {
+        Args.notNull(reuseStrategy, "Connection reuse strategy");
+        Args.notNull(proxyHttpProcessor, "Proxy HTTP processor");
+        Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
+        this.reuseStrategy      = reuseStrategy;
+        this.proxyHttpProcessor = proxyHttpProcessor;
+        this.proxyAuthStrategy  = proxyAuthStrategy;
+        this.authenticator      = new HttpAuthenticator();
+        this.routeDirector      = new BasicRouteDirector();
+    }
+
+    @Override
+    public ClassicHttpResponse execute(
+            final ClassicHttpRequest request,
+            final ExecChain.Scope scope,
+            final ExecChain chain) throws IOException, HttpException {
+        Args.notNull(request, "HTTP request");
+        Args.notNull(scope, "Scope");
+
+        final HttpRoute route = scope.route;
+        final HttpClientContext context = scope.clientContext;
+        final ExecRuntime execRuntime = scope.execRuntime;
+
+        if (!execRuntime.isConnectionAcquired()) {
+            final Object userToken = context.getUserToken();
+            execRuntime.acquireConnection(route, userToken, context);
+        }
+        try {
+            if (!execRuntime.isConnected()) {
+                this.log.debug("Opening connection " + route);
+
+                final RouteTracker tracker = new RouteTracker(route);
+                int step;
+                do {
+                    final HttpRoute fact = tracker.toRoute();
+                    step = this.routeDirector.nextStep(route, fact);
+
+                    switch (step) {
+
+                        case HttpRouteDirector.CONNECT_TARGET:
+                            execRuntime.connect(context);
+                            tracker.connectTarget(route.isSecure());
+                            break;
+                        case HttpRouteDirector.CONNECT_PROXY:
+                            execRuntime.connect(context);
+                            final HttpHost proxy  = route.getProxyHost();
+                            tracker.connectProxy(proxy, false);
+                            break;
+                        case HttpRouteDirector.TUNNEL_TARGET: {
+                            final boolean secure = createTunnelToTarget(route, request, execRuntime, context);
+                            this.log.debug("Tunnel to target created.");
+                            tracker.tunnelTarget(secure);
+                        }   break;
+
+                        case HttpRouteDirector.TUNNEL_PROXY: {
+                            // The most simple example for this case is a proxy chain
+                            // of two proxies, where P1 must be tunnelled to P2.
+                            // route: Source -> P1 -> P2 -> Target (3 hops)
+                            // fact:  Source -> P1 -> Target       (2 hops)
+                            final int hop = fact.getHopCount()-1; // the hop to establish
+                            final boolean secure = createTunnelToProxy(route, hop, context);
+                            this.log.debug("Tunnel to proxy created.");
+                            tracker.tunnelProxy(route.getHopTarget(hop), secure);
+                        }   break;
+
+                        case HttpRouteDirector.LAYER_PROTOCOL:
+                            execRuntime.upgradeTls(context);
+                            tracker.layerProtocol(route.isSecure());
+                            break;
+
+                        case HttpRouteDirector.UNREACHABLE:
+                            throw new HttpException("Unable to establish route: " +
+                                    "planned = " + route + "; current = " + fact);
+                        case HttpRouteDirector.COMPLETE:
+                            break;
+                        default:
+                            throw new IllegalStateException("Unknown step indicator "
+                                    + step + " from RouteDirector.");
+                    }
+
+                } while (step > HttpRouteDirector.COMPLETE);
+            }
+            return chain.proceed(request, scope);
+
+        } catch (final IOException | HttpException | RuntimeException ex) {
+            execRuntime.discardConnection();
+            throw ex;
+        }
+    }
+
+    /**
+     * Creates a tunnel to the target server.
+     * The connection must be established to the (last) proxy.
+     * A CONNECT request for tunnelling through the proxy will
+     * be created and sent, the response received and checked.
+     * This method does <i>not</i> processChallenge the connection with
+     * information about the tunnel, that is left to the caller.
+     */
+    private boolean createTunnelToTarget(
+            final HttpRoute route,
+            final HttpRequest request,
+            final ExecRuntime execRuntime,
+            final HttpClientContext context) throws HttpException, IOException {
+
+        final RequestConfig config = context.getRequestConfig();
+
+        final HttpHost target = route.getTargetHost();
+        final HttpHost proxy = route.getProxyHost();
+        final AuthExchange proxyAuthExchange = context.getAuthExchange(proxy);
+        ClassicHttpResponse response = null;
+
+        final String authority = target.toHostString();
+        final ClassicHttpRequest connect = new BasicClassicHttpRequest("CONNECT", target, authority);
+        connect.setVersion(HttpVersion.HTTP_1_1);
+
+        this.proxyHttpProcessor.process(connect, null, context);
+
+        while (response == null) {
+            connect.removeHeaders(HttpHeaders.PROXY_AUTHORIZATION);
+            this.authenticator.addAuthResponse(proxy, ChallengeType.PROXY, connect, proxyAuthExchange, context);
+
+            response = execRuntime.execute(connect, context);
+
+            final int status = response.getCode();
+            if (status < HttpStatus.SC_SUCCESS) {
+                throw new HttpException("Unexpected response to CONNECT request: " + new StatusLine(response));
+            }
+
+            if (config.isAuthenticationEnabled()) {
+                if (this.authenticator.isChallenged(proxy, ChallengeType.PROXY, response,
+                        proxyAuthExchange, context)) {
+                    if (this.authenticator.prepareAuthResponse(proxy, ChallengeType.PROXY, response,
+                            this.proxyAuthStrategy, proxyAuthExchange, context)) {
+                        // Retry request
+                        if (this.reuseStrategy.keepAlive(request, response, context)) {
+                            this.log.debug("Connection kept alive");
+                            // Consume response content
+                            final HttpEntity entity = response.getEntity();
+                            EntityUtils.consume(entity);
+                        } else {
+                            execRuntime.disconnect();
+                        }
+                        response = null;
+                    }
+                }
+            }
+        }
+
+        final int status = response.getCode();
+        if (status >= HttpStatus.SC_REDIRECTION) {
+
+            // Buffer response content
+            final HttpEntity entity = response.getEntity();
+            final String responseMessage = entity != null ? EntityUtils.toString(entity) : null;
+            execRuntime.disconnect();
+            throw new TunnelRefusedException("CONNECT refused by proxy: " + new StatusLine(response), responseMessage);
+        }
+
+        // How to decide on security of the tunnelled connection?
+        // The socket factory knows only about the segment to the proxy.
+        // Even if that is secure, the hop to the target may be insecure.
+        // Leave it to derived classes, consider insecure by default here.
+        return false;
+    }
+
+    /**
+     * Creates a tunnel to an intermediate proxy.
+     * This method is <i>not</i> implemented in this class.
+     * It just throws an exception here.
+     */
+    private boolean createTunnelToProxy(
+            final HttpRoute route,
+            final int hop,
+            final HttpClientContext context) throws HttpException {
+
+        // Have a look at createTunnelToTarget and replicate the parts
+        // you need in a custom derived class. If your proxies don't require
+        // authentication, it is not too hard. But for the stock version of
+        // HttpClient, we cannot make such simplifying assumptions and would
+        // have to include proxy authentication code. The HttpComponents team
+        // is currently not in a position to support rarely used code of this
+        // complexity. Feel free to submit patches that refactor the code in
+        // createTunnelToTarget to facilitate re-use for proxy tunnelling.
+
+        throw new HttpException("Proxy chains are not supported.");
+    }
+
+}


Mime
View raw message