You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2017/09/02 15:28:12 UTC

[12/17] httpcomponents-client git commit: Moved classes and renamed packages (no functional changes)

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpRequestTaskCallable.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpRequestTaskCallable.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpRequestTaskCallable.java
new file mode 100644
index 0000000..eef7aa1
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpRequestTaskCallable.java
@@ -0,0 +1,120 @@
+/*
+ * ====================================================================
+ * 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.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.protocol.HttpContext;
+
+class HttpRequestTaskCallable<V> implements Callable<V> {
+
+    private final ClassicHttpRequest request;
+    private final HttpClient httpclient;
+    private final AtomicBoolean cancelled = new AtomicBoolean(false);
+
+    private final long scheduled = System.currentTimeMillis();
+    private long started = -1;
+    private long ended = -1;
+
+    private final HttpContext context;
+    private final HttpClientResponseHandler<V> responseHandler;
+    private final FutureCallback<V> callback;
+
+    private final FutureRequestExecutionMetrics metrics;
+
+    HttpRequestTaskCallable(
+            final HttpClient httpClient,
+            final ClassicHttpRequest request,
+            final HttpContext context,
+            final HttpClientResponseHandler<V> responseHandler,
+            final FutureCallback<V> callback,
+            final FutureRequestExecutionMetrics metrics) {
+        this.httpclient = httpClient;
+        this.responseHandler = responseHandler;
+        this.request = request;
+        this.context = context;
+        this.callback = callback;
+        this.metrics = metrics;
+    }
+
+    public long getScheduled() {
+        return scheduled;
+    }
+
+    public long getStarted() {
+        return started;
+    }
+
+    public long getEnded() {
+        return ended;
+    }
+
+    @Override
+    public V call() throws Exception {
+        if (!cancelled.get()) {
+            try {
+                metrics.getActiveConnections().incrementAndGet();
+                started = System.currentTimeMillis();
+                try {
+                    metrics.getScheduledConnections().decrementAndGet();
+                    final V result = httpclient.execute(request, responseHandler, context);
+                    ended = System.currentTimeMillis();
+                    metrics.getSuccessfulConnections().increment(started);
+                    if (callback != null) {
+                        callback.completed(result);
+                    }
+                    return result;
+                } catch (final Exception e) {
+                    metrics.getFailedConnections().increment(started);
+                    ended = System.currentTimeMillis();
+                    if (callback != null) {
+                        callback.failed(e);
+                    }
+                    throw e;
+                }
+            } finally {
+                metrics.getRequests().increment(started);
+                metrics.getTasks().increment(started);
+                metrics.getActiveConnections().decrementAndGet();
+            }
+        } else {
+            throw new IllegalStateException("call has been cancelled");
+        }
+    }
+
+    public void cancel() {
+        cancelled.set(true);
+        if (callback != null) {
+            callback.cancelled();
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/InternalHttpClient.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/InternalHttpClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/InternalHttpClient.java
new file mode 100644
index 0000000..b759809
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/InternalHttpClient.java
@@ -0,0 +1,188 @@
+/*
+ * ====================================================================
+ * 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.util.List;
+
+import org.apache.hc.client5.http.CancellableAware;
+import org.apache.hc.client5.http.ClientProtocolException;
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.auth.AuthSchemeProvider;
+import org.apache.hc.client5.http.auth.CredentialsProvider;
+import org.apache.hc.client5.http.classic.ExecChain;
+import org.apache.hc.client5.http.classic.ExecRuntime;
+import org.apache.hc.client5.http.config.Configurable;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.cookie.CookieSpecProvider;
+import org.apache.hc.client5.http.cookie.CookieStore;
+import org.apache.hc.client5.http.impl.ExecSupport;
+import org.apache.hc.client5.http.io.HttpClientConnectionManager;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.routing.HttpRoutePlanner;
+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.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.config.Lookup;
+import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
+import org.apache.hc.core5.http.protocol.BasicHttpContext;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.net.URIAuthority;
+import org.apache.hc.core5.util.Args;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Internal class.
+ *
+ * @since 4.3
+ */
+@Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
+class InternalHttpClient extends CloseableHttpClient implements Configurable {
+
+    private final Logger log = LogManager.getLogger(getClass());
+
+    private final HttpClientConnectionManager connManager;
+    private final HttpRequestExecutor requestExecutor;
+    private final ExecChainElement execChain;
+    private final HttpRoutePlanner routePlanner;
+    private final Lookup<CookieSpecProvider> cookieSpecRegistry;
+    private final Lookup<AuthSchemeProvider> authSchemeRegistry;
+    private final CookieStore cookieStore;
+    private final CredentialsProvider credentialsProvider;
+    private final RequestConfig defaultConfig;
+    private final List<Closeable> closeables;
+
+    public InternalHttpClient(
+            final HttpClientConnectionManager connManager,
+            final HttpRequestExecutor requestExecutor,
+            final ExecChainElement execChain,
+            final HttpRoutePlanner routePlanner,
+            final Lookup<CookieSpecProvider> cookieSpecRegistry,
+            final Lookup<AuthSchemeProvider> authSchemeRegistry,
+            final CookieStore cookieStore,
+            final CredentialsProvider credentialsProvider,
+            final RequestConfig defaultConfig,
+            final List<Closeable> closeables) {
+        super();
+        this.connManager = Args.notNull(connManager, "Connection manager");
+        this.requestExecutor = Args.notNull(requestExecutor, "Request executor");
+        this.execChain = Args.notNull(execChain, "Execution chain");
+        this.routePlanner = Args.notNull(routePlanner, "Route planner");
+        this.cookieSpecRegistry = cookieSpecRegistry;
+        this.authSchemeRegistry = authSchemeRegistry;
+        this.cookieStore = cookieStore;
+        this.credentialsProvider = credentialsProvider;
+        this.defaultConfig = defaultConfig;
+        this.closeables = closeables;
+    }
+
+    private HttpRoute determineRoute(
+            final HttpHost host,
+            final HttpRequest request,
+            final HttpContext context) throws HttpException {
+        final HttpHost target = host != null ? host : this.routePlanner.determineTargetHost(request, context);
+        return this.routePlanner.determineRoute(target, context);
+    }
+
+    private void setupContext(final HttpClientContext context) {
+        if (context.getAttribute(HttpClientContext.AUTHSCHEME_REGISTRY) == null) {
+            context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, this.authSchemeRegistry);
+        }
+        if (context.getAttribute(HttpClientContext.COOKIESPEC_REGISTRY) == null) {
+            context.setAttribute(HttpClientContext.COOKIESPEC_REGISTRY, this.cookieSpecRegistry);
+        }
+        if (context.getAttribute(HttpClientContext.COOKIE_STORE) == null) {
+            context.setAttribute(HttpClientContext.COOKIE_STORE, this.cookieStore);
+        }
+        if (context.getAttribute(HttpClientContext.CREDS_PROVIDER) == null) {
+            context.setAttribute(HttpClientContext.CREDS_PROVIDER, this.credentialsProvider);
+        }
+        if (context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) {
+            context.setAttribute(HttpClientContext.REQUEST_CONFIG, this.defaultConfig);
+        }
+    }
+
+    @Override
+    protected CloseableHttpResponse doExecute(
+            final HttpHost target,
+            final ClassicHttpRequest request,
+            final HttpContext context) throws IOException {
+        Args.notNull(request, "HTTP request");
+        try {
+            if (request.getScheme() == null && target != null) {
+                request.setScheme(target.getSchemeName());
+            }
+            if (request.getAuthority() == null && target != null) {
+                request.setAuthority(new URIAuthority(target));
+            }
+            final HttpClientContext localcontext = HttpClientContext.adapt(
+                    context != null ? context : new BasicHttpContext());
+            RequestConfig config = null;
+            if (request instanceof Configurable) {
+                config = ((Configurable) request).getConfig();
+            }
+            if (config != null) {
+                localcontext.setRequestConfig(config);
+            }
+            setupContext(localcontext);
+            final HttpRoute route = determineRoute(target, request, localcontext);
+            final ExecRuntime execRuntime = new ExecRuntimeImpl(log, connManager, requestExecutor,
+                    request instanceof CancellableAware ? (CancellableAware) request : null);
+            final ExecChain.Scope scope = new ExecChain.Scope(route, request, execRuntime, localcontext);
+            final ClassicHttpResponse response = this.execChain.execute(ExecSupport.copy(request), scope);
+            return CloseableHttpResponse.adapt(response);
+        } catch (final HttpException httpException) {
+            throw new ClientProtocolException(httpException.getMessage(), httpException);
+        }
+    }
+
+    @Override
+    public RequestConfig getConfig() {
+        return this.defaultConfig;
+    }
+
+    @Override
+    public void close() {
+        if (this.closeables != null) {
+            for (final Closeable closeable: this.closeables) {
+                try {
+                    closeable.close();
+                } catch (final IOException ex) {
+                    this.log.error(ex.getMessage(), ex);
+                }
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/MainClientExec.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/MainClientExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/MainClientExec.java
new file mode 100644
index 0000000..661139d
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/MainClientExec.java
@@ -0,0 +1,154 @@
+/*
+ * ====================================================================
+ * 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.io.InterruptedIOException;
+
+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.classic.ExecChain;
+import org.apache.hc.client5.http.classic.ExecChainHandler;
+import org.apache.hc.client5.http.classic.ExecRuntime;
+import org.apache.hc.client5.http.impl.ConnectionShutdownException;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+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.message.RequestLine;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * The last request executor in the HTTP request execution chain
+ * that is responsible for execution of request / response
+ * exchanges with the opposite endpoint.
+ *
+ * @since 4.3
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
+final class MainClientExec implements ExecChainHandler {
+
+    private final Logger log = LogManager.getLogger(getClass());
+
+    private final ConnectionReuseStrategy reuseStrategy;
+    private final ConnectionKeepAliveStrategy keepAliveStrategy;
+    private final UserTokenHandler userTokenHandler;
+
+    /**
+     * @since 4.4
+     */
+    public MainClientExec(
+            final ConnectionReuseStrategy reuseStrategy,
+            final ConnectionKeepAliveStrategy keepAliveStrategy,
+            final UserTokenHandler userTokenHandler) {
+        Args.notNull(reuseStrategy, "Connection reuse strategy");
+        Args.notNull(keepAliveStrategy, "Connection keep alive strategy");
+        Args.notNull(userTokenHandler, "User token handler");
+        this.reuseStrategy      = reuseStrategy;
+        this.keepAliveStrategy  = keepAliveStrategy;
+        this.userTokenHandler   = userTokenHandler;
+    }
+
+    @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;
+
+        try {
+            if (this.log.isDebugEnabled()) {
+                this.log.debug("Executing request " + new RequestLine(request));
+            }
+            RequestEntityProxy.enhance(request);
+
+            final ClassicHttpResponse response = execRuntime.execute(request, context);
+
+            Object userToken = context.getUserToken();
+            if (userToken == null) {
+                userToken = userTokenHandler.getUserToken(route, context);
+                context.setAttribute(HttpClientContext.USER_TOKEN, userToken);
+            }
+            if (userToken != null) {
+                execRuntime.setConnectionState(userToken);
+            }
+
+            // The connection is in or can be brought to a re-usable state.
+            if (reuseStrategy.keepAlive(request, response, context)) {
+                // Set the idle duration of this connection
+                final TimeValue duration = keepAliveStrategy.getKeepAliveDuration(response, context);
+                if (this.log.isDebugEnabled()) {
+                    final String s;
+                    if (duration != null) {
+                        s = "for " + duration;
+                    } else {
+                        s = "indefinitely";
+                    }
+                    this.log.debug("Connection can be kept alive " + s);
+                }
+                execRuntime.setConnectionValidFor(duration);
+                execRuntime.markConnectionReusable();
+            } else {
+                execRuntime.markConnectionNonReusable();
+            }
+            // check for entity, release connection if possible
+            final HttpEntity entity = response.getEntity();
+            if (entity == null || !entity.isStreaming()) {
+                // connection not needed and (assumed to be) in re-usable state
+                execRuntime.releaseConnection();
+                return new CloseableHttpResponse(response, null);
+            } else {
+                ResponseEntityProxy.enchance(response, execRuntime);
+                return new CloseableHttpResponse(response, execRuntime);
+            }
+        } catch (final ConnectionShutdownException ex) {
+            final InterruptedIOException ioex = new InterruptedIOException(
+                    "Connection has been shut down");
+            ioex.initCause(ex);
+            execRuntime.discardConnection();
+            throw ioex;
+        } catch (final HttpException | RuntimeException | IOException ex) {
+            execRuntime.discardConnection();
+            throw ex;
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/MinimalHttpClient.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/MinimalHttpClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/MinimalHttpClient.java
new file mode 100644
index 0000000..e335abd
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/MinimalHttpClient.java
@@ -0,0 +1,176 @@
+/*
+ * ====================================================================
+ * 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.io.InterruptedIOException;
+
+import org.apache.hc.client5.http.CancellableAware;
+import org.apache.hc.client5.http.ClientProtocolException;
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.classic.ExecRuntime;
+import org.apache.hc.client5.http.config.Configurable;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.ConnectionShutdownException;
+import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
+import org.apache.hc.client5.http.io.HttpClientConnectionManager;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.protocol.RequestClientConnControl;
+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.HttpHost;
+import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
+import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
+import org.apache.hc.core5.http.protocol.BasicHttpContext;
+import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.http.protocol.HttpProcessor;
+import org.apache.hc.core5.http.protocol.RequestContent;
+import org.apache.hc.core5.http.protocol.RequestTargetHost;
+import org.apache.hc.core5.http.protocol.RequestUserAgent;
+import org.apache.hc.core5.net.URIAuthority;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.VersionInfo;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Internal class.
+ *
+ * @since 4.3
+ */
+@Contract(threading = ThreadingBehavior.SAFE)
+public class MinimalHttpClient extends CloseableHttpClient {
+
+    private final Logger log = LogManager.getLogger(getClass());
+
+    private final HttpClientConnectionManager connManager;
+    private final ConnectionReuseStrategy reuseStrategy;
+    private final HttpRequestExecutor requestExecutor;
+    private final HttpProcessor httpProcessor;
+
+    MinimalHttpClient(final HttpClientConnectionManager connManager) {
+        super();
+        this.connManager = Args.notNull(connManager, "HTTP connection manager");
+        this.reuseStrategy = DefaultConnectionReuseStrategy.INSTANCE;
+        this.requestExecutor = new HttpRequestExecutor(this.reuseStrategy);
+        this.httpProcessor = new DefaultHttpProcessor(
+                new RequestContent(),
+                new RequestTargetHost(),
+                new RequestClientConnControl(),
+                new RequestUserAgent(VersionInfo.getSoftwareInfo(
+                        "Apache-HttpClient", "org.apache.hc.client5", getClass())));
+    }
+
+    @Override
+    protected CloseableHttpResponse doExecute(
+            final HttpHost target,
+            final ClassicHttpRequest request,
+            final HttpContext context) throws IOException {
+        Args.notNull(target, "Target host");
+        Args.notNull(request, "HTTP request");
+        if (request.getScheme() == null) {
+            request.setScheme(target.getSchemeName());
+        }
+        if (request.getAuthority() == null) {
+            request.setAuthority(new URIAuthority(target));
+        }
+        final HttpClientContext clientContext = HttpClientContext.adapt(
+                context != null ? context : new BasicHttpContext());
+        RequestConfig config = null;
+        if (request instanceof Configurable) {
+            config = ((Configurable) request).getConfig();
+        }
+        if (config != null) {
+            clientContext.setRequestConfig(config);
+        }
+
+        final HttpRoute route = new HttpRoute(target.getPort() > 0 ? target : new HttpHost(
+                target.getHostName(),
+                DefaultSchemePortResolver.INSTANCE.resolve(target),
+                target.getSchemeName()));
+
+        final ExecRuntime execRuntime = new ExecRuntimeImpl(log, connManager, requestExecutor,
+                request instanceof CancellableAware ? (CancellableAware) request : null);
+        try {
+            if (!execRuntime.isConnectionAcquired()) {
+                execRuntime.acquireConnection(route, null, clientContext);
+            }
+            if (!execRuntime.isConnected()) {
+                execRuntime.connect(clientContext);
+            }
+
+            context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
+            context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
+
+            httpProcessor.process(request, request.getEntity(), context);
+            final ClassicHttpResponse response = execRuntime.execute(request, clientContext);
+            httpProcessor.process(response, response.getEntity(), context);
+
+            if (reuseStrategy.keepAlive(request, response, context)) {
+                execRuntime.markConnectionReusable();
+            } else {
+                execRuntime.markConnectionNonReusable();
+            }
+
+            // check for entity, release connection if possible
+            final HttpEntity entity = response.getEntity();
+            if (entity == null || !entity.isStreaming()) {
+                // connection not needed and (assumed to be) in re-usable state
+                execRuntime.releaseConnection();
+                return new CloseableHttpResponse(response, null);
+            } else {
+                ResponseEntityProxy.enchance(response, execRuntime);
+                return new CloseableHttpResponse(response, execRuntime);
+            }
+        } catch (final ConnectionShutdownException ex) {
+            final InterruptedIOException ioex = new InterruptedIOException("Connection has been shut down");
+            ioex.initCause(ex);
+            execRuntime.discardConnection();
+            throw ioex;
+        } catch (final RuntimeException | IOException ex) {
+            execRuntime.discardConnection();
+            throw ex;
+        } catch (final HttpException httpException) {
+            execRuntime.discardConnection();
+            throw new ClientProtocolException(httpException);
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        this.connManager.close();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/NullBackoffStrategy.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/NullBackoffStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/NullBackoffStrategy.java
new file mode 100644
index 0000000..36440aa
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/NullBackoffStrategy.java
@@ -0,0 +1,49 @@
+/*
+ * ====================================================================
+ * 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 org.apache.hc.client5.http.classic.ConnectionBackoffStrategy;
+import org.apache.hc.core5.http.HttpResponse;
+
+/**
+ * This is a {@link ConnectionBackoffStrategy} that never backs off,
+ * for compatibility with existing behavior.
+ *
+ * @since 4.2
+ */
+public class NullBackoffStrategy implements ConnectionBackoffStrategy {
+
+    @Override
+    public boolean shouldBackoff(final Throwable t) {
+        return false;
+    }
+
+    @Override
+    public boolean shouldBackoff(final HttpResponse resp) {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java
new file mode 100644
index 0000000..39ffcef
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProtocolExec.java
@@ -0,0 +1,249 @@
+/*
+ * ====================================================================
+ * 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.net.URI;
+import java.net.URISyntaxException;
+import java.util.Iterator;
+
+import org.apache.hc.client5.http.AuthenticationStrategy;
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.NonRepeatableRequestException;
+import org.apache.hc.client5.http.StandardMethods;
+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.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.AuthSupport;
+import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+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.ClassicHttpResponse;
+import org.apache.hc.core5.http.Header;
+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.HttpResponse;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.http.protocol.HttpProcessor;
+import org.apache.hc.core5.net.URIAuthority;
+import org.apache.hc.core5.util.Args;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Request executor in the request execution chain that is responsible
+ * for implementation of HTTP specification requirements.
+ * <p>
+ * Further responsibilities such as communication with the opposite
+ * endpoint is delegated to the next executor in the request execution
+ * chain.
+ * </p>
+ *
+ * @since 4.3
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+final class ProtocolExec implements ExecChainHandler {
+
+    private final Logger log = LogManager.getLogger(getClass());
+
+    private final HttpProcessor httpProcessor;
+    private final AuthenticationStrategy targetAuthStrategy;
+    private final AuthenticationStrategy proxyAuthStrategy;
+    private final HttpAuthenticator authenticator;
+
+    public ProtocolExec(
+            final HttpProcessor httpProcessor,
+            final AuthenticationStrategy targetAuthStrategy,
+            final AuthenticationStrategy proxyAuthStrategy) {
+        this.httpProcessor = Args.notNull(httpProcessor, "HTTP protocol processor");
+        this.targetAuthStrategy = Args.notNull(targetAuthStrategy, "Target authentication strategy");
+        this.proxyAuthStrategy = Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
+        this.authenticator = new HttpAuthenticator();
+    }
+
+    @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;
+
+        try {
+            final HttpHost target = route.getTargetHost();
+            final HttpHost proxy = route.getProxyHost();
+            if (proxy != null && !route.isTunnelled()) {
+                try {
+                    URI uri = request.getUri();
+                    if (!uri.isAbsolute()) {
+                        uri = URIUtils.rewriteURI(uri, target, true);
+                    } else {
+                        uri = URIUtils.rewriteURI(uri);
+                    }
+                    request.setPath(uri.toASCIIString());
+                } catch (final URISyntaxException ex) {
+                    throw new ProtocolException("Invalid request URI: " + request.getRequestUri(), ex);
+                }
+            }
+
+            final URIAuthority authority = request.getAuthority();
+            if (authority != null) {
+                final CredentialsProvider credsProvider = context.getCredentialsProvider();
+                if (credsProvider instanceof CredentialsStore) {
+                    AuthSupport.extractFromAuthority(authority, (CredentialsStore) credsProvider);
+                }
+            }
+
+            final AuthExchange targetAuthExchange = context.getAuthExchange(target);
+            final AuthExchange proxyAuthExchange = proxy != null ? context.getAuthExchange(proxy) : new AuthExchange();
+
+            for (int execCount = 1;; execCount++) {
+
+                if (execCount > 1) {
+                    final HttpEntity entity = request.getEntity();
+                    if (entity != null && !entity.isRepeatable()) {
+                        throw new NonRepeatableRequestException("Cannot retry request " +
+                                "with a non-repeatable request entity.");
+                    }
+                }
+
+                // Run request protocol interceptors
+                context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
+                context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
+
+                httpProcessor.process(request, request.getEntity(), context);
+
+                if (!request.containsHeader(HttpHeaders.AUTHORIZATION)) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Target auth state: " + targetAuthExchange.getState());
+                    }
+                    authenticator.addAuthResponse(target, ChallengeType.TARGET, request, targetAuthExchange, context);
+                }
+                if (!request.containsHeader(HttpHeaders.PROXY_AUTHORIZATION) && !route.isTunnelled()) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Proxy auth state: " + proxyAuthExchange.getState());
+                    }
+                    authenticator.addAuthResponse(proxy, ChallengeType.PROXY, request, proxyAuthExchange, context);
+                }
+
+                final ClassicHttpResponse response = chain.proceed(request, scope);
+
+                context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
+                httpProcessor.process(response, response.getEntity(), context);
+
+                if (request.getMethod().equalsIgnoreCase(StandardMethods.TRACE.name())) {
+                    // Do not perform authentication for TRACE request
+                    return response;
+                }
+
+                if (needAuthentication(targetAuthExchange, proxyAuthExchange, route, request, response, context)) {
+                    // Make sure the response body is fully consumed, if present
+                    final HttpEntity entity = response.getEntity();
+                    if (execRuntime.isConnectionReusable()) {
+                        EntityUtils.consume(entity);
+                    } else {
+                        execRuntime.disconnect();
+                        if (proxyAuthExchange.getState() == AuthExchange.State.SUCCESS
+                                && proxyAuthExchange.getAuthScheme() != null
+                                && proxyAuthExchange.getAuthScheme().isConnectionBased()) {
+                            log.debug("Resetting proxy auth state");
+                            proxyAuthExchange.reset();
+                        }
+                        if (targetAuthExchange.getState() == AuthExchange.State.SUCCESS
+                                && targetAuthExchange.getAuthScheme() != null
+                                && targetAuthExchange.getAuthScheme().isConnectionBased()) {
+                            log.debug("Resetting target auth state");
+                            targetAuthExchange.reset();
+                        }
+                    }
+                    // Reset request headers
+                    final ClassicHttpRequest original = scope.originalRequest;
+                    request.setHeaders();
+                    for (final Iterator<Header> it = original.headerIterator(); it.hasNext(); ) {
+                        request.addHeader(it.next());
+                    }
+                } else {
+                    return response;
+                }
+            }
+        } catch (final RuntimeException | HttpException | IOException ex) {
+            execRuntime.discardConnection();
+            throw ex;
+        }
+    }
+
+    private boolean needAuthentication(
+            final AuthExchange targetAuthExchange,
+            final AuthExchange proxyAuthExchange,
+            final HttpRoute route,
+            final ClassicHttpRequest request,
+            final HttpResponse response,
+            final HttpClientContext context) {
+        final RequestConfig config = context.getRequestConfig();
+        if (config.isAuthenticationEnabled()) {
+            final HttpHost target = AuthSupport.resolveAuthTarget(request, route);
+            final boolean targetAuthRequested = authenticator.isChallenged(
+                    target, ChallengeType.TARGET, response, targetAuthExchange, context);
+
+            HttpHost proxy = route.getProxyHost();
+            // if proxy is not set use target host instead
+            if (proxy == null) {
+                proxy = route.getTargetHost();
+            }
+            final boolean proxyAuthRequested = authenticator.isChallenged(
+                    proxy, ChallengeType.PROXY, response, proxyAuthExchange, context);
+
+            if (targetAuthRequested) {
+                return authenticator.prepareAuthResponse(target, ChallengeType.TARGET, response,
+                        targetAuthStrategy, targetAuthExchange, context);
+            }
+            if (proxyAuthRequested) {
+                return authenticator.prepareAuthResponse(proxy, ChallengeType.PROXY, response,
+                        proxyAuthStrategy, proxyAuthExchange, context);
+            }
+        }
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProxyClient.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProxyClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProxyClient.java
new file mode 100644
index 0000000..d044a64
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ProxyClient.java
@@ -0,0 +1,219 @@
+/*
+ * ====================================================================
+ * 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.net.Socket;
+
+import org.apache.hc.client5.http.AuthenticationStrategy;
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.RouteInfo.LayerType;
+import org.apache.hc.client5.http.RouteInfo.TunnelType;
+import org.apache.hc.client5.http.SystemDefaultDnsResolver;
+import org.apache.hc.client5.http.auth.AuthExchange;
+import org.apache.hc.client5.http.auth.AuthSchemeProvider;
+import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.ChallengeType;
+import org.apache.hc.client5.http.auth.Credentials;
+import org.apache.hc.client5.http.config.AuthSchemes;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
+import org.apache.hc.client5.http.impl.TunnelRefusedException;
+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.DigestSchemeFactory;
+import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
+import org.apache.hc.client5.http.impl.auth.KerberosSchemeFactory;
+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.io.ManagedHttpClientConnectionFactory;
+import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.protocol.RequestClientConnControl;
+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.config.CharCodingConfig;
+import org.apache.hc.core5.http.config.H1Config;
+import org.apache.hc.core5.http.config.Lookup;
+import org.apache.hc.core5.http.config.RegistryBuilder;
+import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
+import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
+import org.apache.hc.core5.http.io.HttpConnectionFactory;
+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.BasicHttpContext;
+import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.http.protocol.HttpProcessor;
+import org.apache.hc.core5.http.protocol.RequestTargetHost;
+import org.apache.hc.core5.http.protocol.RequestUserAgent;
+import org.apache.hc.core5.util.Args;
+
+/**
+ * ProxyClient can be used to establish a tunnel via an HTTP proxy.
+ */
+public class ProxyClient {
+
+    private final HttpConnectionFactory<ManagedHttpClientConnection> connFactory;
+    private final RequestConfig requestConfig;
+    private final HttpProcessor httpProcessor;
+    private final HttpRequestExecutor requestExec;
+    private final AuthenticationStrategy proxyAuthStrategy;
+    private final HttpAuthenticator authenticator;
+    private final AuthExchange proxyAuthExchange;
+    private final Lookup<AuthSchemeProvider> authSchemeRegistry;
+    private final ConnectionReuseStrategy reuseStrategy;
+
+    /**
+     * @since 5.0
+     */
+    public ProxyClient(
+            final HttpConnectionFactory<ManagedHttpClientConnection> connFactory,
+            final H1Config h1Config,
+            final CharCodingConfig charCodingConfig,
+            final RequestConfig requestConfig) {
+        super();
+        this.connFactory = connFactory != null ? connFactory : new ManagedHttpClientConnectionFactory(h1Config, charCodingConfig, null, null);
+        this.requestConfig = requestConfig != null ? requestConfig : RequestConfig.DEFAULT;
+        this.httpProcessor = new DefaultHttpProcessor(
+                new RequestTargetHost(), new RequestClientConnControl(), new RequestUserAgent());
+        this.requestExec = new HttpRequestExecutor();
+        this.proxyAuthStrategy = new DefaultAuthenticationStrategy();
+        this.authenticator = new HttpAuthenticator();
+        this.proxyAuthExchange = new AuthExchange();
+        this.authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create()
+                .register(AuthSchemes.BASIC, new BasicSchemeFactory())
+                .register(AuthSchemes.DIGEST, new DigestSchemeFactory())
+                .register(AuthSchemes.NTLM, new NTLMSchemeFactory())
+                .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(SystemDefaultDnsResolver.INSTANCE, true, true))
+                .register(AuthSchemes.KERBEROS, new KerberosSchemeFactory(SystemDefaultDnsResolver.INSTANCE, true, true))
+                .build();
+        this.reuseStrategy = new DefaultConnectionReuseStrategy();
+    }
+
+    /**
+     * @since 4.3
+     */
+    public ProxyClient(final RequestConfig requestConfig) {
+        this(null, null, null, requestConfig);
+    }
+
+    public ProxyClient() {
+        this(null, null, null, null);
+    }
+
+    public Socket tunnel(
+            final HttpHost proxy,
+            final HttpHost target,
+            final Credentials credentials) throws IOException, HttpException {
+        Args.notNull(proxy, "Proxy host");
+        Args.notNull(target, "Target host");
+        Args.notNull(credentials, "Credentials");
+        HttpHost host = target;
+        if (host.getPort() <= 0) {
+            host = new HttpHost(host.getHostName(), 80, host.getSchemeName());
+        }
+        final HttpRoute route = new HttpRoute(
+                host,
+                this.requestConfig.getLocalAddress(),
+                proxy, false, TunnelType.TUNNELLED, LayerType.PLAIN);
+
+        final ManagedHttpClientConnection conn = this.connFactory.createConnection(null);
+        final HttpContext context = new BasicHttpContext();
+        ClassicHttpResponse response;
+
+        final ClassicHttpRequest connect = new BasicClassicHttpRequest("CONNECT", host.toHostString());
+
+        final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
+        credsProvider.setCredentials(new AuthScope(proxy), credentials);
+
+        // Populate the execution context
+        context.setAttribute(HttpCoreContext.HTTP_REQUEST, connect);
+        context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
+        context.setAttribute(HttpClientContext.CREDS_PROVIDER, credsProvider);
+        context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, this.authSchemeRegistry);
+        context.setAttribute(HttpClientContext.REQUEST_CONFIG, this.requestConfig);
+
+        this.requestExec.preProcess(connect, this.httpProcessor, context);
+
+        for (;;) {
+            if (!conn.isOpen()) {
+                final Socket socket = new Socket(proxy.getHostName(), proxy.getPort());
+                conn.bind(socket);
+            }
+
+            this.authenticator.addAuthResponse(proxy, ChallengeType.PROXY, connect, this.proxyAuthExchange, context);
+
+            response = this.requestExec.execute(connect, conn, context);
+
+            final int status = response.getCode();
+            if (status < 200) {
+                throw new HttpException("Unexpected response to CONNECT request: " + response);
+            }
+            if (this.authenticator.isChallenged(proxy, ChallengeType.PROXY, response, this.proxyAuthExchange, context)) {
+                if (this.authenticator.prepareAuthResponse(proxy, ChallengeType.PROXY, response,
+                        this.proxyAuthStrategy, this.proxyAuthExchange, context)) {
+                    // Retry request
+                    if (this.reuseStrategy.keepAlive(connect, response, context)) {
+                        // Consume response content
+                        final HttpEntity entity = response.getEntity();
+                        EntityUtils.consume(entity);
+                    } else {
+                        conn.close();
+                    }
+                    // discard previous auth header
+                    connect.removeHeaders(HttpHeaders.PROXY_AUTHORIZATION);
+                } else {
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+
+        final int status = response.getCode();
+
+        if (status > 299) {
+
+            // Buffer response content
+            final HttpEntity entity = response.getEntity();
+            final String responseMessage = entity != null ? EntityUtils.toString(entity) : null;
+            conn.close();
+            throw new TunnelRefusedException("CONNECT refused by proxy: " + new StatusLine(response), responseMessage);
+        }
+        return conn.getSocket();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RedirectExec.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RedirectExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RedirectExec.java
new file mode 100644
index 0000000..556c9df
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RedirectExec.java
@@ -0,0 +1,197 @@
+/*
+ * ====================================================================
+ * 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.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.auth.AuthExchange;
+import org.apache.hc.client5.http.auth.AuthScheme;
+import org.apache.hc.client5.http.classic.ExecChain;
+import org.apache.hc.client5.http.classic.ExecChainHandler;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.classic.methods.RequestBuilder;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.protocol.RedirectStrategy;
+import org.apache.hc.client5.http.routing.HttpRoutePlanner;
+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.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.util.Args;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Request executor in the request execution chain that is responsible
+ * for handling of request redirects.
+ * <p>
+ * Further responsibilities such as communication with the opposite
+ * endpoint is delegated to the next executor in the request execution
+ * chain.
+ * </p>
+ *
+ * @since 4.3
+ */
+@Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
+final class RedirectExec implements ExecChainHandler {
+
+    private final Logger log = LogManager.getLogger(getClass());
+
+    private final RedirectStrategy redirectStrategy;
+    private final HttpRoutePlanner routePlanner;
+
+    public RedirectExec(
+            final HttpRoutePlanner routePlanner,
+            final RedirectStrategy redirectStrategy) {
+        super();
+        Args.notNull(routePlanner, "HTTP route planner");
+        Args.notNull(redirectStrategy, "HTTP redirect strategy");
+        this.routePlanner = routePlanner;
+        this.redirectStrategy = redirectStrategy;
+    }
+
+    @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 HttpClientContext context = scope.clientContext;
+
+        final List<URI> redirectLocations = context.getRedirectLocations();
+        if (redirectLocations != null) {
+            redirectLocations.clear();
+        }
+
+        final RequestConfig config = context.getRequestConfig();
+        final int maxRedirects = config.getMaxRedirects() > 0 ? config.getMaxRedirects() : 50;
+        ClassicHttpRequest currentRequest = request;
+        ExecChain.Scope currentScope = scope;
+        for (int redirectCount = 0;;) {
+            final ClassicHttpResponse response = chain.proceed(currentRequest, currentScope);
+            try {
+                if (config.isRedirectsEnabled() && this.redirectStrategy.isRedirected(request, response, context)) {
+
+                    if (redirectCount >= maxRedirects) {
+                        throw new RedirectException("Maximum redirects ("+ maxRedirects + ") exceeded");
+                    }
+                    redirectCount++;
+
+                    final URI redirectUri = this.redirectStrategy.getLocationURI(currentRequest, response, context);
+                    final ClassicHttpRequest originalRequest = scope.originalRequest;
+                    ClassicHttpRequest redirect = null;
+                    final int statusCode = response.getCode();
+                    switch (statusCode) {
+                        case HttpStatus.SC_MOVED_PERMANENTLY:
+                        case HttpStatus.SC_MOVED_TEMPORARILY:
+                        case HttpStatus.SC_SEE_OTHER:
+                            if (!StandardMethods.isSafe(request.getMethod())) {
+                                final HttpGet httpGet = new HttpGet(redirectUri);
+                                httpGet.setHeaders(originalRequest.getAllHeaders());
+                                redirect = httpGet;
+                            } else {
+                                redirect = null;
+                            }
+                    }
+                    if (redirect == null) {
+                        redirect = RequestBuilder.copy(originalRequest).setUri(redirectUri).build();
+                    }
+
+                    final HttpHost newTarget = URIUtils.extractHost(redirectUri);
+                    if (newTarget == null) {
+                        throw new ProtocolException("Redirect URI does not specify a valid host name: " +
+                                redirectUri);
+                    }
+
+                    HttpRoute currentRoute = currentScope.route;
+                    final boolean crossSiteRedirect = !currentRoute.getTargetHost().equals(newTarget);
+                    if (crossSiteRedirect) {
+
+                        final AuthExchange targetAuthExchange = context.getAuthExchange(currentRoute.getTargetHost());
+                        this.log.debug("Resetting target auth state");
+                        targetAuthExchange.reset();
+                        if (currentRoute.getProxyHost() != null) {
+                            final AuthExchange proxyAuthExchange = context.getAuthExchange(currentRoute.getProxyHost());
+                            final AuthScheme authScheme = proxyAuthExchange.getAuthScheme();
+                            if (authScheme != null && authScheme.isConnectionBased()) {
+                                this.log.debug("Resetting proxy auth state");
+                                proxyAuthExchange.reset();
+                            }
+                        }
+                        currentRoute = this.routePlanner.determineRoute(newTarget, context);
+                        currentScope = new ExecChain.Scope(
+                                currentRoute,
+                                currentScope.originalRequest,
+                                currentScope.execRuntime,
+                                currentScope.clientContext);
+                    }
+
+                    if (this.log.isDebugEnabled()) {
+                        this.log.debug("Redirecting to '" + redirectUri + "' via " + currentRoute);
+                    }
+                    currentRequest = redirect;
+                    RequestEntityProxy.enhance(currentRequest);
+
+                    EntityUtils.consume(response.getEntity());
+                    response.close();
+                } else {
+                    return response;
+                }
+            } catch (final RuntimeException | IOException ex) {
+                response.close();
+                throw ex;
+            } catch (final HttpException ex) {
+                // Protocol exception related to a direct.
+                // The underlying connection may still be salvaged.
+                try {
+                    EntityUtils.consume(response.getEntity());
+                } catch (final IOException ioex) {
+                    this.log.debug("I/O error while releasing connection", ioex);
+                } finally {
+                    response.close();
+                }
+                throw ex;
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RequestAbortedException.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RequestAbortedException.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RequestAbortedException.java
new file mode 100644
index 0000000..d270354
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RequestAbortedException.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.classic;
+
+import java.io.InterruptedIOException;
+
+/**
+ * Signals that the request has been aborted.
+ *
+ * @since 4.3
+ */
+public class RequestAbortedException extends InterruptedIOException {
+
+    private static final long serialVersionUID = 4973849966012490112L;
+
+    public RequestAbortedException(final String message) {
+        super(message);
+    }
+
+    public RequestAbortedException(final String message, final Throwable cause) {
+        super(message);
+        if (cause != null) {
+            initCause(cause);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RequestEntityProxy.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RequestEntityProxy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RequestEntityProxy.java
new file mode 100644
index 0000000..d2c4537
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RequestEntityProxy.java
@@ -0,0 +1,142 @@
+/*
+ * ====================================================================
+ * 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.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.hc.core5.function.Supplier;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpEntity;
+
+/**
+ * A Proxy class for {@link org.apache.hc.core5.http.HttpEntity} enclosed in a request message.
+ *
+ * @since 4.3
+ */
+class RequestEntityProxy implements HttpEntity  {
+
+    static void enhance(final ClassicHttpRequest request) {
+        final HttpEntity entity = request.getEntity();
+        if (entity != null && !entity.isRepeatable() && !isEnhanced(entity)) {
+            request.setEntity(new RequestEntityProxy(entity));
+        }
+    }
+
+    static boolean isEnhanced(final HttpEntity entity) {
+        return entity instanceof RequestEntityProxy;
+    }
+
+    private final HttpEntity original;
+    private boolean consumed = false;
+
+    RequestEntityProxy(final HttpEntity original) {
+        super();
+        this.original = original;
+    }
+
+    public HttpEntity getOriginal() {
+        return original;
+    }
+
+    public boolean isConsumed() {
+        return consumed;
+    }
+
+    @Override
+    public boolean isRepeatable() {
+        if (!consumed) {
+            return true;
+        } else {
+            return original.isRepeatable();
+        }
+    }
+
+    @Override
+    public boolean isChunked() {
+        return original.isChunked();
+    }
+
+    @Override
+    public long getContentLength() {
+        return original.getContentLength();
+    }
+
+    @Override
+    public String getContentType() {
+        return original.getContentType();
+    }
+
+    @Override
+    public String getContentEncoding() {
+        return original.getContentEncoding();
+    }
+
+    @Override
+    public InputStream getContent() throws IOException, IllegalStateException {
+        return original.getContent();
+    }
+
+    @Override
+    public void writeTo(final OutputStream outstream) throws IOException {
+        consumed = true;
+        original.writeTo(outstream);
+    }
+
+    @Override
+    public boolean isStreaming() {
+        return original.isStreaming();
+    }
+
+    @Override
+    public Supplier<List<? extends Header>> getTrailers() {
+        return original.getTrailers();
+    }
+
+    @Override
+    public Set<String> getTrailerNames() {
+        return original.getTrailerNames();
+    }
+
+    @Override
+    public void close() throws IOException {
+        original.close();
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("RequestEntityProxy{");
+        sb.append(original);
+        sb.append('}');
+        return sb.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RequestFailedException.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RequestFailedException.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RequestFailedException.java
new file mode 100644
index 0000000..85462e5
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RequestFailedException.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.classic;
+
+import java.io.InterruptedIOException;
+
+/**
+ * Signals that the request has been aborted or failed due to an expected condition.
+ *
+ * @since 5.0
+ */
+public class RequestFailedException extends InterruptedIOException {
+
+    private static final long serialVersionUID = 4973849966012490112L;
+
+    public RequestFailedException(final String message) {
+        super(message);
+    }
+
+    public RequestFailedException(final String message, final Throwable cause) {
+        super(message);
+        if (cause != null) {
+            initCause(cause);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ResponseEntityProxy.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ResponseEntityProxy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ResponseEntityProxy.java
new file mode 100644
index 0000000..c521bdb
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ResponseEntityProxy.java
@@ -0,0 +1,158 @@
+/*
+ * ====================================================================
+ * 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.io.InputStream;
+import java.io.OutputStream;
+import java.net.SocketException;
+
+import org.apache.hc.client5.http.classic.ExecRuntime;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.io.EofSensorInputStream;
+import org.apache.hc.core5.http.io.EofSensorWatcher;
+import org.apache.hc.core5.http.io.entity.HttpEntityWrapper;
+
+/**
+ * A wrapper class for {@link HttpEntity} enclosed in a response message.
+ *
+ * @since 4.3
+ */
+class ResponseEntityProxy extends HttpEntityWrapper implements EofSensorWatcher {
+
+    private final ExecRuntime execRuntime;
+
+    public static void enchance(final ClassicHttpResponse response, final ExecRuntime execRuntime) {
+        final HttpEntity entity = response.getEntity();
+        if (entity != null && entity.isStreaming() && execRuntime != null) {
+            response.setEntity(new ResponseEntityProxy(entity, execRuntime));
+        }
+    }
+
+    ResponseEntityProxy(final HttpEntity entity, final ExecRuntime execRuntime) {
+        super(entity);
+        this.execRuntime = execRuntime;
+    }
+
+    private void cleanup() throws IOException {
+        if (this.execRuntime != null) {
+            if (this.execRuntime.isConnected()) {
+                this.execRuntime.disconnect();
+            }
+            this.execRuntime.discardConnection();
+        }
+    }
+
+    private void discardConnection() {
+        if (this.execRuntime != null) {
+            this.execRuntime.discardConnection();
+        }
+    }
+
+    public void releaseConnection() {
+        if (this.execRuntime != null) {
+            this.execRuntime.releaseConnection();
+        }
+    }
+
+    @Override
+    public boolean isRepeatable() {
+        return false;
+    }
+
+    @Override
+    public InputStream getContent() throws IOException {
+        return new EofSensorInputStream(super.getContent(), this);
+    }
+
+    @Override
+    public void writeTo(final OutputStream outstream) throws IOException {
+        try {
+            if (outstream != null) {
+                super.writeTo(outstream);
+            }
+            releaseConnection();
+        } catch (IOException | RuntimeException ex) {
+            discardConnection();
+            throw ex;
+        } finally {
+            cleanup();
+        }
+    }
+
+    @Override
+    public boolean eofDetected(final InputStream wrapped) throws IOException {
+        try {
+            // there may be some cleanup required, such as
+            // reading trailers after the response body:
+            if (wrapped != null) {
+                wrapped.close();
+            }
+            releaseConnection();
+        } catch (IOException | RuntimeException ex) {
+            discardConnection();
+            throw ex;
+        } finally {
+            cleanup();
+        }
+        return false;
+    }
+
+    @Override
+    public boolean streamClosed(final InputStream wrapped) throws IOException {
+        try {
+            final boolean open = execRuntime != null && execRuntime.isConnectionAcquired();
+            // this assumes that closing the stream will
+            // consume the remainder of the response body:
+            try {
+                if (wrapped != null) {
+                    wrapped.close();
+                }
+                releaseConnection();
+            } catch (final SocketException ex) {
+                if (open) {
+                    throw ex;
+                }
+            }
+        } catch (IOException | RuntimeException ex) {
+            discardConnection();
+            throw ex;
+        } finally {
+            cleanup();
+        }
+        return false;
+    }
+
+    @Override
+    public boolean streamAbort(final InputStream wrapped) throws IOException {
+        cleanup();
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6d17126c/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RetryExec.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RetryExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RetryExec.java
new file mode 100644
index 0000000..cd80c54
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/RetryExec.java
@@ -0,0 +1,127 @@
+/*
+ * ====================================================================
+ * 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.HttpRequestRetryHandler;
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.NonRepeatableRequestException;
+import org.apache.hc.client5.http.classic.ExecChain;
+import org.apache.hc.client5.http.classic.ExecChainHandler;
+import org.apache.hc.client5.http.impl.ExecSupport;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+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.HttpEntity;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.NoHttpResponseException;
+import org.apache.hc.core5.util.Args;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Request executor in the request execution chain that is responsible
+ * for making a decision whether a request failed due to an I/O error
+ * should be re-executed.
+ * <p>
+ * Further responsibilities such as communication with the opposite
+ * endpoint is delegated to the next executor in the request execution
+ * chain.
+ * </p>
+ *
+ * @since 4.3
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
+final class RetryExec implements ExecChainHandler {
+
+    private final Logger log = LogManager.getLogger(getClass());
+
+    private final HttpRequestRetryHandler retryHandler;
+
+    public RetryExec(
+            final HttpRequestRetryHandler retryHandler) {
+        Args.notNull(retryHandler, "HTTP request retry handler");
+        this.retryHandler = retryHandler;
+    }
+
+    @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;
+        ClassicHttpRequest currentRequest = request;
+        for (int execCount = 1;; execCount++) {
+            try {
+                return chain.proceed(currentRequest, scope);
+            } catch (final IOException ex) {
+                if (scope.execRuntime.isExecutionAborted()) {
+                    throw new RequestFailedException("Request aborted");
+                }
+                if (retryHandler.retryRequest(request, ex, execCount, context)) {
+                    if (this.log.isInfoEnabled()) {
+                        this.log.info("I/O exception ("+ ex.getClass().getName() +
+                                ") caught when processing request to "
+                                + route +
+                                ": "
+                                + ex.getMessage());
+                    }
+                    if (this.log.isDebugEnabled()) {
+                        this.log.debug(ex.getMessage(), ex);
+                    }
+                    final HttpEntity entity = request.getEntity();
+                    if (entity != null && !entity.isRepeatable()) {
+                        this.log.debug("Cannot retry non-repeatable request");
+                        throw new NonRepeatableRequestException("Cannot retry request " +
+                                "with a non-repeatable request entity", ex);
+                    }
+                    currentRequest = ExecSupport.copy(scope.originalRequest);
+                    if (this.log.isInfoEnabled()) {
+                        this.log.info("Retrying request to " + route);
+                    }
+                } else {
+                    if (ex instanceof NoHttpResponseException) {
+                        final NoHttpResponseException updatedex = new NoHttpResponseException(
+                                route.getTargetHost().toHostString() + " failed to respond");
+                        updatedex.setStackTrace(ex.getStackTrace());
+                        throw updatedex;
+                    } else {
+                        throw ex;
+                    }
+                }
+            }
+        }
+    }
+
+}