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/05/01 13:06:48 UTC
svn commit: r1793325 [1/2] - in
/httpcomponents/httpclient/trunk/httpclient5/src:
main/java/org/apache/hc/client5/http/impl/sync/
test/java/org/apache/hc/client5/http/impl/sync/
Author: olegk
Date: Mon May 1 13:06:48 2017
New Revision: 1793325
URL: http://svn.apache.org/viewvc?rev=1793325&view=rev
Log:
Refactored connection routing and protocol execution code in the classic exec chain
Added:
httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ConnectExec.java (with props)
httpcomponents/httpclient/trunk/httpclient5/src/test/java/org/apache/hc/client5/http/impl/sync/TestConnectExec.java (with props)
Removed:
httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/MinimalClientExec.java
httpcomponents/httpclient/trunk/httpclient5/src/test/java/org/apache/hc/client5/http/impl/sync/TestMinimalClientExec.java
Modified:
httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ChainElements.java
httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/HttpClientBuilder.java
httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/MainClientExec.java
httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/MinimalHttpClient.java
httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ProtocolExec.java
httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/RequestEntityProxy.java
httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/RetryExec.java
httpcomponents/httpclient/trunk/httpclient5/src/test/java/org/apache/hc/client5/http/impl/sync/TestMainClientExec.java
httpcomponents/httpclient/trunk/httpclient5/src/test/java/org/apache/hc/client5/http/impl/sync/TestProtocolExec.java
Modified: httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ChainElements.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ChainElements.java?rev=1793325&r1=1793324&r2=1793325&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ChainElements.java (original)
+++ httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ChainElements.java Mon May 1 13:06:48 2017
@@ -29,6 +29,6 @@ package org.apache.hc.client5.http.impl.
public enum ChainElements {
- REDIRECT, BACK_OFF, RETRY_SERVICE_UNAVAILABLE, RETRY_IO_ERROR, PROTOCOL, MAIN_TRANSPORT
+ REDIRECT, BACK_OFF, RETRY_SERVICE_UNAVAILABLE, RETRY_IO_ERROR, PROTOCOL, CONNECT, MAIN_TRANSPORT
}
\ No newline at end of file
Added: httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ConnectExec.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ConnectExec.java?rev=1793325&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ConnectExec.java (added)
+++ httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ConnectExec.java Mon May 1 13:06:48 2017
@@ -0,0 +1,282 @@
+/*
+ * ====================================================================
+ * 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.sync;
+
+import java.io.IOException;
+
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.RouteTracker;
+import org.apache.hc.client5.http.auth.AuthExchange;
+import org.apache.hc.client5.http.auth.ChallengeType;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
+import org.apache.hc.client5.http.impl.routing.BasicRouteDirector;
+import org.apache.hc.client5.http.protocol.AuthenticationStrategy;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.routing.HttpRouteDirector;
+import org.apache.hc.client5.http.sync.ExecChain;
+import org.apache.hc.client5.http.sync.ExecChainHandler;
+import org.apache.hc.client5.http.sync.ExecRuntime;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ConnectionReuseStrategy;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.io.entity.BufferedHttpEntity;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
+import org.apache.hc.core5.http.message.StatusLine;
+import org.apache.hc.core5.http.protocol.HttpProcessor;
+import org.apache.hc.core5.util.Args;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Request executor in the HTTP request execution chain
+ * that is responsible for establishing connection to the target
+ * origin server as specified by the current route.
+ *
+ * @since 5.0
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
+public final class ConnectExec implements ExecChainHandler {
+
+ private final Logger log = LogManager.getLogger(getClass());
+
+ private final ConnectionReuseStrategy reuseStrategy;
+ private final HttpProcessor proxyHttpProcessor;
+ private final AuthenticationStrategy proxyAuthStrategy;
+ private final HttpAuthenticator authenticator;
+ private final HttpRouteDirector routeDirector;
+
+ public ConnectExec(
+ final ConnectionReuseStrategy reuseStrategy,
+ final HttpProcessor proxyHttpProcessor,
+ final AuthenticationStrategy proxyAuthStrategy) {
+ Args.notNull(reuseStrategy, "Connection reuse strategy");
+ Args.notNull(proxyHttpProcessor, "Proxy HTTP processor");
+ Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
+ this.reuseStrategy = reuseStrategy;
+ this.proxyHttpProcessor = proxyHttpProcessor;
+ this.proxyAuthStrategy = proxyAuthStrategy;
+ this.authenticator = new HttpAuthenticator();
+ this.routeDirector = new BasicRouteDirector();
+ }
+
+ @Override
+ public ClassicHttpResponse execute(
+ final ClassicHttpRequest request,
+ final ExecChain.Scope scope,
+ final ExecChain chain) throws IOException, HttpException {
+ Args.notNull(request, "HTTP request");
+ Args.notNull(scope, "Scope");
+
+ final HttpRoute route = scope.route;
+ final HttpClientContext context = scope.clientContext;
+ final ExecRuntime execRuntime = scope.execRuntime;
+
+ if (!execRuntime.isConnectionAcquired()) {
+ final Object userToken = context.getUserToken();
+ execRuntime.acquireConnection(route, userToken, context);
+ }
+ try {
+ if (!execRuntime.isConnected()) {
+ this.log.debug("Opening connection " + route);
+
+ final RouteTracker tracker = new RouteTracker(route);
+ int step;
+ do {
+ final HttpRoute fact = tracker.toRoute();
+ step = this.routeDirector.nextStep(route, fact);
+
+ switch (step) {
+
+ case HttpRouteDirector.CONNECT_TARGET:
+ execRuntime.connect(context);
+ tracker.connectTarget(route.isSecure());
+ break;
+ case HttpRouteDirector.CONNECT_PROXY:
+ execRuntime.connect(context);
+ final HttpHost proxy = route.getProxyHost();
+ tracker.connectProxy(proxy, false);
+ break;
+ case HttpRouteDirector.TUNNEL_TARGET: {
+ final boolean secure = createTunnelToTarget(route, request, execRuntime, context);
+ this.log.debug("Tunnel to target created.");
+ tracker.tunnelTarget(secure);
+ } break;
+
+ case HttpRouteDirector.TUNNEL_PROXY: {
+ // The most simple example for this case is a proxy chain
+ // of two proxies, where P1 must be tunnelled to P2.
+ // route: Source -> P1 -> P2 -> Target (3 hops)
+ // fact: Source -> P1 -> Target (2 hops)
+ final int hop = fact.getHopCount()-1; // the hop to establish
+ final boolean secure = createTunnelToProxy(route, hop, context);
+ this.log.debug("Tunnel to proxy created.");
+ tracker.tunnelProxy(route.getHopTarget(hop), secure);
+ } break;
+
+ case HttpRouteDirector.LAYER_PROTOCOL:
+ execRuntime.upgradeTls(context);
+ tracker.layerProtocol(route.isSecure());
+ break;
+
+ case HttpRouteDirector.UNREACHABLE:
+ throw new HttpException("Unable to establish route: " +
+ "planned = " + route + "; current = " + fact);
+ case HttpRouteDirector.COMPLETE:
+ break;
+ default:
+ throw new IllegalStateException("Unknown step indicator "
+ + step + " from RouteDirector.");
+ }
+
+ } while (step > HttpRouteDirector.COMPLETE);
+ }
+ return chain.proceed(request, scope);
+
+ } catch (final IOException | HttpException | RuntimeException ex) {
+ execRuntime.discardConnection();
+ throw ex;
+ }
+ }
+
+ /**
+ * Creates a tunnel to the target server.
+ * The connection must be established to the (last) proxy.
+ * A CONNECT request for tunnelling through the proxy will
+ * be created and sent, the response received and checked.
+ * This method does <i>not</i> processChallenge the connection with
+ * information about the tunnel, that is left to the caller.
+ */
+ private boolean createTunnelToTarget(
+ final HttpRoute route,
+ final HttpRequest request,
+ final ExecRuntime execRuntime,
+ final HttpClientContext context) throws HttpException, IOException {
+
+ final RequestConfig config = context.getRequestConfig();
+
+ final HttpHost target = route.getTargetHost();
+ final HttpHost proxy = route.getProxyHost();
+ final AuthExchange proxyAuthExchange = context.getAuthExchange(proxy);
+ ClassicHttpResponse response = null;
+
+ final String authority = target.toHostString();
+ final ClassicHttpRequest connect = new BasicClassicHttpRequest("CONNECT", target, authority);
+ connect.setVersion(HttpVersion.HTTP_1_1);
+
+ this.proxyHttpProcessor.process(connect, null, context);
+
+ while (response == null) {
+ if (!execRuntime.isConnected()) {
+ execRuntime.connect(context);
+ }
+
+ connect.removeHeaders(HttpHeaders.PROXY_AUTHORIZATION);
+ this.authenticator.addAuthResponse(proxy, ChallengeType.PROXY, connect, proxyAuthExchange, context);
+
+ response = execRuntime.execute(connect, context);
+
+ final int status = response.getCode();
+ if (status < HttpStatus.SC_SUCCESS) {
+ throw new HttpException("Unexpected response to CONNECT request: " + new StatusLine(response));
+ }
+
+ if (config.isAuthenticationEnabled()) {
+ if (this.authenticator.isChallenged(proxy, ChallengeType.PROXY, response,
+ proxyAuthExchange, context)) {
+ if (this.authenticator.prepareAuthResponse(proxy, ChallengeType.PROXY, response,
+ this.proxyAuthStrategy, proxyAuthExchange, context)) {
+ // Retry request
+ if (this.reuseStrategy.keepAlive(request, response, context)) {
+ this.log.debug("Connection kept alive");
+ // Consume response content
+ final HttpEntity entity = response.getEntity();
+ EntityUtils.consume(entity);
+ } else {
+ execRuntime.disconnect();
+ }
+ response = null;
+ }
+ }
+ }
+ }
+
+ final int status = response.getCode();
+ if (status >= HttpStatus.SC_REDIRECTION) {
+
+ // Buffer response content
+ final HttpEntity entity = response.getEntity();
+ if (entity != null) {
+ response.setEntity(new BufferedHttpEntity(entity));
+ }
+
+ execRuntime.disconnect();
+ throw new TunnelRefusedException("CONNECT refused by proxy: " +
+ new StatusLine(response), response);
+ }
+
+ // How to decide on security of the tunnelled connection?
+ // The socket factory knows only about the segment to the proxy.
+ // Even if that is secure, the hop to the target may be insecure.
+ // Leave it to derived classes, consider insecure by default here.
+ return false;
+ }
+
+ /**
+ * Creates a tunnel to an intermediate proxy.
+ * This method is <i>not</i> implemented in this class.
+ * It just throws an exception here.
+ */
+ private boolean createTunnelToProxy(
+ final HttpRoute route,
+ final int hop,
+ final HttpClientContext context) throws HttpException {
+
+ // Have a look at createTunnelToTarget and replicate the parts
+ // you need in a custom derived class. If your proxies don't require
+ // authentication, it is not too hard. But for the stock version of
+ // HttpClient, we cannot make such simplifying assumptions and would
+ // have to include proxy authentication code. The HttpComponents team
+ // is currently not in a position to support rarely used code of this
+ // complexity. Feel free to submit patches that refactor the code in
+ // createTunnelToTarget to facilitate re-use for proxy tunnelling.
+
+ throw new HttpException("Proxy chains are not supported.");
+ }
+
+}
Propchange: httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ConnectExec.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ConnectExec.java
------------------------------------------------------------------------------
svn:keywords = Date Revision
Propchange: httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ConnectExec.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/HttpClientBuilder.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/HttpClientBuilder.java?rev=1793325&r1=1793324&r2=1793325&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/HttpClientBuilder.java (original)
+++ httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/HttpClientBuilder.java Mon May 1 13:06:48 2017
@@ -99,6 +99,7 @@ import org.apache.hc.core5.http.impl.Def
import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.http.protocol.HttpProcessor;
import org.apache.hc.core5.http.protocol.HttpProcessorBuilder;
import org.apache.hc.core5.http.protocol.RequestContent;
import org.apache.hc.core5.http.protocol.RequestTargetHost;
@@ -725,6 +726,7 @@ public class HttpClientBuilder {
reuseStrategyCopy = DefaultConnectionReuseStrategy.INSTANCE;
}
}
+
ConnectionKeepAliveStrategy keepAliveStrategyCopy = this.keepAliveStrategy;
if (keepAliveStrategyCopy == null) {
keepAliveStrategyCopy = DefaultConnectionKeepAliveStrategy.INSTANCE;
@@ -759,14 +761,14 @@ public class HttpClientBuilder {
final NamedElementChain<ExecChainHandler> execChainDefinition = new NamedElementChain<>();
execChainDefinition.addLast(
- new MainClientExec(
+ new MainClientExec(reuseStrategyCopy, keepAliveStrategyCopy, userTokenHandlerCopy),
+ ChainElements.MAIN_TRANSPORT.name());
+ execChainDefinition.addFirst(
+ new ConnectExec(
reuseStrategyCopy,
- keepAliveStrategyCopy,
new DefaultHttpProcessor(new RequestTargetHost(), new RequestUserAgent(userAgentCopy)),
- targetAuthStrategyCopy,
- proxyAuthStrategyCopy,
- userTokenHandlerCopy),
- ChainElements.MAIN_TRANSPORT.name());
+ proxyAuthStrategyCopy),
+ ChainElements.CONNECT.name());
final HttpProcessorBuilder b = HttpProcessorBuilder.create();
if (requestInterceptors != null) {
@@ -813,8 +815,9 @@ public class HttpClientBuilder {
}
}
}
+ final HttpProcessor httpProcessor = b.build();
execChainDefinition.addFirst(
- new ProtocolExec(b.build()),
+ new ProtocolExec(httpProcessor, targetAuthStrategyCopy, proxyAuthStrategyCopy),
ChainElements.PROTOCOL.name());
// Add request retry executor, if not disabled
Modified: httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/MainClientExec.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/MainClientExec.java?rev=1793325&r1=1793324&r2=1793325&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/MainClientExec.java (original)
+++ httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/MainClientExec.java Mon May 1 13:06:48 2017
@@ -32,18 +32,9 @@ import java.io.InterruptedIOException;
import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
import org.apache.hc.client5.http.HttpRoute;
-import org.apache.hc.client5.http.RouteTracker;
-import org.apache.hc.client5.http.auth.AuthExchange;
-import org.apache.hc.client5.http.auth.ChallengeType;
-import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.ConnectionShutdownException;
-import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
-import org.apache.hc.client5.http.impl.routing.BasicRouteDirector;
-import org.apache.hc.client5.http.protocol.AuthenticationStrategy;
import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.client5.http.protocol.NonRepeatableRequestException;
import org.apache.hc.client5.http.protocol.UserTokenHandler;
-import org.apache.hc.client5.http.routing.HttpRouteDirector;
import org.apache.hc.client5.http.sync.ExecChain;
import org.apache.hc.client5.http.sync.ExecChainHandler;
import org.apache.hc.client5.http.sync.ExecRuntime;
@@ -54,21 +45,7 @@ import org.apache.hc.core5.http.ClassicH
import org.apache.hc.core5.http.ConnectionReuseStrategy;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpException;
-import org.apache.hc.core5.http.HttpHeaders;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.HttpResponse;
-import org.apache.hc.core5.http.HttpStatus;
-import org.apache.hc.core5.http.HttpVersion;
-import org.apache.hc.core5.http.io.entity.BufferedHttpEntity;
-import org.apache.hc.core5.http.io.entity.EntityUtils;
-import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.apache.hc.core5.http.message.RequestLine;
-import org.apache.hc.core5.http.message.StatusLine;
-import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
-import org.apache.hc.core5.http.protocol.HttpProcessor;
-import org.apache.hc.core5.http.protocol.RequestTargetHost;
-import org.apache.hc.core5.net.URIAuthority;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.TimeValue;
import org.apache.logging.log4j.LogManager;
@@ -78,9 +55,6 @@ 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.
- * This executor will automatically retry the request in case
- * of an authentication challenge by an intermediate proxy or
- * by the target server.
*
* @since 4.3
*/
@@ -91,12 +65,7 @@ final class MainClientExec implements Ex
private final ConnectionReuseStrategy reuseStrategy;
private final ConnectionKeepAliveStrategy keepAliveStrategy;
- private final HttpProcessor proxyHttpProcessor;
- private final AuthenticationStrategy targetAuthStrategy;
- private final AuthenticationStrategy proxyAuthStrategy;
- private final HttpAuthenticator authenticator;
private final UserTokenHandler userTokenHandler;
- private final HttpRouteDirector routeDirector;
/**
* @since 4.4
@@ -104,37 +73,15 @@ final class MainClientExec implements Ex
public MainClientExec(
final ConnectionReuseStrategy reuseStrategy,
final ConnectionKeepAliveStrategy keepAliveStrategy,
- final HttpProcessor proxyHttpProcessor,
- final AuthenticationStrategy targetAuthStrategy,
- final AuthenticationStrategy proxyAuthStrategy,
final UserTokenHandler userTokenHandler) {
Args.notNull(reuseStrategy, "Connection reuse strategy");
Args.notNull(keepAliveStrategy, "Connection keep alive strategy");
- Args.notNull(proxyHttpProcessor, "Proxy HTTP processor");
- Args.notNull(targetAuthStrategy, "Target authentication strategy");
- Args.notNull(proxyAuthStrategy, "Proxy authentication strategy");
Args.notNull(userTokenHandler, "User token handler");
- this.authenticator = new HttpAuthenticator();
- this.routeDirector = new BasicRouteDirector();
this.reuseStrategy = reuseStrategy;
this.keepAliveStrategy = keepAliveStrategy;
- this.proxyHttpProcessor = proxyHttpProcessor;
- this.targetAuthStrategy = targetAuthStrategy;
- this.proxyAuthStrategy = proxyAuthStrategy;
this.userTokenHandler = userTokenHandler;
}
- public MainClientExec(
- final ConnectionReuseStrategy reuseStrategy,
- final ConnectionKeepAliveStrategy keepAliveStrategy,
- final AuthenticationStrategy targetAuthStrategy,
- final AuthenticationStrategy proxyAuthStrategy,
- final UserTokenHandler userTokenHandler) {
- this(reuseStrategy, keepAliveStrategy,
- new DefaultHttpProcessor(new RequestTargetHost()),
- targetAuthStrategy, proxyAuthStrategy, userTokenHandler);
- }
-
@Override
public ClassicHttpResponse execute(
final ClassicHttpRequest request,
@@ -146,116 +93,15 @@ final class MainClientExec implements Ex
final HttpClientContext context = scope.clientContext;
final ExecRuntime execRuntime = scope.execRuntime;
- RequestEntityProxy.enhance(request);
-
- Object userToken = context.getUserToken();
- if (!execRuntime.isConnectionAcquired()) {
- execRuntime.acquireConnection(route, userToken, context);
- }
try {
- final AuthExchange targetAuthExchange = context.getAuthExchange(route.getTargetHost());
- final AuthExchange proxyAuthExchange = route.getProxyHost() != null ?
- context.getAuthExchange(route.getProxyHost()) : new AuthExchange();
-
- ClassicHttpResponse response;
- for (int execCount = 1;; execCount++) {
-
- if (execCount > 1 && !RequestEntityProxy.isRepeatable(request)) {
- throw new NonRepeatableRequestException("Cannot retry request " +
- "with a non-repeatable request entity.");
- }
-
- if (!execRuntime.isConnected()) {
- this.log.debug("Opening connection " + route);
- try {
- establishRoute(route, request, execRuntime, context);
- } catch (final TunnelRefusedException ex) {
- if (this.log.isDebugEnabled()) {
- this.log.debug(ex.getMessage());
- }
- response = ex.getResponse();
- break;
- }
- }
- if (this.log.isDebugEnabled()) {
- this.log.debug("Executing request " + new RequestLine(request));
- }
-
- if (!request.containsHeader(HttpHeaders.AUTHORIZATION)) {
- if (this.log.isDebugEnabled()) {
- this.log.debug("Target auth state: " + targetAuthExchange.getState());
- }
- this.authenticator.addAuthResponse(
- route.getTargetHost(), ChallengeType.TARGET, request, targetAuthExchange, context);
- }
- if (!request.containsHeader(HttpHeaders.PROXY_AUTHORIZATION) && !route.isTunnelled()) {
- if (this.log.isDebugEnabled()) {
- this.log.debug("Proxy auth state: " + proxyAuthExchange.getState());
- }
- this.authenticator.addAuthResponse(
- route.getProxyHost(), ChallengeType.PROXY, request, proxyAuthExchange, context);
- }
-
- response = execRuntime.execute(request, context);
-
- // 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();
- }
-
- if (request.getMethod().equalsIgnoreCase("TRACE")) {
- // Do not perform authentication for TRACE request
- break;
- }
-
- 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()) {
- this.log.debug("Resetting proxy auth state");
- proxyAuthExchange.reset();
- }
- if (targetAuthExchange.getState() == AuthExchange.State.SUCCESS
- && targetAuthExchange.getAuthScheme() != null
- && targetAuthExchange.getAuthScheme().isConnectionBased()) {
- this.log.debug("Resetting target auth state");
- targetAuthExchange.reset();
- }
- }
- // discard previous auth headers
- final HttpRequest original = scope.originalRequest;
- if (!original.containsHeader(HttpHeaders.AUTHORIZATION)) {
- request.removeHeaders(HttpHeaders.AUTHORIZATION);
- }
- if (!original.containsHeader(HttpHeaders.PROXY_AUTHORIZATION)) {
- request.removeHeaders(HttpHeaders.PROXY_AUTHORIZATION);
- }
- } else {
- break;
- }
+ 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);
@@ -264,6 +110,24 @@ final class MainClientExec implements Ex
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()) {
@@ -284,213 +148,7 @@ final class MainClientExec implements Ex
execRuntime.discardConnection();
throw ex;
}
- }
-
- /**
- * Establishes the target route.
- */
- void establishRoute(
- final HttpRoute route,
- final HttpRequest request,
- final ExecRuntime execRuntime,
- final HttpClientContext context) throws HttpException, IOException {
- final RouteTracker tracker = new RouteTracker(route);
- int step;
- do {
- final HttpRoute fact = tracker.toRoute();
- step = this.routeDirector.nextStep(route, fact);
-
- switch (step) {
-
- case HttpRouteDirector.CONNECT_TARGET:
- execRuntime.connect(context);
- tracker.connectTarget(route.isSecure());
- break;
- case HttpRouteDirector.CONNECT_PROXY:
- execRuntime.connect(context);
- final HttpHost proxy = route.getProxyHost();
- tracker.connectProxy(proxy, false);
- break;
- case HttpRouteDirector.TUNNEL_TARGET: {
- final boolean secure = createTunnelToTarget(route, request, execRuntime, context);
- this.log.debug("Tunnel to target created.");
- tracker.tunnelTarget(secure);
- } break;
-
- case HttpRouteDirector.TUNNEL_PROXY: {
- // The most simple example for this case is a proxy chain
- // of two proxies, where P1 must be tunnelled to P2.
- // route: Source -> P1 -> P2 -> Target (3 hops)
- // fact: Source -> P1 -> Target (2 hops)
- final int hop = fact.getHopCount()-1; // the hop to establish
- final boolean secure = createTunnelToProxy(route, hop, context);
- this.log.debug("Tunnel to proxy created.");
- tracker.tunnelProxy(route.getHopTarget(hop), secure);
- } break;
-
- case HttpRouteDirector.LAYER_PROTOCOL:
- execRuntime.upgradeTls(context);
- tracker.layerProtocol(route.isSecure());
- break;
-
- case HttpRouteDirector.UNREACHABLE:
- throw new HttpException("Unable to establish route: " +
- "planned = " + route + "; current = " + fact);
- case HttpRouteDirector.COMPLETE:
- break;
- default:
- throw new IllegalStateException("Unknown step indicator "
- + step + " from RouteDirector.");
- }
-
- } while (step > HttpRouteDirector.COMPLETE);
- }
-
- /**
- * Creates a tunnel to the target server.
- * The connection must be established to the (last) proxy.
- * A CONNECT request for tunnelling through the proxy will
- * be created and sent, the response received and checked.
- * This method does <i>not</i> processChallenge the connection with
- * information about the tunnel, that is left to the caller.
- */
- private boolean createTunnelToTarget(
- final HttpRoute route,
- final HttpRequest request,
- final ExecRuntime execRuntime,
- final HttpClientContext context) throws HttpException, IOException {
-
- final RequestConfig config = context.getRequestConfig();
-
- final HttpHost target = route.getTargetHost();
- final HttpHost proxy = route.getProxyHost();
- final AuthExchange proxyAuthExchange = context.getAuthExchange(proxy);
- ClassicHttpResponse response = null;
-
- final String authority = target.toHostString();
- final ClassicHttpRequest connect = new BasicClassicHttpRequest("CONNECT", target, authority);
- connect.setVersion(HttpVersion.HTTP_1_1);
-
- this.proxyHttpProcessor.process(connect, null, context);
-
- while (response == null) {
- if (!execRuntime.isConnected()) {
- execRuntime.connect(context);
- }
-
- connect.removeHeaders(HttpHeaders.PROXY_AUTHORIZATION);
- this.authenticator.addAuthResponse(proxy, ChallengeType.PROXY, connect, proxyAuthExchange, context);
-
- response = execRuntime.execute(connect, context);
-
- final int status = response.getCode();
- if (status < HttpStatus.SC_SUCCESS) {
- throw new HttpException("Unexpected response to CONNECT request: " + new StatusLine(response));
- }
-
- if (config.isAuthenticationEnabled()) {
- if (this.authenticator.isChallenged(proxy, ChallengeType.PROXY, response,
- proxyAuthExchange, context)) {
- if (this.authenticator.prepareAuthResponse(proxy, ChallengeType.PROXY, response,
- this.proxyAuthStrategy, proxyAuthExchange, context)) {
- // Retry request
- if (this.reuseStrategy.keepAlive(request, response, context)) {
- this.log.debug("Connection kept alive");
- // Consume response content
- final HttpEntity entity = response.getEntity();
- EntityUtils.consume(entity);
- } else {
- execRuntime.disconnect();
- }
- response = null;
- }
- }
- }
- }
-
- final int status = response.getCode();
- if (status >= HttpStatus.SC_REDIRECTION) {
-
- // Buffer response content
- final HttpEntity entity = response.getEntity();
- if (entity != null) {
- response.setEntity(new BufferedHttpEntity(entity));
- }
-
- execRuntime.disconnect();
- execRuntime.discardConnection();
- throw new TunnelRefusedException("CONNECT refused by proxy: " +
- new StatusLine(response), response);
- }
-
- // How to decide on security of the tunnelled connection?
- // The socket factory knows only about the segment to the proxy.
- // Even if that is secure, the hop to the target may be insecure.
- // Leave it to derived classes, consider insecure by default here.
- return false;
- }
-
- /**
- * Creates a tunnel to an intermediate proxy.
- * This method is <i>not</i> implemented in this class.
- * It just throws an exception here.
- */
- private boolean createTunnelToProxy(
- final HttpRoute route,
- final int hop,
- final HttpClientContext context) throws HttpException {
-
- // Have a look at createTunnelToTarget and replicate the parts
- // you need in a custom derived class. If your proxies don't require
- // authentication, it is not too hard. But for the stock version of
- // HttpClient, we cannot make such simplifying assumptions and would
- // have to include proxy authentication code. The HttpComponents team
- // is currently not in a position to support rarely used code of this
- // complexity. Feel free to submit patches that refactor the code in
- // createTunnelToTarget to facilitate re-use for proxy tunnelling.
- throw new HttpException("Proxy chains are not supported.");
- }
-
- 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 URIAuthority authority = request.getAuthority();
- final String scheme = request.getScheme();
- HttpHost target = authority != null ? new HttpHost(authority, scheme) : route.getTargetHost();;
- if (target.getPort() < 0) {
- target = new HttpHost(
- target.getHostName(),
- route.getTargetHost().getPort(),
- target.getSchemeName());
- }
- final boolean targetAuthRequested = this.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 = this.authenticator.isChallenged(
- proxy, ChallengeType.PROXY, response, proxyAuthExchange, context);
-
- if (targetAuthRequested) {
- return this.authenticator.prepareAuthResponse(target, ChallengeType.TARGET, response,
- this.targetAuthStrategy, targetAuthExchange, context);
- }
- if (proxyAuthRequested) {
- return this.authenticator.prepareAuthResponse(proxy, ChallengeType.PROXY, response,
- this.proxyAuthStrategy, proxyAuthExchange, context);
- }
- }
- return false;
}
}
Modified: httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/MinimalHttpClient.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/MinimalHttpClient.java?rev=1793325&r1=1793324&r2=1793325&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/MinimalHttpClient.java (original)
+++ httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/MinimalHttpClient.java Mon May 1 13:06:48 2017
@@ -28,30 +28,40 @@
package org.apache.hc.client5.http.impl.sync;
import java.io.IOException;
+import java.io.InterruptedIOException;
import org.apache.hc.client5.http.CancellableAware;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.config.Configurable;
import org.apache.hc.client5.http.config.RequestConfig;
-import org.apache.hc.client5.http.impl.DefaultConnectionKeepAliveStrategy;
-import org.apache.hc.client5.http.impl.ExecSupport;
+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.ClientProtocolException;
import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.client5.http.sync.ExecChain;
+import org.apache.hc.client5.http.protocol.RequestClientConnControl;
import org.apache.hc.client5.http.sync.ExecRuntime;
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;
@@ -66,16 +76,21 @@ public class MinimalHttpClient extends C
private final Logger log = LogManager.getLogger(getClass());
private final HttpClientConnectionManager connManager;
- private final MinimalClientExec execChain;
+ 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.execChain = new MinimalClientExec(
- DefaultConnectionReuseStrategy.INSTANCE,
- DefaultConnectionKeepAliveStrategy.INSTANCE);
- this.requestExecutor = new HttpRequestExecutor(DefaultConnectionReuseStrategy.INSTANCE);
+ 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
@@ -85,28 +100,70 @@ public class MinimalHttpClient extends C
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 (request.getScheme() == null) {
- request.setScheme(target.getSchemeName());
+ if (!execRuntime.isConnectionAcquired()) {
+ execRuntime.acquireConnection(route, null, clientContext);
}
- if (request.getAuthority() == null) {
- request.setAuthority(new URIAuthority(target));
+ if (!execRuntime.isConnected()) {
+ execRuntime.connect(clientContext);
}
- final HttpClientContext localcontext = HttpClientContext.adapt(
- context != null ? context : new BasicHttpContext());
- RequestConfig config = null;
- if (request instanceof Configurable) {
- config = ((Configurable) request).getConfig();
+
+ 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();
}
- if (config != null) {
- localcontext.setRequestConfig(config);
+
+ // 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);
}
- final ExecRuntime execRuntime = new ExecRuntimeImpl(log, connManager, requestExecutor,
- request instanceof CancellableAware ? (CancellableAware) request : null);
- final ExecChain.Scope scope = new ExecChain.Scope(new HttpRoute(target), request, execRuntime, localcontext);
- final ClassicHttpResponse response = this.execChain.execute(ExecSupport.copy(request), scope, null);
- return CloseableHttpResponse.adapt(response);
+ } 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);
}
}
Modified: httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ProtocolExec.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ProtocolExec.java?rev=1793325&r1=1793324&r2=1793325&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ProtocolExec.java (original)
+++ httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ProtocolExec.java Mon May 1 13:06:48 2017
@@ -30,33 +30,46 @@ package org.apache.hc.client5.http.impl.
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.util.Iterator;
import org.apache.hc.client5.http.HttpRoute;
+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.auth.util.CredentialSupport;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
+import org.apache.hc.client5.http.protocol.AuthenticationStrategy;
import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.protocol.NonRepeatableRequestException;
import org.apache.hc.client5.http.sync.ExecChain;
import org.apache.hc.client5.http.sync.ExecChainHandler;
+import org.apache.hc.client5.http.sync.ExecRuntime;
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.
- * Internally this executor relies on a {@link HttpProcessor} to populate
- * requisite HTTP request headers, process HTTP response headers and processChallenge
- * session state in {@link HttpClientContext}.
* <p>
* Further responsibilities such as communication with the opposite
* endpoint is delegated to the next executor in the request execution
@@ -68,11 +81,21 @@ import org.apache.hc.core5.util.Args;
@Contract(threading = ThreadingBehavior.IMMUTABLE)
final class ProtocolExec implements ExecChainHandler {
- private final HttpProcessor httpProcessor;
+ private final Logger log = LogManager.getLogger(getClass());
- public ProtocolExec(final HttpProcessor httpProcessor) {
- Args.notNull(httpProcessor, "HTTP protocol processor");
- this.httpProcessor = httpProcessor;
+ 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
@@ -85,46 +108,150 @@ final class ProtocolExec implements Exec
final HttpRoute route = scope.route;
final HttpClientContext context = scope.clientContext;
+ final ExecRuntime execRuntime = scope.execRuntime;
- if (route.getProxyHost() != null && !route.isTunnelled()) {
- try {
- URI uri = request.getUri();
- if (!uri.isAbsolute()) {
- final HttpHost target = route.getTargetHost();
- uri = URIUtils.rewriteURI(uri, target, true);
- } else {
- uri = URIUtils.rewriteURI(uri);
+ 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);
}
- request.setPath(uri.toASCIIString());
- } catch (final URISyntaxException ex) {
- throw new ProtocolException("Invalid URI: " + request.getRequestUri(), ex);
}
- }
- final URIAuthority authority = request.getAuthority();
- if (authority != null) {
- final CredentialsProvider credsProvider = context.getCredentialsProvider();
- if (credsProvider instanceof CredentialsStore) {
- CredentialSupport.extractFromAuthority(authority, (CredentialsStore) credsProvider);
+ final URIAuthority authority = request.getAuthority();
+ if (authority != null) {
+ final CredentialsProvider credsProvider = context.getCredentialsProvider();
+ if (credsProvider instanceof CredentialsStore) {
+ CredentialSupport.extractFromAuthority(authority, (CredentialsStore) credsProvider);
+ }
}
- }
- // Run request protocol interceptors
- context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
- context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
+ final AuthExchange targetAuthExchange = context.getAuthExchange(target);
+ final AuthExchange proxyAuthExchange = proxy != null ? context.getAuthExchange(proxy) : new AuthExchange();
- this.httpProcessor.process(request, request.getEntity(), context);
+ for (int execCount = 1;; execCount++) {
- final ClassicHttpResponse response = chain.proceed(request, scope);
- try {
- // Run response protocol interceptors
- context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
- this.httpProcessor.process(response, response.getEntity(), context);
- return response;
+ 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) {
- response.close();
+ 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 URIAuthority authority = request.getAuthority();
+ final String scheme = request.getScheme();
+ HttpHost target = authority != null ? new HttpHost(authority, scheme) : route.getTargetHost();;
+ if (target.getPort() < 0) {
+ target = new HttpHost(
+ target.getHostName(),
+ route.getTargetHost().getPort(),
+ target.getSchemeName());
+ }
+ 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;
+ }
+
}
Modified: httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/RequestEntityProxy.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/RequestEntityProxy.java?rev=1793325&r1=1793324&r2=1793325&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/RequestEntityProxy.java (original)
+++ httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/RequestEntityProxy.java Mon May 1 13:06:48 2017
@@ -55,20 +55,6 @@ class RequestEntityProxy implements Http
return entity instanceof RequestEntityProxy;
}
- static boolean isRepeatable(final ClassicHttpRequest request) {
- final HttpEntity entity = request.getEntity();
- if (entity != null) {
- if (isEnhanced(entity)) {
- final RequestEntityProxy proxy = (RequestEntityProxy) entity;
- if (!proxy.isConsumed()) {
- return true;
- }
- }
- return entity.isRepeatable();
- }
- return true;
- }
-
private final HttpEntity original;
private boolean consumed = false;
@@ -87,7 +73,11 @@ class RequestEntityProxy implements Http
@Override
public boolean isRepeatable() {
- return original.isRepeatable();
+ if (!consumed) {
+ return true;
+ } else {
+ return original.isRepeatable();
+ }
}
@Override
Modified: httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/RetryExec.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/RetryExec.java?rev=1793325&r1=1793324&r2=1793325&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/RetryExec.java (original)
+++ httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/RetryExec.java Mon May 1 13:06:48 2017
@@ -40,6 +40,7 @@ import org.apache.hc.core5.annotation.Co
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;
@@ -99,7 +100,8 @@ final class RetryExec implements ExecCha
if (this.log.isDebugEnabled()) {
this.log.debug(ex.getMessage(), ex);
}
- if (!RequestEntityProxy.isRepeatable(request)) {
+ 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);
Added: httpcomponents/httpclient/trunk/httpclient5/src/test/java/org/apache/hc/client5/http/impl/sync/TestConnectExec.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5/src/test/java/org/apache/hc/client5/http/impl/sync/TestConnectExec.java?rev=1793325&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5/src/test/java/org/apache/hc/client5/http/impl/sync/TestConnectExec.java (added)
+++ httpcomponents/httpclient/trunk/httpclient5/src/test/java/org/apache/hc/client5/http/impl/sync/TestConnectExec.java Mon May 1 13:06:48 2017
@@ -0,0 +1,359 @@
+/*
+ * ====================================================================
+ * 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.sync;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.RouteInfo;
+import org.apache.hc.client5.http.auth.AuthChallenge;
+import org.apache.hc.client5.http.auth.AuthScheme;
+import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.ChallengeType;
+import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
+import org.apache.hc.client5.http.entity.EntityBuilder;
+import org.apache.hc.client5.http.impl.auth.BasicScheme;
+import org.apache.hc.client5.http.protocol.AuthenticationStrategy;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.sync.ExecChain;
+import org.apache.hc.client5.http.sync.ExecRuntime;
+import org.apache.hc.client5.http.sync.methods.HttpGet;
+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.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
+import org.apache.hc.core5.http.protocol.HttpProcessor;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+@SuppressWarnings({"boxing","static-access"}) // test code
+public class TestConnectExec {
+
+ @Mock
+ private ConnectionReuseStrategy reuseStrategy;
+ @Mock
+ private HttpProcessor proxyHttpProcessor;
+ @Mock
+ private AuthenticationStrategy proxyAuthStrategy;
+ @Mock
+ private ExecRuntime execRuntime;
+ @Mock
+ private ExecChain execChain;
+
+ private ConnectExec exec;
+ private HttpHost target;
+ private HttpHost proxy;
+
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ exec = new ConnectExec(reuseStrategy, proxyHttpProcessor, proxyAuthStrategy);
+ target = new HttpHost("foo", 80);
+ proxy = new HttpHost("bar", 8888);
+ }
+
+ @Test
+ public void testExecAcquireConnection() throws Exception {
+ final HttpRoute route = new HttpRoute(target);
+ final HttpClientContext context = new HttpClientContext();
+ final ClassicHttpRequest request = new HttpGet("http://bar/test");
+ final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
+ response.setEntity(EntityBuilder.create()
+ .setStream(new ByteArrayInputStream(new byte[]{}))
+ .build());
+ context.setUserToken("Blah");
+
+ Mockito.when(execRuntime.isConnectionAcquired()).thenReturn(false);
+ Mockito.when(execRuntime.execute(
+ Mockito.same(request),
+ Mockito.<HttpClientContext>any())).thenReturn(response);
+ Mockito.when(reuseStrategy.keepAlive(
+ Mockito.same(request),
+ Mockito.same(response),
+ Mockito.<HttpClientContext>any())).thenReturn(false);
+ final ExecChain.Scope scope = new ExecChain.Scope(route, request, execRuntime, context);
+ exec.execute(request, scope, execChain);
+ Mockito.verify(execRuntime).acquireConnection(route, "Blah", context);
+ Mockito.verify(execRuntime).connect(context);
+ }
+
+ @Test
+ public void testEstablishDirectRoute() throws Exception {
+ final HttpRoute route = new HttpRoute(target);
+ final HttpClientContext context = new HttpClientContext();
+ final ClassicHttpRequest request = new HttpGet("http://bar/test");
+
+ final ConnectionState connectionState = new ConnectionState();
+ Mockito.doAnswer(connectionState.connectAnswer()).when(execRuntime).connect(Mockito.<HttpClientContext>any());
+ Mockito.when(execRuntime.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
+
+ final ExecChain.Scope scope = new ExecChain.Scope(route, request, execRuntime, context);
+ exec.execute(request, scope, execChain);
+
+ Mockito.verify(execRuntime).connect(context);
+ Mockito.verify(execRuntime, Mockito.never()).execute(Mockito.<ClassicHttpRequest>any(), Mockito.<HttpClientContext>any());
+ }
+
+ @Test
+ public void testEstablishRouteDirectProxy() throws Exception {
+ final HttpRoute route = new HttpRoute(target, null, proxy, false);
+ final HttpClientContext context = new HttpClientContext();
+ final ClassicHttpRequest request = new HttpGet("http://bar/test");
+
+ final ConnectionState connectionState = new ConnectionState();
+ Mockito.doAnswer(connectionState.connectAnswer()).when(execRuntime).connect(Mockito.<HttpClientContext>any());
+ Mockito.when(execRuntime.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
+
+ final ExecChain.Scope scope = new ExecChain.Scope(route, request, execRuntime, context);
+ exec.execute(request, scope, execChain);
+
+ Mockito.verify(execRuntime).connect(context);
+ Mockito.verify(execRuntime, Mockito.never()).execute(Mockito.<ClassicHttpRequest>any(), Mockito.<HttpClientContext>any());
+ }
+
+ @Test
+ public void testEstablishRouteViaProxyTunnel() throws Exception {
+ final HttpRoute route = new HttpRoute(target, null, proxy, true);
+ final HttpClientContext context = new HttpClientContext();
+ final ClassicHttpRequest request = new HttpGet("http://bar/test");
+ final ClassicHttpResponse response = new BasicClassicHttpResponse(200, "OK");
+
+ final ConnectionState connectionState = new ConnectionState();
+ Mockito.doAnswer(connectionState.connectAnswer()).when(execRuntime).connect(Mockito.<HttpClientContext>any());
+ Mockito.when(execRuntime.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
+ Mockito.when(execRuntime.execute(
+ Mockito.<ClassicHttpRequest>any(),
+ Mockito.<HttpClientContext>any())).thenReturn(response);
+
+ final ExecChain.Scope scope = new ExecChain.Scope(route, request, execRuntime, context);
+ exec.execute(request, scope, execChain);
+
+ Mockito.verify(execRuntime).connect(context);
+ final ArgumentCaptor<ClassicHttpRequest> reqCaptor = ArgumentCaptor.forClass(ClassicHttpRequest.class);
+ Mockito.verify(execRuntime).execute(
+ reqCaptor.capture(),
+ Mockito.same(context));
+ final HttpRequest connect = reqCaptor.getValue();
+ Assert.assertNotNull(connect);
+ Assert.assertEquals("CONNECT", connect.getMethod());
+ Assert.assertEquals(HttpVersion.HTTP_1_1, connect.getVersion());
+ Assert.assertEquals("foo:80", connect.getRequestUri());
+ }
+
+ @Test(expected = HttpException.class)
+ public void testEstablishRouteViaProxyTunnelUnexpectedResponse() throws Exception {
+ final HttpRoute route = new HttpRoute(target, null, proxy, true);
+ final HttpClientContext context = new HttpClientContext();
+ final ClassicHttpRequest request = new HttpGet("http://bar/test");
+ final ClassicHttpResponse response = new BasicClassicHttpResponse(101, "Lost");
+
+ final ConnectionState connectionState = new ConnectionState();
+ Mockito.doAnswer(connectionState.connectAnswer()).when(execRuntime).connect(Mockito.<HttpClientContext>any());
+ Mockito.when(execRuntime.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
+ Mockito.when(execRuntime.execute(
+ Mockito.<ClassicHttpRequest>any(),
+ Mockito.<HttpClientContext>any())).thenReturn(response);
+
+ final ExecChain.Scope scope = new ExecChain.Scope(route, request, execRuntime, context);
+ exec.execute(request, scope, execChain);
+ }
+
+ @Test(expected = HttpException.class)
+ public void testEstablishRouteViaProxyTunnelFailure() throws Exception {
+ final HttpRoute route = new HttpRoute(target, null, proxy, true);
+ final HttpClientContext context = new HttpClientContext();
+ final ClassicHttpRequest request = new HttpGet("http://bar/test");
+ final ClassicHttpResponse response = new BasicClassicHttpResponse(500, "Boom");
+ response.setEntity(new StringEntity("Ka-boom"));
+
+ final ConnectionState connectionState = new ConnectionState();
+ Mockito.doAnswer(connectionState.connectAnswer()).when(execRuntime).connect(Mockito.<HttpClientContext>any());
+ Mockito.when(execRuntime.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
+ Mockito.when(execRuntime.execute(
+ Mockito.<ClassicHttpRequest>any(),
+ Mockito.<HttpClientContext>any())).thenReturn(response);
+
+ final ExecChain.Scope scope = new ExecChain.Scope(route, request, execRuntime, context);
+ try {
+ exec.execute(request, scope, execChain);
+ } catch (final TunnelRefusedException ex) {
+ final ClassicHttpResponse r = ex.getResponse();
+ Assert.assertEquals("Ka-boom", EntityUtils.toString(r.getEntity()));
+ Mockito.verify(execRuntime).disconnect();
+ Mockito.verify(execRuntime).discardConnection();
+ throw ex;
+ }
+ }
+
+ @Test
+ public void testEstablishRouteViaProxyTunnelRetryOnAuthChallengePersistentConnection() throws Exception {
+ final HttpRoute route = new HttpRoute(target, null, proxy, true);
+ final HttpClientContext context = new HttpClientContext();
+ final ClassicHttpRequest request = new HttpGet("http://bar/test");
+ final ClassicHttpResponse response1 = new BasicClassicHttpResponse(407, "Huh?");
+ response1.setHeader(HttpHeaders.PROXY_AUTHENTICATE, "Basic realm=test");
+ final InputStream instream1 = Mockito.spy(new ByteArrayInputStream(new byte[] {1, 2, 3}));
+ response1.setEntity(EntityBuilder.create()
+ .setStream(instream1)
+ .build());
+ final ClassicHttpResponse response2 = new BasicClassicHttpResponse(200, "OK");
+
+ final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+ credentialsProvider.setCredentials(new AuthScope(proxy), new UsernamePasswordCredentials("user", "pass".toCharArray()));
+ context.setCredentialsProvider(credentialsProvider);
+
+ final ConnectionState connectionState = new ConnectionState();
+ Mockito.doAnswer(connectionState.connectAnswer()).when(execRuntime).connect(Mockito.<HttpClientContext>any());
+ Mockito.when(execRuntime.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
+ Mockito.when(reuseStrategy.keepAlive(
+ Mockito.same(request),
+ Mockito.<HttpResponse>any(),
+ Mockito.<HttpClientContext>any())).thenReturn(Boolean.TRUE);
+ Mockito.when(execRuntime.execute(
+ Mockito.<ClassicHttpRequest>any(),
+ Mockito.<HttpClientContext>any())).thenReturn(response1, response2);
+
+ Mockito.when(proxyAuthStrategy.select(
+ Mockito.eq(ChallengeType.PROXY),
+ Mockito.<Map<String, AuthChallenge>>any(),
+ Mockito.<HttpClientContext>any())).thenReturn(Collections.<AuthScheme>singletonList(new BasicScheme()));
+
+ final ExecChain.Scope scope = new ExecChain.Scope(route, request, execRuntime, context);
+ exec.execute(request, scope, execChain);
+
+ Mockito.verify(execRuntime).connect(context);
+ Mockito.verify(instream1).close();
+ }
+
+ @Test
+ public void testEstablishRouteViaProxyTunnelRetryOnAuthChallengeNonPersistentConnection() throws Exception {
+ final HttpRoute route = new HttpRoute(target, null, proxy, true);
+ final HttpClientContext context = new HttpClientContext();
+ final ClassicHttpRequest request = new HttpGet("http://bar/test");
+ final ClassicHttpResponse response1 = new BasicClassicHttpResponse(407, "Huh?");
+ response1.setHeader(HttpHeaders.PROXY_AUTHENTICATE, "Basic realm=test");
+ final InputStream instream1 = Mockito.spy(new ByteArrayInputStream(new byte[] {1, 2, 3}));
+ response1.setEntity(EntityBuilder.create()
+ .setStream(instream1)
+ .build());
+ final ClassicHttpResponse response2 = new BasicClassicHttpResponse(200, "OK");
+
+ final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+ credentialsProvider.setCredentials(new AuthScope(proxy), new UsernamePasswordCredentials("user", "pass".toCharArray()));
+ context.setCredentialsProvider(credentialsProvider);
+
+ final ConnectionState connectionState = new ConnectionState();
+ Mockito.doAnswer(connectionState.connectAnswer()).when(execRuntime).connect(Mockito.<HttpClientContext>any());
+ Mockito.when(execRuntime.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
+ Mockito.when(reuseStrategy.keepAlive(
+ Mockito.same(request),
+ Mockito.<HttpResponse>any(),
+ Mockito.<HttpClientContext>any())).thenReturn(Boolean.FALSE);
+ Mockito.when(execRuntime.execute(
+ Mockito.<ClassicHttpRequest>any(),
+ Mockito.<HttpClientContext>any())).thenReturn(response1, response2);
+
+ Mockito.when(proxyAuthStrategy.select(
+ Mockito.eq(ChallengeType.PROXY),
+ Mockito.<Map<String, AuthChallenge>>any(),
+ Mockito.<HttpClientContext>any())).thenReturn(Collections.<AuthScheme>singletonList(new BasicScheme()));
+
+ final ExecChain.Scope scope = new ExecChain.Scope(route, request, execRuntime, context);
+ exec.execute(request, scope, execChain);
+
+ Mockito.verify(execRuntime).connect(context);
+ Mockito.verify(instream1, Mockito.never()).close();
+ Mockito.verify(execRuntime).disconnect();
+ }
+
+ @Test(expected = HttpException.class)
+ public void testEstablishRouteViaProxyTunnelMultipleHops() throws Exception {
+ final HttpHost proxy1 = new HttpHost("this", 8888);
+ final HttpHost proxy2 = new HttpHost("that", 8888);
+ final HttpRoute route = new HttpRoute(target, null, new HttpHost[] {proxy1, proxy2},
+ true, RouteInfo.TunnelType.TUNNELLED, RouteInfo.LayerType.LAYERED);
+ final HttpClientContext context = new HttpClientContext();
+ final ClassicHttpRequest request = new HttpGet("http://bar/test");
+
+ final ConnectionState connectionState = new ConnectionState();
+ Mockito.doAnswer(connectionState.connectAnswer()).when(execRuntime).connect(Mockito.<HttpClientContext>any());
+ Mockito.when(execRuntime.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
+
+ final ExecChain.Scope scope = new ExecChain.Scope(route, request, execRuntime, context);
+ exec.execute(request, scope, execChain);
+ }
+
+ static class ConnectionState {
+
+ private boolean connected;
+
+ public Answer connectAnswer() {
+
+ return new Answer() {
+
+ @Override
+ public Object answer(final InvocationOnMock invocationOnMock) throws Throwable {
+ connected = true;
+ return null;
+ }
+
+ };
+ }
+
+ public Answer<Boolean> isConnectedAnswer() {
+
+ return new Answer<Boolean>() {
+
+ @Override
+ public Boolean answer(final InvocationOnMock invocationOnMock) throws Throwable {
+ return connected;
+ }
+
+ };
+
+ };
+ }
+
+}
Propchange: httpcomponents/httpclient/trunk/httpclient5/src/test/java/org/apache/hc/client5/http/impl/sync/TestConnectExec.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: httpcomponents/httpclient/trunk/httpclient5/src/test/java/org/apache/hc/client5/http/impl/sync/TestConnectExec.java
------------------------------------------------------------------------------
svn:keywords = Date Revision
Propchange: httpcomponents/httpclient/trunk/httpclient5/src/test/java/org/apache/hc/client5/http/impl/sync/TestConnectExec.java
------------------------------------------------------------------------------
svn:mime-type = text/plain