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;
+ }
+ }
+ }
+ }
+ }
+
+}