Refactored connection routing and protocol execution code in the classic exec chain
git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1793325 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
bde89fee57
commit
d43ee7177b
|
@ -29,6 +29,6 @@ package org.apache.hc.client5.http.impl.sync;
|
|||
|
||||
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
|
||||
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
|
||||
}
|
|
@ -99,6 +99,7 @@ import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
|
|||
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(
|
||||
reuseStrategyCopy,
|
||||
keepAliveStrategyCopy,
|
||||
new DefaultHttpProcessor(new RequestTargetHost(), new RequestUserAgent(userAgentCopy)),
|
||||
targetAuthStrategyCopy,
|
||||
proxyAuthStrategyCopy,
|
||||
userTokenHandlerCopy),
|
||||
new MainClientExec(reuseStrategyCopy, keepAliveStrategyCopy, userTokenHandlerCopy),
|
||||
ChainElements.MAIN_TRANSPORT.name());
|
||||
execChainDefinition.addFirst(
|
||||
new ConnectExec(
|
||||
reuseStrategyCopy,
|
||||
new DefaultHttpProcessor(new RequestTargetHost(), new RequestUserAgent(userAgentCopy)),
|
||||
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
|
||||
|
|
|
@ -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.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.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 ExecChainHandler {
|
|||
|
||||
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 ExecChainHandler {
|
|||
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 ExecChainHandler {
|
|||
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 ExecChainHandler {
|
|||
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 ExecChainHandler {
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,149 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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 java.io.InterruptedIOException;
|
||||
|
||||
import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
|
||||
import org.apache.hc.client5.http.HttpRoute;
|
||||
import org.apache.hc.client5.http.impl.ConnectionShutdownException;
|
||||
import org.apache.hc.client5.http.impl.DefaultConnectionKeepAliveStrategy;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.client5.http.protocol.RequestClientConnControl;
|
||||
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.impl.DefaultConnectionReuseStrategy;
|
||||
import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
|
||||
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.util.Args;
|
||||
import org.apache.hc.core5.util.TimeValue;
|
||||
import org.apache.hc.core5.util.VersionInfo;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Request executor that implements the most fundamental aspects of
|
||||
* the HTTP specification and the most straight-forward request / response
|
||||
* exchange with the target server. This executor does not support
|
||||
* execution via proxy and will make no attempts to retry the request
|
||||
* in case of a redirect, authentication challenge or I/O error.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
@Contract(threading = ThreadingBehavior.IMMUTABLE)
|
||||
final class MinimalClientExec implements ExecChainHandler {
|
||||
|
||||
private final Logger log = LogManager.getLogger(getClass());
|
||||
|
||||
private final ConnectionReuseStrategy reuseStrategy;
|
||||
private final ConnectionKeepAliveStrategy keepAliveStrategy;
|
||||
private final HttpProcessor httpProcessor;
|
||||
|
||||
public MinimalClientExec(
|
||||
final ConnectionReuseStrategy reuseStrategy,
|
||||
final ConnectionKeepAliveStrategy keepAliveStrategy) {
|
||||
this.reuseStrategy = reuseStrategy != null ? reuseStrategy : DefaultConnectionReuseStrategy.INSTANCE;
|
||||
this.keepAliveStrategy = keepAliveStrategy != null ? keepAliveStrategy : DefaultConnectionKeepAliveStrategy.INSTANCE;
|
||||
this.httpProcessor = new DefaultHttpProcessor(
|
||||
new RequestContent(),
|
||||
new RequestTargetHost(),
|
||||
new RequestClientConnControl(),
|
||||
new RequestUserAgent(VersionInfo.getSoftwareInfo(
|
||||
"Apache-HttpClient", "org.apache.hc.client5", getClass())));
|
||||
}
|
||||
|
||||
@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()) {
|
||||
execRuntime.acquireConnection(route, null, context);
|
||||
}
|
||||
try {
|
||||
if (!execRuntime.isConnected()) {
|
||||
execRuntime.connect(context);
|
||||
}
|
||||
|
||||
context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
|
||||
context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
|
||||
|
||||
httpProcessor.process(request, request.getEntity(), context);
|
||||
final ClassicHttpResponse response = execRuntime.execute(request, context);
|
||||
httpProcessor.process(response, response.getEntity(), 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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 CloseableHttpClient {
|
|||
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 CloseableHttpClient {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,33 +30,46 @@ package org.apache.hc.client5.http.impl.sync;
|
|||
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 ExecChainHandler {
|
|||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Run request protocol interceptors
|
||||
context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
|
||||
context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
|
||||
|
||||
this.httpProcessor.process(request, request.getEntity(), context);
|
||||
|
||||
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;
|
||||
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) {
|
||||
CredentialSupport.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) {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -55,20 +55,6 @@ class RequestEntityProxy implements HttpEntity {
|
|||
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 HttpEntity {
|
|||
|
||||
@Override
|
||||
public boolean isRepeatable() {
|
||||
return original.isRepeatable();
|
||||
if (!consumed) {
|
||||
return true;
|
||||
} else {
|
||||
return original.isRepeatable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -40,6 +40,7 @@ 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;
|
||||
|
@ -99,7 +100,8 @@ final class RetryExec implements ExecChainHandler {
|
|||
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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -27,52 +27,28 @@
|
|||
package org.apache.hc.client5.http.impl.sync;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
|
||||
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.AuthExchange;
|
||||
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.ConnectionShutdownException;
|
||||
import org.apache.hc.client5.http.impl.auth.BasicScheme;
|
||||
import org.apache.hc.client5.http.impl.auth.NTLMScheme;
|
||||
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.sync.ExecChain;
|
||||
import org.apache.hc.client5.http.sync.ExecRuntime;
|
||||
import org.apache.hc.client5.http.sync.methods.HttpGet;
|
||||
import org.apache.hc.client5.http.sync.methods.HttpPost;
|
||||
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.apache.hc.core5.util.TimeValue;
|
||||
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;
|
||||
|
@ -87,27 +63,18 @@ public class TestMainClientExec {
|
|||
@Mock
|
||||
private ConnectionKeepAliveStrategy keepAliveStrategy;
|
||||
@Mock
|
||||
private HttpProcessor proxyHttpProcessor;
|
||||
@Mock
|
||||
private AuthenticationStrategy targetAuthStrategy;
|
||||
@Mock
|
||||
private AuthenticationStrategy proxyAuthStrategy;
|
||||
@Mock
|
||||
private UserTokenHandler userTokenHandler;
|
||||
@Mock
|
||||
private ExecRuntime endpoint;
|
||||
|
||||
private MainClientExec mainClientExec;
|
||||
private HttpHost target;
|
||||
private HttpHost proxy;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mainClientExec = new MainClientExec(reuseStrategy, keepAliveStrategy, proxyHttpProcessor, targetAuthStrategy,
|
||||
proxyAuthStrategy, userTokenHandler);
|
||||
mainClientExec = new MainClientExec(reuseStrategy, keepAliveStrategy, userTokenHandler);
|
||||
target = new HttpHost("foo", 80);
|
||||
proxy = new HttpHost("bar", 8888);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -131,8 +98,6 @@ public class TestMainClientExec {
|
|||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
|
||||
Mockito.verify(endpoint).acquireConnection(route, null, context);
|
||||
Mockito.verify(endpoint).connect(context);
|
||||
Mockito.verify(endpoint).execute(request, context);
|
||||
Mockito.verify(endpoint, Mockito.times(1)).markConnectionNonReusable();
|
||||
Mockito.verify(endpoint, Mockito.never()).releaseConnection();
|
||||
|
@ -162,8 +127,6 @@ public class TestMainClientExec {
|
|||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
|
||||
|
||||
Mockito.verify(endpoint).acquireConnection(route, null, context);
|
||||
Mockito.verify(endpoint).connect(context);
|
||||
Mockito.verify(endpoint).execute(request, context);
|
||||
Mockito.verify(endpoint).markConnectionNonReusable();
|
||||
Mockito.verify(endpoint).releaseConnection();
|
||||
|
@ -200,8 +163,6 @@ public class TestMainClientExec {
|
|||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
|
||||
|
||||
Mockito.verify(endpoint).acquireConnection(route, null, context);
|
||||
Mockito.verify(endpoint).connect(context);
|
||||
Mockito.verify(endpoint).execute(request, context);
|
||||
Mockito.verify(endpoint).markConnectionReusable();
|
||||
Mockito.verify(endpoint, Mockito.never()).releaseConnection();
|
||||
|
@ -234,8 +195,6 @@ public class TestMainClientExec {
|
|||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
|
||||
|
||||
Mockito.verify(endpoint).acquireConnection(route, null, context);
|
||||
Mockito.verify(endpoint).connect(context);
|
||||
Mockito.verify(endpoint).execute(request, context);
|
||||
Mockito.verify(endpoint).releaseConnection();
|
||||
|
||||
|
@ -355,355 +314,6 @@ public class TestMainClientExec {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecRequestRetryOnAuthChallenge() throws Exception {
|
||||
final HttpRoute route = new HttpRoute(target);
|
||||
final HttpClientContext context = new HttpClientContext();
|
||||
final ClassicHttpRequest request = new HttpGet("http://foo/test");
|
||||
final ClassicHttpResponse response1 = new BasicClassicHttpResponse(401, "Huh?");
|
||||
response1.setHeader(HttpHeaders.WWW_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 InputStream instream2 = Mockito.spy(new ByteArrayInputStream(new byte[] {2, 3, 4}));
|
||||
response2.setEntity(EntityBuilder.create()
|
||||
.setStream(instream2)
|
||||
.build());
|
||||
|
||||
final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
|
||||
credentialsProvider.setCredentials(new AuthScope(target), new UsernamePasswordCredentials("user", "pass".toCharArray()));
|
||||
context.setCredentialsProvider(credentialsProvider);
|
||||
|
||||
final ConnectionState connectionState = new ConnectionState();
|
||||
Mockito.doAnswer(connectionState.connectAnswer()).when(endpoint).connect(Mockito.<HttpClientContext>any());
|
||||
Mockito.when(endpoint.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
|
||||
Mockito.when(endpoint.execute(
|
||||
Mockito.same(request),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(response1, response2);
|
||||
Mockito.when(reuseStrategy.keepAlive(
|
||||
Mockito.same(request),
|
||||
Mockito.<HttpResponse>any(),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(Boolean.TRUE);
|
||||
Mockito.when(targetAuthStrategy.select(
|
||||
Mockito.eq(ChallengeType.TARGET),
|
||||
Mockito.<Map<String, AuthChallenge>>any(),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(Collections.<AuthScheme>singletonList(new BasicScheme()));
|
||||
Mockito.when(endpoint.isConnectionReusable()).thenReturn(true);
|
||||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
|
||||
Mockito.verify(endpoint, Mockito.times(2)).execute(request, context);
|
||||
Mockito.verify(instream1).close();
|
||||
Mockito.verify(instream2, Mockito.never()).close();
|
||||
|
||||
Assert.assertNotNull(finalResponse);
|
||||
Assert.assertEquals(200, finalResponse.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecEntityEnclosingRequestRetryOnAuthChallenge() throws Exception {
|
||||
final HttpRoute route = new HttpRoute(target, proxy);
|
||||
final ClassicHttpRequest request = new HttpGet("http://foo/test");
|
||||
final ClassicHttpResponse response1 = new BasicClassicHttpResponse(401, "Huh?");
|
||||
response1.setHeader(HttpHeaders.WWW_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 InputStream instream2 = Mockito.spy(new ByteArrayInputStream(new byte[] {2, 3, 4}));
|
||||
response2.setEntity(EntityBuilder.create()
|
||||
.setStream(instream2)
|
||||
.build());
|
||||
|
||||
final HttpClientContext context = new HttpClientContext();
|
||||
|
||||
final AuthExchange proxyAuthExchange = new AuthExchange();
|
||||
proxyAuthExchange.setState(AuthExchange.State.SUCCESS);
|
||||
proxyAuthExchange.select(new NTLMScheme());
|
||||
context.setAuthExchange(proxy, proxyAuthExchange);
|
||||
|
||||
final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
|
||||
credentialsProvider.setCredentials(new AuthScope(target), new UsernamePasswordCredentials("user", "pass".toCharArray()));
|
||||
context.setCredentialsProvider(credentialsProvider);
|
||||
|
||||
final ConnectionState connectionState = new ConnectionState();
|
||||
Mockito.doAnswer(connectionState.connectAnswer()).when(endpoint).connect(Mockito.<HttpClientContext>any());
|
||||
Mockito.when(endpoint.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
|
||||
Mockito.when(endpoint.execute(
|
||||
Mockito.same(request),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(response1, response2);
|
||||
Mockito.when(reuseStrategy.keepAlive(
|
||||
Mockito.same(request),
|
||||
Mockito.<HttpResponse>any(),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(Boolean.FALSE);
|
||||
|
||||
Mockito.when(targetAuthStrategy.select(
|
||||
Mockito.eq(ChallengeType.TARGET),
|
||||
Mockito.<Map<String, AuthChallenge>>any(),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(Collections.<AuthScheme>singletonList(new BasicScheme()));
|
||||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
|
||||
Mockito.verify(endpoint, Mockito.times(2)).execute(request, context);
|
||||
Mockito.verify(endpoint).disconnect();
|
||||
Mockito.verify(instream2, Mockito.never()).close();
|
||||
|
||||
Assert.assertNotNull(finalResponse);
|
||||
Assert.assertEquals(200, finalResponse.getCode());
|
||||
Assert.assertNull(proxyAuthExchange.getAuthScheme());
|
||||
}
|
||||
|
||||
@Test(expected = NonRepeatableRequestException.class)
|
||||
public void testExecEntityEnclosingRequest() throws Exception {
|
||||
final HttpRoute route = new HttpRoute(target);
|
||||
final HttpClientContext context = new HttpClientContext();
|
||||
final HttpPost request = new HttpPost("http://foo/test");
|
||||
final InputStream instream0 = new ByteArrayInputStream(new byte[] {1, 2, 3});
|
||||
request.setEntity(EntityBuilder.create()
|
||||
.setStream(instream0)
|
||||
.build());
|
||||
final ClassicHttpResponse response1 = new BasicClassicHttpResponse(401, "Huh?");
|
||||
response1.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=test");
|
||||
final InputStream instream1 = new ByteArrayInputStream(new byte[] {1, 2, 3});
|
||||
response1.setEntity(EntityBuilder.create()
|
||||
.setStream(instream1)
|
||||
.build());
|
||||
|
||||
final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
|
||||
credentialsProvider.setCredentials(new AuthScope(target), new UsernamePasswordCredentials("user", "pass".toCharArray()));
|
||||
context.setCredentialsProvider(credentialsProvider);
|
||||
|
||||
final ConnectionState connectionState = new ConnectionState();
|
||||
Mockito.doAnswer(connectionState.connectAnswer()).when(endpoint).connect(Mockito.<HttpClientContext>any());
|
||||
Mockito.when(endpoint.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
|
||||
Mockito.when(endpoint.execute(
|
||||
Mockito.same(request),
|
||||
Mockito.<HttpClientContext>any())).thenAnswer(new Answer<HttpResponse>() {
|
||||
|
||||
@Override
|
||||
public HttpResponse answer(final InvocationOnMock invocationOnMock) throws Throwable {
|
||||
final Object[] args = invocationOnMock.getArguments();
|
||||
final ClassicHttpRequest requestEE = (ClassicHttpRequest) args[0];
|
||||
requestEE.getEntity().writeTo(new ByteArrayOutputStream());
|
||||
return response1;
|
||||
}
|
||||
|
||||
});
|
||||
Mockito.when(reuseStrategy.keepAlive(
|
||||
Mockito.same(request),
|
||||
Mockito.<HttpResponse>any(),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(Boolean.TRUE);
|
||||
|
||||
Mockito.when(targetAuthStrategy.select(
|
||||
Mockito.eq(ChallengeType.TARGET),
|
||||
Mockito.<Map<String, AuthChallenge>>any(),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(Collections.<AuthScheme>singletonList(new BasicScheme()));
|
||||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
mainClientExec.execute(request, scope, null);
|
||||
}
|
||||
|
||||
@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(endpoint).connect(Mockito.<HttpClientContext>any());
|
||||
Mockito.when(endpoint.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
|
||||
|
||||
mainClientExec.establishRoute(route, request, endpoint, context);
|
||||
|
||||
Mockito.verify(endpoint).connect(context);
|
||||
Mockito.verify(endpoint, 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(endpoint).connect(Mockito.<HttpClientContext>any());
|
||||
Mockito.when(endpoint.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
|
||||
|
||||
mainClientExec.establishRoute(route, request, endpoint, context);
|
||||
|
||||
Mockito.verify(endpoint).connect(context);
|
||||
Mockito.verify(endpoint, 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(endpoint).connect(Mockito.<HttpClientContext>any());
|
||||
Mockito.when(endpoint.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
|
||||
Mockito.when(endpoint.execute(
|
||||
Mockito.<ClassicHttpRequest>any(),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(response);
|
||||
|
||||
mainClientExec.establishRoute(route, request, endpoint, context);
|
||||
|
||||
Mockito.verify(endpoint).connect(context);
|
||||
final ArgumentCaptor<ClassicHttpRequest> reqCaptor = ArgumentCaptor.forClass(ClassicHttpRequest.class);
|
||||
Mockito.verify(endpoint).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(endpoint).connect(Mockito.<HttpClientContext>any());
|
||||
Mockito.when(endpoint.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
|
||||
Mockito.when(endpoint.execute(
|
||||
Mockito.<ClassicHttpRequest>any(),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(response);
|
||||
|
||||
mainClientExec.establishRoute(route, request, endpoint, context);
|
||||
}
|
||||
|
||||
@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(endpoint).connect(Mockito.<HttpClientContext>any());
|
||||
Mockito.when(endpoint.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
|
||||
Mockito.when(endpoint.execute(
|
||||
Mockito.<ClassicHttpRequest>any(),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(response);
|
||||
|
||||
try {
|
||||
mainClientExec.establishRoute(route, request, endpoint, context);
|
||||
} catch (final TunnelRefusedException ex) {
|
||||
final ClassicHttpResponse r = ex.getResponse();
|
||||
Assert.assertEquals("Ka-boom", EntityUtils.toString(r.getEntity()));
|
||||
Mockito.verify(endpoint).disconnect();
|
||||
Mockito.verify(endpoint).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(endpoint).connect(Mockito.<HttpClientContext>any());
|
||||
Mockito.when(endpoint.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
|
||||
Mockito.when(reuseStrategy.keepAlive(
|
||||
Mockito.same(request),
|
||||
Mockito.<HttpResponse>any(),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(Boolean.TRUE);
|
||||
Mockito.when(endpoint.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()));
|
||||
|
||||
mainClientExec.establishRoute(route, request, endpoint, context);
|
||||
|
||||
Mockito.verify(endpoint).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(endpoint).connect(Mockito.<HttpClientContext>any());
|
||||
Mockito.when(endpoint.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
|
||||
Mockito.when(reuseStrategy.keepAlive(
|
||||
Mockito.same(request),
|
||||
Mockito.<HttpResponse>any(),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(Boolean.FALSE);
|
||||
Mockito.when(endpoint.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()));
|
||||
|
||||
mainClientExec.establishRoute(route, request, endpoint, context);
|
||||
|
||||
Mockito.verify(endpoint).connect(context);
|
||||
Mockito.verify(instream1, Mockito.never()).close();
|
||||
Mockito.verify(endpoint).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(endpoint).connect(Mockito.<HttpClientContext>any());
|
||||
Mockito.when(endpoint.isConnected()).thenAnswer(connectionState.isConnectedAnswer());
|
||||
|
||||
mainClientExec.establishRoute(route, request, endpoint, context);
|
||||
}
|
||||
|
||||
static class ConnectionState {
|
||||
|
||||
private boolean connected;
|
||||
|
|
|
@ -1,313 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.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.entity.EntityBuilder;
|
||||
import org.apache.hc.client5.http.impl.ConnectionShutdownException;
|
||||
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.HttpHost;
|
||||
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
|
||||
import org.apache.hc.core5.util.TimeValue;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@SuppressWarnings({"boxing","static-access"}) // test code
|
||||
public class TestMinimalClientExec {
|
||||
|
||||
@Mock
|
||||
private ConnectionReuseStrategy reuseStrategy;
|
||||
@Mock
|
||||
private ConnectionKeepAliveStrategy keepAliveStrategy;
|
||||
@Mock
|
||||
private ExecRuntime endpoint;
|
||||
|
||||
private MinimalClientExec minimalClientExec;
|
||||
private HttpHost target;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
minimalClientExec = new MinimalClientExec(reuseStrategy, keepAliveStrategy);
|
||||
target = new HttpHost("foo", 80);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecRequestNonPersistentConnection() 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());
|
||||
|
||||
Mockito.when(endpoint.isConnectionAcquired()).thenReturn(false);
|
||||
Mockito.when(endpoint.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, endpoint, context);
|
||||
final ClassicHttpResponse finalResponse = minimalClientExec.execute(request, scope, null);
|
||||
Mockito.verify(endpoint).acquireConnection(route, null, context);
|
||||
Mockito.verify(endpoint).connect(context);
|
||||
Mockito.verify(endpoint).execute(request, context);
|
||||
Mockito.verify(endpoint).markConnectionNonReusable();
|
||||
Mockito.verify(endpoint, Mockito.never()).releaseConnection();
|
||||
|
||||
Assert.assertNotNull(finalResponse);
|
||||
Assert.assertTrue(finalResponse instanceof CloseableHttpResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecRequestNonPersistentConnectionNoResponseEntity() 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(null);
|
||||
|
||||
Mockito.when(endpoint.isConnectionAcquired()).thenReturn(false);
|
||||
Mockito.when(endpoint.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, endpoint, context);
|
||||
final ClassicHttpResponse finalResponse = minimalClientExec.execute(request, scope, null);
|
||||
|
||||
Mockito.verify(endpoint).acquireConnection(route, null, context);
|
||||
Mockito.verify(endpoint).connect(context);
|
||||
Mockito.verify(endpoint).execute(request, context);
|
||||
Mockito.verify(endpoint).markConnectionNonReusable();
|
||||
Mockito.verify(endpoint).releaseConnection();
|
||||
|
||||
Assert.assertNotNull(finalResponse);
|
||||
Assert.assertTrue(finalResponse instanceof CloseableHttpResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecRequestPersistentConnection() 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");
|
||||
// The entity is streaming
|
||||
response.setEntity(EntityBuilder.create()
|
||||
.setStream(new ByteArrayInputStream(new byte[]{}))
|
||||
.build());
|
||||
|
||||
Mockito.when(endpoint.isConnected()).thenReturn(false);
|
||||
Mockito.when(endpoint.execute(
|
||||
Mockito.same(request),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(response);
|
||||
Mockito.when(reuseStrategy.keepAlive(
|
||||
Mockito.same(request),
|
||||
Mockito.same(response),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(true);
|
||||
Mockito.when(keepAliveStrategy.getKeepAliveDuration(
|
||||
Mockito.same(response),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(TimeValue.ofMillis(678L));
|
||||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
final ClassicHttpResponse finalResponse = minimalClientExec.execute(request, scope, null);
|
||||
|
||||
Mockito.verify(endpoint).acquireConnection(route, null, context);
|
||||
Mockito.verify(endpoint).connect(context);
|
||||
Mockito.verify(endpoint).execute(request, context);
|
||||
Mockito.verify(endpoint).markConnectionReusable();
|
||||
Mockito.verify(endpoint, Mockito.never()).releaseConnection();
|
||||
|
||||
Assert.assertNotNull(finalResponse);
|
||||
Assert.assertTrue(finalResponse instanceof CloseableHttpResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecRequestPersistentConnectionNoResponseEntity() 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");
|
||||
|
||||
Mockito.when(endpoint.isConnected()).thenReturn(false);
|
||||
Mockito.when(endpoint.execute(
|
||||
Mockito.same(request),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(response);
|
||||
Mockito.when(reuseStrategy.keepAlive(
|
||||
Mockito.same(request),
|
||||
Mockito.same(response),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(true);
|
||||
Mockito.when(keepAliveStrategy.getKeepAliveDuration(
|
||||
Mockito.same(response),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(TimeValue.ofMillis(678L));
|
||||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
final ClassicHttpResponse finalResponse = minimalClientExec.execute(request, scope, null);
|
||||
|
||||
Mockito.verify(endpoint).acquireConnection(route, null, context);
|
||||
Mockito.verify(endpoint).connect(context);
|
||||
Mockito.verify(endpoint).execute(request, context);
|
||||
Mockito.verify(endpoint).releaseConnection();
|
||||
|
||||
Assert.assertNotNull(finalResponse);
|
||||
Assert.assertTrue(finalResponse instanceof CloseableHttpResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecRequestConnectionRelease() 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");
|
||||
// The entity is streaming
|
||||
response.setEntity(EntityBuilder.create()
|
||||
.setStream(new ByteArrayInputStream(new byte[]{}))
|
||||
.build());
|
||||
|
||||
Mockito.when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
|
||||
Mockito.when(endpoint.execute(
|
||||
Mockito.same(request),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(response);
|
||||
Mockito.when(reuseStrategy.keepAlive(
|
||||
Mockito.same(request),
|
||||
Mockito.same(response),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(Boolean.FALSE);
|
||||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
final ClassicHttpResponse finalResponse = minimalClientExec.execute(request, scope, null);
|
||||
Mockito.verify(endpoint, Mockito.times(1)).execute(request, context);
|
||||
Mockito.verify(endpoint, Mockito.never()).disconnect();
|
||||
Mockito.verify(endpoint, Mockito.never()).releaseConnection();
|
||||
|
||||
Assert.assertNotNull(finalResponse);
|
||||
Assert.assertTrue(finalResponse instanceof CloseableHttpResponse);
|
||||
finalResponse.close();
|
||||
|
||||
Mockito.verify(endpoint).disconnect();
|
||||
Mockito.verify(endpoint).discardConnection();
|
||||
}
|
||||
|
||||
@Test(expected=InterruptedIOException.class)
|
||||
public void testExecConnectionShutDown() throws Exception {
|
||||
final HttpRoute route = new HttpRoute(target);
|
||||
final HttpClientContext context = new HttpClientContext();
|
||||
final ClassicHttpRequest request = new HttpGet("http://bar/test");
|
||||
|
||||
Mockito.when(endpoint.execute(
|
||||
Mockito.same(request),
|
||||
Mockito.<HttpClientContext>any())).thenThrow(new ConnectionShutdownException());
|
||||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
try {
|
||||
minimalClientExec.execute(request, scope, null);
|
||||
} catch (Exception ex) {
|
||||
Mockito.verify(endpoint).discardConnection();
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected=RuntimeException.class)
|
||||
public void testExecRuntimeException() throws Exception {
|
||||
final HttpRoute route = new HttpRoute(target);
|
||||
final HttpClientContext context = new HttpClientContext();
|
||||
final ClassicHttpRequest request = new HttpGet("http://bar/test");
|
||||
|
||||
Mockito.when(endpoint.execute(
|
||||
Mockito.same(request),
|
||||
Mockito.<HttpClientContext>any())).thenThrow(new RuntimeException("Ka-boom"));
|
||||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
try {
|
||||
minimalClientExec.execute(request, scope, null);
|
||||
} catch (final Exception ex) {
|
||||
Mockito.verify(endpoint).discardConnection();
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected=HttpException.class)
|
||||
public void testExecHttpException() throws Exception {
|
||||
final HttpRoute route = new HttpRoute(target);
|
||||
final HttpClientContext context = new HttpClientContext();
|
||||
final ClassicHttpRequest request = new HttpGet("http://bar/test");
|
||||
|
||||
Mockito.when(endpoint.execute(
|
||||
Mockito.same(request),
|
||||
Mockito.<HttpClientContext>any())).thenThrow(new HttpException("Ka-boom"));
|
||||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
try {
|
||||
minimalClientExec.execute(request, scope, null);
|
||||
} catch (final Exception ex) {
|
||||
Mockito.verify(endpoint).discardConnection();
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected=IOException.class)
|
||||
public void testExecIOException() throws Exception {
|
||||
final HttpRoute route = new HttpRoute(target);
|
||||
final HttpClientContext context = new HttpClientContext();
|
||||
final ClassicHttpRequest request = new HttpGet("http://bar/test");
|
||||
|
||||
Mockito.when(endpoint.execute(
|
||||
Mockito.same(request),
|
||||
Mockito.<HttpClientContext>any())).thenThrow(new IOException("Ka-boom"));
|
||||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
try {
|
||||
minimalClientExec.execute(request, scope, null);
|
||||
} catch (final Exception ex) {
|
||||
Mockito.verify(endpoint).discardConnection();
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -26,22 +26,41 @@
|
|||
*/
|
||||
package org.apache.hc.client5.http.impl.sync;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.hc.client5.http.HttpRoute;
|
||||
import org.apache.hc.client5.http.auth.AuthChallenge;
|
||||
import org.apache.hc.client5.http.auth.AuthExchange;
|
||||
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.Credentials;
|
||||
import org.apache.hc.client5.http.auth.CredentialsProvider;
|
||||
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.impl.auth.NTLMScheme;
|
||||
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.ExecRuntime;
|
||||
import org.apache.hc.client5.http.sync.methods.HttpGet;
|
||||
import org.apache.hc.client5.http.sync.methods.HttpPost;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.EntityDetails;
|
||||
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.message.BasicClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||
import org.apache.hc.core5.http.protocol.HttpProcessor;
|
||||
import org.junit.Assert;
|
||||
|
@ -50,6 +69,8 @@ import org.junit.Test;
|
|||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
@SuppressWarnings({"static-access"}) // test code
|
||||
public class TestProtocolExec {
|
||||
|
@ -57,18 +78,24 @@ public class TestProtocolExec {
|
|||
@Mock
|
||||
private HttpProcessor httpProcessor;
|
||||
@Mock
|
||||
private AuthenticationStrategy targetAuthStrategy;
|
||||
@Mock
|
||||
private AuthenticationStrategy proxyAuthStrategy;
|
||||
@Mock
|
||||
private ExecChain chain;
|
||||
@Mock
|
||||
private ExecRuntime endpoint;
|
||||
private ExecRuntime execRuntime;
|
||||
|
||||
private ProtocolExec protocolExec;
|
||||
private HttpHost target;
|
||||
private HttpHost proxy;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
protocolExec = new ProtocolExec(httpProcessor);
|
||||
protocolExec = new ProtocolExec(httpProcessor, targetAuthStrategy, proxyAuthStrategy);
|
||||
target = new HttpHost("foo", 80);
|
||||
proxy = new HttpHost("bar", 8888);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -83,7 +110,7 @@ public class TestProtocolExec {
|
|||
Mockito.<ClassicHttpRequest>any(),
|
||||
Mockito.<ExecChain.Scope>any())).thenReturn(response);
|
||||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, execRuntime, context);
|
||||
protocolExec.execute(request, scope, chain);
|
||||
|
||||
Mockito.verify(httpProcessor).process(request, null, context);
|
||||
|
@ -107,7 +134,7 @@ public class TestProtocolExec {
|
|||
Mockito.<ClassicHttpRequest>any(),
|
||||
Mockito.<ExecChain.Scope>any())).thenReturn(response);
|
||||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, execRuntime, context);
|
||||
protocolExec.execute(request, scope, chain);
|
||||
Assert.assertEquals(new URI("http://bar/test"), request.getUri());
|
||||
final CredentialsProvider credentialsProvider = context.getCredentialsProvider();
|
||||
|
@ -129,11 +156,11 @@ public class TestProtocolExec {
|
|||
Mockito.<ExecChain.Scope>any())).thenReturn(response);
|
||||
Mockito.doThrow(new HttpException("Ooopsie")).when(httpProcessor).process(
|
||||
Mockito.same(response), Mockito.isNull(EntityDetails.class), Mockito.<HttpContext>any());
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, execRuntime, context);
|
||||
try {
|
||||
protocolExec.execute(request, scope, chain);
|
||||
} catch (final Exception ex) {
|
||||
Mockito.verify(response).close();
|
||||
Mockito.verify(execRuntime).discardConnection();
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
@ -150,11 +177,11 @@ public class TestProtocolExec {
|
|||
Mockito.<ExecChain.Scope>any())).thenReturn(response);
|
||||
Mockito.doThrow(new IOException("Ooopsie")).when(httpProcessor).process(
|
||||
Mockito.same(response), Mockito.isNull(EntityDetails.class), Mockito.<HttpContext>any());
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, execRuntime, context);
|
||||
try {
|
||||
protocolExec.execute(request, scope, chain);
|
||||
} catch (final Exception ex) {
|
||||
Mockito.verify(response).close();
|
||||
Mockito.verify(execRuntime).discardConnection();
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
@ -171,13 +198,143 @@ public class TestProtocolExec {
|
|||
Mockito.<ExecChain.Scope>any())).thenReturn(response);
|
||||
Mockito.doThrow(new RuntimeException("Ooopsie")).when(httpProcessor).process(
|
||||
Mockito.same(response), Mockito.isNull(EntityDetails.class), Mockito.<HttpContext>any());
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, endpoint, context);
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, execRuntime, context);
|
||||
try {
|
||||
protocolExec.execute(request, scope, chain);
|
||||
} catch (final Exception ex) {
|
||||
Mockito.verify(response).close();
|
||||
Mockito.verify(execRuntime).discardConnection();
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecRequestRetryOnAuthChallenge() throws Exception {
|
||||
final HttpRoute route = new HttpRoute(target);
|
||||
final HttpClientContext context = new HttpClientContext();
|
||||
final ClassicHttpRequest request = new HttpGet("http://foo/test");
|
||||
final ClassicHttpResponse response1 = new BasicClassicHttpResponse(401, "Huh?");
|
||||
response1.setHeader(HttpHeaders.WWW_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 InputStream instream2 = Mockito.spy(new ByteArrayInputStream(new byte[] {2, 3, 4}));
|
||||
response2.setEntity(EntityBuilder.create()
|
||||
.setStream(instream2)
|
||||
.build());
|
||||
|
||||
final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
|
||||
credentialsProvider.setCredentials(new AuthScope(target), new UsernamePasswordCredentials("user", "pass".toCharArray()));
|
||||
context.setCredentialsProvider(credentialsProvider);
|
||||
|
||||
Mockito.when(chain.proceed(
|
||||
Mockito.same(request),
|
||||
Mockito.<ExecChain.Scope>any())).thenReturn(response1, response2);
|
||||
Mockito.when(targetAuthStrategy.select(
|
||||
Mockito.eq(ChallengeType.TARGET),
|
||||
Mockito.<Map<String, AuthChallenge>>any(),
|
||||
Mockito.<HttpClientContext>any())).thenReturn(Collections.<AuthScheme>singletonList(new BasicScheme()));
|
||||
Mockito.when(execRuntime.isConnectionReusable()).thenReturn(true);
|
||||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope(route, request, execRuntime, context);
|
||||
final ClassicHttpResponse finalResponse = protocolExec.execute(request, scope, chain);
|
||||
Mockito.verify(chain, Mockito.times(2)).proceed(request, scope);
|
||||
Mockito.verify(instream1).close();
|
||||
Mockito.verify(instream2, Mockito.never()).close();
|
||||
|
||||
Assert.assertNotNull(finalResponse);
|
||||
Assert.assertEquals(200, finalResponse.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecEntityEnclosingRequestRetryOnAuthChallenge() throws Exception {
|
||||
final HttpRoute route = new HttpRoute(target, proxy);
|
||||
final ClassicHttpRequest request = new HttpGet("http://foo/test");
|
||||
final ClassicHttpResponse response1 = new BasicClassicHttpResponse(401, "Huh?");
|
||||
response1.setHeader(HttpHeaders.WWW_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 InputStream instream2 = Mockito.spy(new ByteArrayInputStream(new byte[] {2, 3, 4}));
|
||||
response2.setEntity(EntityBuilder.create()
|
||||
.setStream(instream2)
|
||||
.build());
|
||||
|
||||
final HttpClientContext context = new HttpClientContext();
|
||||
|
||||
final AuthExchange proxyAuthExchange = new AuthExchange();
|
||||
proxyAuthExchange.setState(AuthExchange.State.SUCCESS);
|
||||
proxyAuthExchange.select(new NTLMScheme());
|
||||
context.setAuthExchange(proxy, proxyAuthExchange);
|
||||
|
||||
final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
|
||||
credentialsProvider.setCredentials(new AuthScope(target), new UsernamePasswordCredentials("user", "pass".toCharArray()));
|
||||
context.setCredentialsProvider(credentialsProvider);
|
||||
|
||||
Mockito.when(chain.proceed(
|
||||
Mockito.same(request),
|
||||
Mockito.<ExecChain.Scope>any())).thenReturn(response1, response2);
|
||||
|
||||
Mockito.when(targetAuthStrategy.select(
|
||||
Mockito.eq(ChallengeType.TARGET),
|
||||
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);
|
||||
final ClassicHttpResponse finalResponse = protocolExec.execute(request, scope, chain);
|
||||
Mockito.verify(chain, Mockito.times(2)).proceed(request, scope);
|
||||
Mockito.verify(execRuntime).disconnect();
|
||||
Mockito.verify(instream2, Mockito.never()).close();
|
||||
|
||||
Assert.assertNotNull(finalResponse);
|
||||
Assert.assertEquals(200, finalResponse.getCode());
|
||||
Assert.assertNull(proxyAuthExchange.getAuthScheme());
|
||||
}
|
||||
|
||||
@Test(expected = NonRepeatableRequestException.class)
|
||||
public void testExecEntityEnclosingRequest() throws Exception {
|
||||
final HttpRoute route = new HttpRoute(target);
|
||||
final HttpClientContext context = new HttpClientContext();
|
||||
final HttpPost request = new HttpPost("http://foo/test");
|
||||
final InputStream instream0 = new ByteArrayInputStream(new byte[] {1, 2, 3});
|
||||
request.setEntity(EntityBuilder.create()
|
||||
.setStream(instream0)
|
||||
.build());
|
||||
final ClassicHttpResponse response1 = new BasicClassicHttpResponse(401, "Huh?");
|
||||
response1.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=test");
|
||||
final InputStream instream1 = new ByteArrayInputStream(new byte[] {1, 2, 3});
|
||||
response1.setEntity(EntityBuilder.create()
|
||||
.setStream(instream1)
|
||||
.build());
|
||||
|
||||
final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
|
||||
credentialsProvider.setCredentials(new AuthScope(target), new UsernamePasswordCredentials("user", "pass".toCharArray()));
|
||||
context.setCredentialsProvider(credentialsProvider);
|
||||
|
||||
Mockito.when(chain.proceed(
|
||||
Mockito.same(request),
|
||||
Mockito.<ExecChain.Scope>any())).thenAnswer(new Answer<HttpResponse>() {
|
||||
|
||||
@Override
|
||||
public HttpResponse answer(final InvocationOnMock invocationOnMock) throws Throwable {
|
||||
final Object[] args = invocationOnMock.getArguments();
|
||||
final ClassicHttpRequest requestEE = (ClassicHttpRequest) args[0];
|
||||
requestEE.getEntity().writeTo(new ByteArrayOutputStream());
|
||||
return response1;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Mockito.when(targetAuthStrategy.select(
|
||||
Mockito.eq(ChallengeType.TARGET),
|
||||
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);
|
||||
protocolExec.execute(request, scope, chain);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue