HTTPCLIENT-751: Support for RFC 2817 (Upgrading to TLS Within HTTP/1.1)
This commit is contained in:
parent
1111c62dd3
commit
3235f009d5
|
@ -62,13 +62,14 @@ public class RequestConfig implements Cloneable {
|
|||
private final TimeValue connectionKeepAlive;
|
||||
private final boolean contentCompressionEnabled;
|
||||
private final boolean hardCancellationEnabled;
|
||||
private final boolean protocolUpgradeEnabled;
|
||||
|
||||
/**
|
||||
* Intended for CDI compatibility
|
||||
*/
|
||||
protected RequestConfig() {
|
||||
this(false, null, null, false, false, 0, false, null, null,
|
||||
DEFAULT_CONNECTION_REQUEST_TIMEOUT, null, null, DEFAULT_CONN_KEEP_ALIVE, false, false);
|
||||
DEFAULT_CONNECTION_REQUEST_TIMEOUT, null, null, DEFAULT_CONN_KEEP_ALIVE, false, false, false);
|
||||
}
|
||||
|
||||
RequestConfig(
|
||||
|
@ -86,7 +87,8 @@ public class RequestConfig implements Cloneable {
|
|||
final Timeout responseTimeout,
|
||||
final TimeValue connectionKeepAlive,
|
||||
final boolean contentCompressionEnabled,
|
||||
final boolean hardCancellationEnabled) {
|
||||
final boolean hardCancellationEnabled,
|
||||
final boolean protocolUpgradeEnabled) {
|
||||
super();
|
||||
this.expectContinueEnabled = expectContinueEnabled;
|
||||
this.proxy = proxy;
|
||||
|
@ -103,6 +105,7 @@ public class RequestConfig implements Cloneable {
|
|||
this.connectionKeepAlive = connectionKeepAlive;
|
||||
this.contentCompressionEnabled = contentCompressionEnabled;
|
||||
this.hardCancellationEnabled = hardCancellationEnabled;
|
||||
this.protocolUpgradeEnabled = protocolUpgradeEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -217,6 +220,13 @@ public class RequestConfig implements Cloneable {
|
|||
return hardCancellationEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Builder#setProtocolUpgradeEnabled(boolean) (boolean)
|
||||
*/
|
||||
public boolean isProtocolUpgradeEnabled() {
|
||||
return protocolUpgradeEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RequestConfig clone() throws CloneNotSupportedException {
|
||||
return (RequestConfig) super.clone();
|
||||
|
@ -241,6 +251,7 @@ public class RequestConfig implements Cloneable {
|
|||
builder.append(", connectionKeepAlive=").append(connectionKeepAlive);
|
||||
builder.append(", contentCompressionEnabled=").append(contentCompressionEnabled);
|
||||
builder.append(", hardCancellationEnabled=").append(hardCancellationEnabled);
|
||||
builder.append(", protocolUpgradeEnabled=").append(protocolUpgradeEnabled);
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
@ -265,7 +276,8 @@ public class RequestConfig implements Cloneable {
|
|||
.setResponseTimeout(config.getResponseTimeout())
|
||||
.setConnectionKeepAlive(config.getConnectionKeepAlive())
|
||||
.setContentCompressionEnabled(config.isContentCompressionEnabled())
|
||||
.setHardCancellationEnabled(config.isHardCancellationEnabled());
|
||||
.setHardCancellationEnabled(config.isHardCancellationEnabled())
|
||||
.setProtocolUpgradeEnabled(config.isProtocolUpgradeEnabled());
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
@ -285,6 +297,7 @@ public class RequestConfig implements Cloneable {
|
|||
private TimeValue connectionKeepAlive;
|
||||
private boolean contentCompressionEnabled;
|
||||
private boolean hardCancellationEnabled;
|
||||
private boolean protocolUpgradeEnabled;
|
||||
|
||||
Builder() {
|
||||
super();
|
||||
|
@ -294,6 +307,7 @@ public class RequestConfig implements Cloneable {
|
|||
this.connectionRequestTimeout = DEFAULT_CONNECTION_REQUEST_TIMEOUT;
|
||||
this.contentCompressionEnabled = true;
|
||||
this.hardCancellationEnabled = true;
|
||||
this.protocolUpgradeEnabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -570,6 +584,23 @@ public class RequestConfig implements Cloneable {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the client server should automatically attempt to upgrade
|
||||
* to a safer or a newer version of the protocol, whenever possible.
|
||||
* <p>
|
||||
* Presently supported: HTTP/1.1 TLS upgrade
|
||||
* </p>
|
||||
* <p>
|
||||
* Default: {@code true}
|
||||
* </p>
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
public Builder setProtocolUpgradeEnabled(final boolean protocolUpgradeEnabled) {
|
||||
this.protocolUpgradeEnabled = protocolUpgradeEnabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RequestConfig build() {
|
||||
return new RequestConfig(
|
||||
expectContinueEnabled,
|
||||
|
@ -586,7 +617,8 @@ public class RequestConfig implements Cloneable {
|
|||
responseTimeout,
|
||||
connectionKeepAlive != null ? connectionKeepAlive : DEFAULT_CONN_KEEP_ALIVE,
|
||||
contentCompressionEnabled,
|
||||
hardCancellationEnabled);
|
||||
hardCancellationEnabled,
|
||||
protocolUpgradeEnabled);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.hc.core5.annotation.Internal;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpMessage;
|
||||
import org.apache.hc.core5.http.ParseException;
|
||||
import org.apache.hc.core5.http.ProtocolException;
|
||||
import org.apache.hc.core5.http.ProtocolVersion;
|
||||
import org.apache.hc.core5.http.message.MessageSupport;
|
||||
import org.apache.hc.core5.http.ssl.TLS;
|
||||
|
||||
/**
|
||||
* Protocol switch handler.
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
@Internal
|
||||
public final class ProtocolSwitchStrategy {
|
||||
|
||||
enum ProtocolSwitch { FAILURE, TLS }
|
||||
|
||||
public ProtocolVersion switchProtocol(final HttpMessage response) throws ProtocolException {
|
||||
final Iterator<String> it = MessageSupport.iterateTokens(response, HttpHeaders.UPGRADE);
|
||||
|
||||
ProtocolVersion tlsUpgrade = null;
|
||||
while (it.hasNext()) {
|
||||
final String token = it.next();
|
||||
if (token.startsWith("TLS")) {
|
||||
// TODO: Improve handling of HTTP protocol token once HttpVersion has a #parse method
|
||||
try {
|
||||
tlsUpgrade = token.length() == 3 ? TLS.V_1_2.getVersion() : TLS.parse(token.replace("TLS/", "TLSv"));
|
||||
} catch (final ParseException ex) {
|
||||
throw new ProtocolException("Invalid protocol: " + token);
|
||||
}
|
||||
} else if (token.equals("HTTP/1.1")) {
|
||||
// TODO: Improve handling of HTTP protocol token once HttpVersion has a #parse method
|
||||
} else {
|
||||
throw new ProtocolException("Unsupported protocol: " + token);
|
||||
}
|
||||
}
|
||||
if (tlsUpgrade == null) {
|
||||
throw new ProtocolException("Invalid protocol switch response");
|
||||
}
|
||||
return tlsUpgrade;
|
||||
}
|
||||
|
||||
}
|
|
@ -76,6 +76,7 @@ import org.apache.hc.client5.http.protocol.RedirectStrategy;
|
|||
import org.apache.hc.client5.http.protocol.RequestAddCookies;
|
||||
import org.apache.hc.client5.http.protocol.RequestDefaultHeaders;
|
||||
import org.apache.hc.client5.http.protocol.RequestExpectContinue;
|
||||
import org.apache.hc.client5.http.protocol.RequestUpgrade;
|
||||
import org.apache.hc.client5.http.protocol.RequestValidateTrace;
|
||||
import org.apache.hc.client5.http.protocol.ResponseProcessCookies;
|
||||
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
|
||||
|
@ -843,7 +844,8 @@ public class HttpAsyncClientBuilder {
|
|||
new H2RequestContent(),
|
||||
new H2RequestConnControl(),
|
||||
new RequestUserAgent(userAgentCopy),
|
||||
new RequestExpectContinue());
|
||||
new RequestExpectContinue(),
|
||||
new RequestUpgrade());
|
||||
if (!cookieManagementDisabled) {
|
||||
b.add(RequestAddCookies.INSTANCE);
|
||||
}
|
||||
|
|
|
@ -40,17 +40,21 @@ import org.apache.hc.client5.http.async.AsyncExecCallback;
|
|||
import org.apache.hc.client5.http.async.AsyncExecChain;
|
||||
import org.apache.hc.client5.http.async.AsyncExecChainHandler;
|
||||
import org.apache.hc.client5.http.async.AsyncExecRuntime;
|
||||
import org.apache.hc.client5.http.impl.ProtocolSwitchStrategy;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.core5.annotation.Contract;
|
||||
import org.apache.hc.core5.annotation.Internal;
|
||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||
import org.apache.hc.core5.concurrent.CancellableDependency;
|
||||
import org.apache.hc.core5.concurrent.FutureCallback;
|
||||
import org.apache.hc.core5.http.EntityDetails;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpException;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.ProtocolException;
|
||||
import org.apache.hc.core5.http.ProtocolVersion;
|
||||
import org.apache.hc.core5.http.message.RequestLine;
|
||||
import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler;
|
||||
import org.apache.hc.core5.http.nio.AsyncDataConsumer;
|
||||
|
@ -82,6 +86,7 @@ class HttpAsyncMainClientExec implements AsyncExecChainHandler {
|
|||
private final HttpProcessor httpProcessor;
|
||||
private final ConnectionKeepAliveStrategy keepAliveStrategy;
|
||||
private final UserTokenHandler userTokenHandler;
|
||||
private final ProtocolSwitchStrategy protocolSwitchStrategy;
|
||||
|
||||
HttpAsyncMainClientExec(final HttpProcessor httpProcessor,
|
||||
final ConnectionKeepAliveStrategy keepAliveStrategy,
|
||||
|
@ -89,6 +94,7 @@ class HttpAsyncMainClientExec implements AsyncExecChainHandler {
|
|||
this.httpProcessor = Args.notNull(httpProcessor, "HTTP protocol processor");
|
||||
this.keepAliveStrategy = keepAliveStrategy;
|
||||
this.userTokenHandler = userTokenHandler;
|
||||
this.protocolSwitchStrategy = new ProtocolSwitchStrategy();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -195,8 +201,36 @@ class HttpAsyncMainClientExec implements AsyncExecChainHandler {
|
|||
public void consumeInformation(
|
||||
final HttpResponse response,
|
||||
final HttpContext context) throws HttpException, IOException {
|
||||
if (response.getCode() == HttpStatus.SC_SWITCHING_PROTOCOLS) {
|
||||
final ProtocolVersion upgradeProtocol = protocolSwitchStrategy.switchProtocol(response);
|
||||
if (upgradeProtocol == null || !upgradeProtocol.getProtocol().equals("TLS")) {
|
||||
throw new ProtocolException("Failure switching protocols");
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Switching to {}", upgradeProtocol);
|
||||
}
|
||||
execRuntime.upgradeTls(clientContext, new FutureCallback<AsyncExecRuntime>() {
|
||||
|
||||
@Override
|
||||
public void completed(final AsyncExecRuntime result) {
|
||||
LOG.debug("Successfully switched to {}", upgradeProtocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception ex) {
|
||||
asyncExecCallback.failed(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
asyncExecCallback.failed(new InterruptedIOException());
|
||||
}
|
||||
|
||||
});
|
||||
} else {
|
||||
asyncExecCallback.handleInformationResponse(response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consumeResponse(
|
||||
|
|
|
@ -80,6 +80,7 @@ import org.apache.hc.client5.http.protocol.RequestAddCookies;
|
|||
import org.apache.hc.client5.http.protocol.RequestClientConnControl;
|
||||
import org.apache.hc.client5.http.protocol.RequestDefaultHeaders;
|
||||
import org.apache.hc.client5.http.protocol.RequestExpectContinue;
|
||||
import org.apache.hc.client5.http.protocol.RequestUpgrade;
|
||||
import org.apache.hc.client5.http.protocol.RequestValidateTrace;
|
||||
import org.apache.hc.client5.http.protocol.ResponseProcessCookies;
|
||||
import org.apache.hc.client5.http.routing.HttpRoutePlanner;
|
||||
|
@ -824,7 +825,8 @@ public class HttpClientBuilder {
|
|||
new RequestContent(),
|
||||
new RequestClientConnControl(),
|
||||
new RequestUserAgent(userAgentCopy),
|
||||
new RequestExpectContinue());
|
||||
new RequestExpectContinue(),
|
||||
new RequestUpgrade());
|
||||
if (!cookieManagementDisabled) {
|
||||
b.add(RequestAddCookies.INSTANCE);
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import org.apache.hc.client5.http.classic.ExecChain;
|
|||
import org.apache.hc.client5.http.classic.ExecChainHandler;
|
||||
import org.apache.hc.client5.http.classic.ExecRuntime;
|
||||
import org.apache.hc.client5.http.impl.ConnectionShutdownException;
|
||||
import org.apache.hc.client5.http.impl.ProtocolSwitchStrategy;
|
||||
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.core5.annotation.Contract;
|
||||
|
@ -47,6 +48,9 @@ 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.HttpStatus;
|
||||
import org.apache.hc.core5.http.ProtocolException;
|
||||
import org.apache.hc.core5.http.ProtocolVersion;
|
||||
import org.apache.hc.core5.http.message.RequestLine;
|
||||
import org.apache.hc.core5.http.protocol.HttpCoreContext;
|
||||
import org.apache.hc.core5.http.protocol.HttpProcessor;
|
||||
|
@ -74,6 +78,7 @@ public final class MainClientExec implements ExecChainHandler {
|
|||
private final ConnectionReuseStrategy reuseStrategy;
|
||||
private final ConnectionKeepAliveStrategy keepAliveStrategy;
|
||||
private final UserTokenHandler userTokenHandler;
|
||||
private final ProtocolSwitchStrategy protocolSwitchStrategy;
|
||||
|
||||
/**
|
||||
* @since 4.4
|
||||
|
@ -89,6 +94,7 @@ public final class MainClientExec implements ExecChainHandler {
|
|||
this.reuseStrategy = Args.notNull(reuseStrategy, "Connection reuse strategy");
|
||||
this.keepAliveStrategy = Args.notNull(keepAliveStrategy, "Connection keep alive strategy");
|
||||
this.userTokenHandler = Args.notNull(userTokenHandler, "User token handler");
|
||||
this.protocolSwitchStrategy = new ProtocolSwitchStrategy();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -113,7 +119,27 @@ public final class MainClientExec implements ExecChainHandler {
|
|||
|
||||
httpProcessor.process(request, request.getEntity(), context);
|
||||
|
||||
final ClassicHttpResponse response = execRuntime.execute(exchangeId, request, null, context);
|
||||
final ClassicHttpResponse response = execRuntime.execute(
|
||||
exchangeId,
|
||||
request,
|
||||
(r, connection, c) -> {
|
||||
if (r.getCode() == HttpStatus.SC_SWITCHING_PROTOCOLS) {
|
||||
final ProtocolVersion upgradeProtocol = protocolSwitchStrategy.switchProtocol(r);
|
||||
if (upgradeProtocol == null || !upgradeProtocol.getProtocol().equals("TLS")) {
|
||||
throw new ProtocolException("Failure switching protocols");
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Switching to {}", upgradeProtocol);
|
||||
}
|
||||
try {
|
||||
execRuntime.upgradeTls(context);
|
||||
} catch (final IOException ex) {
|
||||
throw new HttpException("Failure upgrading to TLS", ex);
|
||||
}
|
||||
LOG.debug("Successfully switched to {}", upgradeProtocol);
|
||||
}
|
||||
},
|
||||
context);
|
||||
|
||||
context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
|
||||
httpProcessor.process(response, response.getEntity(), context);
|
||||
|
|
|
@ -53,6 +53,7 @@ import org.apache.hc.core5.annotation.Internal;
|
|||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||
import org.apache.hc.core5.http.ConnectionClosedException;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.URIScheme;
|
||||
import org.apache.hc.core5.http.config.Lookup;
|
||||
import org.apache.hc.core5.http.io.SocketConfig;
|
||||
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||
|
@ -184,7 +185,7 @@ public class DefaultHttpClientConnectionOperator implements HttpClientConnection
|
|||
final Timeout soTimeout = socketConfig.getSoTimeout();
|
||||
final SocketAddress socksProxyAddress = socketConfig.getSocksProxyAddress();
|
||||
final Proxy socksProxy = socksProxyAddress != null ? new Proxy(Proxy.Type.SOCKS, socksProxyAddress) : null;
|
||||
final int port = this.schemePortResolver.resolve(host);
|
||||
final int port = this.schemePortResolver.resolve(host.getSchemeName(), host);
|
||||
for (int i = 0; i < remoteAddresses.length; i++) {
|
||||
final InetAddress address = remoteAddresses[i];
|
||||
final boolean last = i == remoteAddresses.length - 1;
|
||||
|
@ -269,13 +270,14 @@ public class DefaultHttpClientConnectionOperator implements HttpClientConnection
|
|||
if (socket == null) {
|
||||
throw new ConnectionClosedException("Connection is closed");
|
||||
}
|
||||
final TlsSocketStrategy tlsSocketStrategy = tlsSocketStrategyLookup != null ? tlsSocketStrategyLookup.lookup(host.getSchemeName()) : null;
|
||||
final String newProtocol = URIScheme.HTTP.same(host.getSchemeName()) ? URIScheme.HTTPS.id : host.getSchemeName();
|
||||
final TlsSocketStrategy tlsSocketStrategy = tlsSocketStrategyLookup != null ? tlsSocketStrategyLookup.lookup(newProtocol) : null;
|
||||
if (tlsSocketStrategy != null) {
|
||||
final int port = this.schemePortResolver.resolve(host);
|
||||
final int port = this.schemePortResolver.resolve(newProtocol, host);
|
||||
final SSLSocket upgradedSocket = tlsSocketStrategy.upgrade(socket, host.getHostName(), port, attachment, context);
|
||||
conn.bind(upgradedSocket);
|
||||
} else {
|
||||
throw new UnsupportedSchemeException(host.getSchemeName() + " protocol is not supported");
|
||||
throw new UnsupportedSchemeException(newProtocol + " protocol is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -505,10 +505,9 @@ public class PoolingHttpClientConnectionManager
|
|||
Args.notNull(endpoint, "Managed endpoint");
|
||||
final InternalConnectionEndpoint internalEndpoint = cast(endpoint);
|
||||
final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = internalEndpoint.getValidatedPoolEntry();
|
||||
final HttpRoute route = poolEntry.getRoute();
|
||||
final HttpHost host = route.getProxyHost() != null ? route.getProxyHost() : route.getTargetHost();
|
||||
final TlsConfig tlsConfig = resolveTlsConfig(host);
|
||||
this.connectionOperator.upgrade(poolEntry.getConnection(), route.getTargetHost(), tlsConfig, context);
|
||||
final HttpHost target = poolEntry.getRoute().getTargetHost();
|
||||
final TlsConfig tlsConfig = resolveTlsConfig(target);
|
||||
this.connectionOperator.upgrade(poolEntry.getConnection(), target, tlsConfig, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -45,6 +45,7 @@ import org.apache.hc.core5.concurrent.ComplexFuture;
|
|||
import org.apache.hc.core5.concurrent.FutureCallback;
|
||||
import org.apache.hc.core5.concurrent.FutureContribution;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.URIScheme;
|
||||
import org.apache.hc.core5.http.config.Lookup;
|
||||
import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
|
||||
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||
|
@ -174,11 +175,13 @@ final class DefaultAsyncClientConnectionOperator implements AsyncClientConnectio
|
|||
final Object attachment,
|
||||
final HttpContext context,
|
||||
final FutureCallback<ManagedAsyncClientConnection> callback) {
|
||||
final TlsStrategy tlsStrategy = tlsStrategyLookup != null ? tlsStrategyLookup.lookup(host.getSchemeName()) : null;
|
||||
final String newProtocol = URIScheme.HTTP.same(host.getSchemeName()) ? URIScheme.HTTPS.id : host.getSchemeName();
|
||||
final HttpHost remoteEndpoint = RoutingSupport.normalize(host, schemePortResolver);
|
||||
final TlsStrategy tlsStrategy = tlsStrategyLookup != null ? tlsStrategyLookup.lookup(newProtocol) : null;
|
||||
if (tlsStrategy != null) {
|
||||
tlsStrategy.upgrade(
|
||||
connection,
|
||||
host,
|
||||
remoteEndpoint,
|
||||
attachment,
|
||||
null,
|
||||
new CallbackContribution<TransportSecurityLayer>(callback) {
|
||||
|
@ -192,7 +195,7 @@ final class DefaultAsyncClientConnectionOperator implements AsyncClientConnectio
|
|||
|
||||
});
|
||||
} else {
|
||||
callback.failed(new UnsupportedSchemeException(host.getSchemeName() + " protocol is not supported"));
|
||||
callback.failed(new UnsupportedSchemeException(newProtocol + " protocol is not supported"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -245,13 +245,21 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio
|
|||
return connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT;
|
||||
}
|
||||
|
||||
private TlsConfig resolveTlsConfig(final HttpHost host, final Object attachment) {
|
||||
if (attachment instanceof TlsConfig) {
|
||||
return (TlsConfig) attachment;
|
||||
}
|
||||
private TlsConfig resolveTlsConfig(final HttpHost host) {
|
||||
final Resolver<HttpHost, TlsConfig> resolver = this.tlsConfigResolver;
|
||||
final TlsConfig tlsConfig = resolver != null ? resolver.resolve(host) : null;
|
||||
return tlsConfig != null ? tlsConfig : TlsConfig.DEFAULT;
|
||||
TlsConfig tlsConfig = resolver != null ? resolver.resolve(host) : null;
|
||||
if (tlsConfig == null) {
|
||||
tlsConfig = TlsConfig.DEFAULT;
|
||||
}
|
||||
if (URIScheme.HTTP.same(host.getSchemeName())
|
||||
&& tlsConfig.getHttpVersionPolicy() == HttpVersionPolicy.NEGOTIATE) {
|
||||
// Plain HTTP does not support protocol negotiation.
|
||||
// Fall back to HTTP/1.1
|
||||
tlsConfig = TlsConfig.copy(tlsConfig)
|
||||
.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)
|
||||
.build();
|
||||
}
|
||||
return tlsConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -441,7 +449,6 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio
|
|||
}
|
||||
final InetSocketAddress localAddress = route.getLocalSocketAddress();
|
||||
final ConnectionConfig connectionConfig = resolveConnectionConfig(route);
|
||||
final TlsConfig tlsConfig = resolveTlsConfig(host, attachment);
|
||||
final Timeout connectTimeout = timeout != null ? timeout : connectionConfig.getConnectTimeout();
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
|
@ -452,9 +459,7 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio
|
|||
host,
|
||||
localAddress,
|
||||
connectTimeout,
|
||||
route.isTunnelled() ? TlsConfig.copy(tlsConfig)
|
||||
.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)
|
||||
.build() : tlsConfig,
|
||||
route.isTunnelled() ? null : resolveTlsConfig(host),
|
||||
context,
|
||||
new FutureCallback<ManagedAsyncClientConnection>() {
|
||||
|
||||
|
@ -499,12 +504,11 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio
|
|||
final InternalConnectionEndpoint internalEndpoint = cast(endpoint);
|
||||
final PoolEntry<HttpRoute, ManagedAsyncClientConnection> poolEntry = internalEndpoint.getValidatedPoolEntry();
|
||||
final HttpRoute route = poolEntry.getRoute();
|
||||
final HttpHost host = route.getProxyHost() != null ? route.getProxyHost() : route.getTargetHost();
|
||||
final TlsConfig tlsConfig = resolveTlsConfig(host, attachment);
|
||||
final HttpHost target = route.getTargetHost();
|
||||
connectionOperator.upgrade(
|
||||
poolEntry.getConnection(),
|
||||
route.getTargetHost(),
|
||||
attachment != null ? attachment : tlsConfig,
|
||||
target,
|
||||
attachment != null ? attachment : resolveTlsConfig(target),
|
||||
context,
|
||||
new CallbackContribution<ManagedAsyncClientConnection>(callback) {
|
||||
|
||||
|
|
|
@ -66,11 +66,11 @@ public class RequestExpectContinue implements HttpRequestInterceptor {
|
|||
Args.notNull(request, "HTTP request");
|
||||
|
||||
if (!request.containsHeader(HttpHeaders.EXPECT)) {
|
||||
final ProtocolVersion version = request.getVersion() != null ? request.getVersion() : HttpVersion.HTTP_1_1;
|
||||
final HttpClientContext clientContext = HttpClientContext.adapt(context);
|
||||
final ProtocolVersion version = request.getVersion() != null ? request.getVersion() : clientContext.getProtocolVersion();
|
||||
// Do not send the expect header if request body is known to be empty
|
||||
if (entity != null
|
||||
&& entity.getContentLength() != 0 && !version.lessEquals(HttpVersion.HTTP_1_0)) {
|
||||
final HttpClientContext clientContext = HttpClientContext.adapt(context);
|
||||
final RequestConfig config = clientContext.getRequestConfig();
|
||||
if (config.isExpectContinueEnabled()) {
|
||||
request.addHeader(HttpHeaders.EXPECT, HeaderElements.CONTINUE);
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.protocol;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.core5.annotation.Contract;
|
||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||
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.HttpRequest;
|
||||
import org.apache.hc.core5.http.HttpRequestInterceptor;
|
||||
import org.apache.hc.core5.http.Method;
|
||||
import org.apache.hc.core5.http.ProtocolVersion;
|
||||
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||
import org.apache.hc.core5.util.Args;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @since 5.4
|
||||
*/
|
||||
@Contract(threading = ThreadingBehavior.STATELESS)
|
||||
public final class RequestUpgrade implements HttpRequestInterceptor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RequestUpgrade.class);
|
||||
|
||||
public RequestUpgrade() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(
|
||||
final HttpRequest request,
|
||||
final EntityDetails entity,
|
||||
final HttpContext context) throws HttpException, IOException {
|
||||
Args.notNull(request, "HTTP request");
|
||||
Args.notNull(context, "HTTP context");
|
||||
|
||||
final HttpClientContext clientContext = HttpClientContext.adapt(context);
|
||||
final RequestConfig requestConfig = clientContext.getRequestConfig();
|
||||
if (requestConfig.isProtocolUpgradeEnabled()) {
|
||||
final ProtocolVersion version = request.getVersion() != null ? request.getVersion() : clientContext.getProtocolVersion();
|
||||
if (!request.containsHeader(HttpHeaders.UPGRADE) && version.getMajor() == 1 && version.getMinor() >= 1) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Connection is upgradable: protocol version = {}", version);
|
||||
}
|
||||
final String method = request.getMethod();
|
||||
if ((Method.OPTIONS.isSame(method) || Method.HEAD.isSame(method) || Method.GET.isSame(method)) &&
|
||||
clientContext.getSSLSession() == null) {
|
||||
LOG.debug("Connection is upgradable to TLS: method = {}", method);
|
||||
request.addHeader(HttpHeaders.UPGRADE, "TLS/1.2");
|
||||
request.addHeader(HttpHeaders.CONNECTION, HttpHeaders.UPGRADE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl;
|
||||
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.ProtocolException;
|
||||
import org.apache.hc.core5.http.message.BasicHttpResponse;
|
||||
import org.apache.hc.core5.http.ssl.TLS;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Simple tests for {@link DefaultAuthenticationStrategy}.
|
||||
*/
|
||||
public class TestProtocolSwitchStrategy {
|
||||
|
||||
ProtocolSwitchStrategy switchStrategy;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
switchStrategy = new ProtocolSwitchStrategy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwitchToTLS() throws Exception {
|
||||
final HttpResponse response1 = new BasicHttpResponse(HttpStatus.SC_SWITCHING_PROTOCOLS);
|
||||
response1.addHeader(HttpHeaders.UPGRADE, "TLS");
|
||||
Assertions.assertEquals(TLS.V_1_2.getVersion(), switchStrategy.switchProtocol(response1));
|
||||
|
||||
final HttpResponse response2 = new BasicHttpResponse(HttpStatus.SC_SWITCHING_PROTOCOLS);
|
||||
response2.addHeader(HttpHeaders.UPGRADE, "TLS/1.3");
|
||||
Assertions.assertEquals(TLS.V_1_3.getVersion(), switchStrategy.switchProtocol(response2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwitchToHTTP11AndTLS() throws Exception {
|
||||
final HttpResponse response1 = new BasicHttpResponse(HttpStatus.SC_SWITCHING_PROTOCOLS);
|
||||
response1.addHeader(HttpHeaders.UPGRADE, "TLS, HTTP/1.1");
|
||||
Assertions.assertEquals(TLS.V_1_2.getVersion(), switchStrategy.switchProtocol(response1));
|
||||
|
||||
final HttpResponse response2 = new BasicHttpResponse(HttpStatus.SC_SWITCHING_PROTOCOLS);
|
||||
response2.addHeader(HttpHeaders.UPGRADE, ",, HTTP/1.1, TLS, ");
|
||||
Assertions.assertEquals(TLS.V_1_2.getVersion(), switchStrategy.switchProtocol(response2));
|
||||
|
||||
final HttpResponse response3 = new BasicHttpResponse(HttpStatus.SC_SWITCHING_PROTOCOLS);
|
||||
response3.addHeader(HttpHeaders.UPGRADE, "HTTP/1.1");
|
||||
response3.addHeader(HttpHeaders.UPGRADE, "TLS/1.2");
|
||||
Assertions.assertEquals(TLS.V_1_2.getVersion(), switchStrategy.switchProtocol(response3));
|
||||
|
||||
final HttpResponse response4 = new BasicHttpResponse(HttpStatus.SC_SWITCHING_PROTOCOLS);
|
||||
response4.addHeader(HttpHeaders.UPGRADE, "HTTP/1.1");
|
||||
response4.addHeader(HttpHeaders.UPGRADE, "TLS/1.2, TLS/1.3");
|
||||
Assertions.assertEquals(TLS.V_1_3.getVersion(), switchStrategy.switchProtocol(response4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwitchInvalid() throws Exception {
|
||||
final HttpResponse response1 = new BasicHttpResponse(HttpStatus.SC_SWITCHING_PROTOCOLS);
|
||||
response1.addHeader(HttpHeaders.UPGRADE, "Crap");
|
||||
Assertions.assertThrows(ProtocolException.class, () -> switchStrategy.switchProtocol(response1));
|
||||
|
||||
final HttpResponse response2 = new BasicHttpResponse(HttpStatus.SC_SWITCHING_PROTOCOLS);
|
||||
response2.addHeader(HttpHeaders.UPGRADE, "TLS, huh?");
|
||||
Assertions.assertThrows(ProtocolException.class, () -> switchStrategy.switchProtocol(response2));
|
||||
|
||||
final HttpResponse response3 = new BasicHttpResponse(HttpStatus.SC_SWITCHING_PROTOCOLS);
|
||||
response3.addHeader(HttpHeaders.UPGRADE, ",,,");
|
||||
Assertions.assertThrows(ProtocolException.class, () -> switchStrategy.switchProtocol(response3));
|
||||
}
|
||||
|
||||
}
|
|
@ -56,7 +56,6 @@ import org.mockito.Mock;
|
|||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@SuppressWarnings({"boxing","static-access"}) // test code
|
||||
public class TestMainClientExec {
|
||||
|
||||
@Mock
|
||||
|
@ -104,7 +103,7 @@ public class TestMainClientExec {
|
|||
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
|
||||
|
||||
Mockito.verify(httpProcessor).process(request, null, context);
|
||||
Mockito.verify(execRuntime).execute("test", request, null, context);
|
||||
Mockito.verify(execRuntime).execute(Mockito.eq("test"), Mockito.same(request), Mockito.any(), Mockito.any());
|
||||
Mockito.verify(httpProcessor).process(response, responseEntity, context);
|
||||
|
||||
Assertions.assertEquals(route, context.getHttpRoute());
|
||||
|
@ -134,7 +133,7 @@ public class TestMainClientExec {
|
|||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
|
||||
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
|
||||
Mockito.verify(execRuntime).execute("test", request, null, context);
|
||||
Mockito.verify(execRuntime).execute(Mockito.eq("test"), Mockito.same(request), Mockito.any(), Mockito.any());
|
||||
Mockito.verify(execRuntime, Mockito.times(1)).markConnectionNonReusable();
|
||||
Mockito.verify(execRuntime, Mockito.never()).releaseEndpoint();
|
||||
|
||||
|
@ -164,7 +163,7 @@ public class TestMainClientExec {
|
|||
final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
|
||||
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
|
||||
|
||||
Mockito.verify(execRuntime).execute("test", request, null, context);
|
||||
Mockito.verify(execRuntime).execute(Mockito.eq("test"), Mockito.same(request), Mockito.any(), Mockito.any());
|
||||
Mockito.verify(execRuntime).markConnectionNonReusable();
|
||||
Mockito.verify(execRuntime).releaseEndpoint();
|
||||
|
||||
|
@ -200,7 +199,7 @@ public class TestMainClientExec {
|
|||
final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
|
||||
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
|
||||
|
||||
Mockito.verify(execRuntime).execute("test", request, null, context);
|
||||
Mockito.verify(execRuntime).execute(Mockito.eq("test"), Mockito.same(request), Mockito.any(), Mockito.any());
|
||||
Mockito.verify(execRuntime).markConnectionReusable(null, TimeValue.ofMilliseconds(678L));
|
||||
Mockito.verify(execRuntime, Mockito.never()).releaseEndpoint();
|
||||
|
||||
|
@ -231,7 +230,7 @@ public class TestMainClientExec {
|
|||
final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
|
||||
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
|
||||
|
||||
Mockito.verify(execRuntime).execute("test", request, null, context);
|
||||
Mockito.verify(execRuntime).execute(Mockito.eq("test"), Mockito.same(request), Mockito.any(), Mockito.any());
|
||||
Mockito.verify(execRuntime).releaseEndpoint();
|
||||
|
||||
Assertions.assertNotNull(finalResponse);
|
||||
|
@ -261,7 +260,7 @@ public class TestMainClientExec {
|
|||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
|
||||
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
|
||||
Mockito.verify(execRuntime, Mockito.times(1)).execute("test", request, null, context);
|
||||
Mockito.verify(execRuntime, Mockito.times(1)).execute(Mockito.eq("test"), Mockito.same(request), Mockito.any(), Mockito.any());
|
||||
Mockito.verify(execRuntime, Mockito.never()).disconnectEndpoint();
|
||||
Mockito.verify(execRuntime, Mockito.never()).releaseEndpoint();
|
||||
|
||||
|
|
|
@ -385,7 +385,7 @@ public class TestBasicHttpClientConnectionManager {
|
|||
mgr.setTlsConfig(tlsConfig);
|
||||
|
||||
Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[] {remote});
|
||||
Mockito.when(schemePortResolver.resolve(target)).thenReturn(8443);
|
||||
Mockito.when(schemePortResolver.resolve(target.getSchemeName(), target)).thenReturn(8443);
|
||||
Mockito.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
|
||||
|
||||
Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
|
||||
|
@ -399,7 +399,7 @@ public class TestBasicHttpClientConnectionManager {
|
|||
mgr.connect(endpoint1, null, context);
|
||||
|
||||
Mockito.verify(dnsResolver, Mockito.times(1)).resolve("somehost");
|
||||
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target);
|
||||
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target.getSchemeName(), target);
|
||||
Mockito.verify(detachedSocketFactory, Mockito.times(1)).create(null);
|
||||
Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 234);
|
||||
Mockito.verify(tlsSocketStrategy).upgrade(socket, "somehost", 8443, tlsConfig, context);
|
||||
|
@ -407,7 +407,7 @@ public class TestBasicHttpClientConnectionManager {
|
|||
mgr.connect(endpoint1, TimeValue.ofMilliseconds(123), context);
|
||||
|
||||
Mockito.verify(dnsResolver, Mockito.times(2)).resolve("somehost");
|
||||
Mockito.verify(schemePortResolver, Mockito.times(2)).resolve(target);
|
||||
Mockito.verify(schemePortResolver, Mockito.times(2)).resolve(target.getSchemeName(), target);
|
||||
Mockito.verify(detachedSocketFactory, Mockito.times(2)).create(null);
|
||||
Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 123);
|
||||
Mockito.verify(tlsSocketStrategy, Mockito.times(2)).upgrade(socket, "somehost", 8443, tlsConfig, context);
|
||||
|
@ -442,15 +442,15 @@ public class TestBasicHttpClientConnectionManager {
|
|||
mgr.setTlsConfig(tlsConfig);
|
||||
|
||||
Mockito.when(dnsResolver.resolve("someproxy")).thenReturn(new InetAddress[] {remote});
|
||||
Mockito.when(schemePortResolver.resolve(proxy)).thenReturn(8080);
|
||||
Mockito.when(schemePortResolver.resolve(target)).thenReturn(8443);
|
||||
Mockito.when(schemePortResolver.resolve(proxy.getSchemeName(), proxy)).thenReturn(8080);
|
||||
Mockito.when(schemePortResolver.resolve(target.getSchemeName(), target)).thenReturn(8443);
|
||||
Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
|
||||
Mockito.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
|
||||
|
||||
mgr.connect(endpoint1, null, context);
|
||||
|
||||
Mockito.verify(dnsResolver, Mockito.times(1)).resolve("someproxy");
|
||||
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(proxy);
|
||||
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(proxy.getSchemeName(), proxy);
|
||||
Mockito.verify(detachedSocketFactory, Mockito.times(1)).create(null);
|
||||
Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8080), 234);
|
||||
|
||||
|
@ -458,7 +458,7 @@ public class TestBasicHttpClientConnectionManager {
|
|||
|
||||
mgr.upgrade(endpoint1, context);
|
||||
|
||||
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target);
|
||||
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target.getSchemeName(), target);
|
||||
Mockito.verify(tlsSocketStrategy, Mockito.times(1)).upgrade(
|
||||
socket, "somehost", 8443, tlsConfig, context);
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ public class TestHttpClientConnectionOperator {
|
|||
final InetAddress ip2 = InetAddress.getByAddress(new byte[] {127, 0, 0, 2});
|
||||
|
||||
Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[] { ip1, ip2 });
|
||||
Mockito.when(schemePortResolver.resolve(host)).thenReturn(80);
|
||||
Mockito.when(schemePortResolver.resolve(host.getSchemeName(), host)).thenReturn(80);
|
||||
Mockito.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
|
||||
|
||||
final SocketConfig socketConfig = SocketConfig.custom()
|
||||
|
@ -129,7 +129,7 @@ public class TestHttpClientConnectionOperator {
|
|||
.build();
|
||||
|
||||
Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[] { ip1, ip2 });
|
||||
Mockito.when(schemePortResolver.resolve(host)).thenReturn(443);
|
||||
Mockito.when(schemePortResolver.resolve(host.getSchemeName(), host)).thenReturn(443);
|
||||
Mockito.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
|
||||
|
||||
Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
|
||||
|
@ -194,7 +194,7 @@ public class TestHttpClientConnectionOperator {
|
|||
final InetAddress ip2 = InetAddress.getByAddress(new byte[] {10, 0, 0, 2});
|
||||
|
||||
Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[] { ip1, ip2 });
|
||||
Mockito.when(schemePortResolver.resolve(host)).thenReturn(80);
|
||||
Mockito.when(schemePortResolver.resolve(host.getSchemeName(), host)).thenReturn(80);
|
||||
Mockito.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
|
||||
Mockito.doThrow(new ConnectException()).when(socket).connect(
|
||||
Mockito.eq(new InetSocketAddress(ip1, 80)),
|
||||
|
@ -219,7 +219,7 @@ public class TestHttpClientConnectionOperator {
|
|||
final InetAddress ip = InetAddress.getByAddress(new byte[] {127, 0, 0, 23});
|
||||
final HttpHost host = new HttpHost(ip);
|
||||
|
||||
Mockito.when(schemePortResolver.resolve(host)).thenReturn(80);
|
||||
Mockito.when(schemePortResolver.resolve(host.getSchemeName(), host)).thenReturn(80);
|
||||
Mockito.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
|
||||
|
||||
final InetSocketAddress localAddress = new InetSocketAddress(local, 0);
|
||||
|
@ -242,7 +242,7 @@ public class TestHttpClientConnectionOperator {
|
|||
Mockito.when(conn.isOpen()).thenReturn(true);
|
||||
Mockito.when(conn.getSocket()).thenReturn(socket);
|
||||
Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
|
||||
Mockito.when(schemePortResolver.resolve(host)).thenReturn(443);
|
||||
Mockito.when(schemePortResolver.resolve(host.getSchemeName(), host)).thenReturn(443);
|
||||
|
||||
final SSLSocket upgradedSocket = Mockito.mock(SSLSocket.class);
|
||||
Mockito.when(tlsSocketStrategy.upgrade(
|
||||
|
|
|
@ -263,7 +263,7 @@ public class TestPoolingHttpClientConnectionManager {
|
|||
mgr.setDefaultTlsConfig(tlsConfig);
|
||||
|
||||
Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[]{remote});
|
||||
Mockito.when(schemePortResolver.resolve(target)).thenReturn(8443);
|
||||
Mockito.when(schemePortResolver.resolve(target.getSchemeName(), target)).thenReturn(8443);
|
||||
Mockito.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
|
||||
|
||||
Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
|
||||
|
@ -277,7 +277,7 @@ public class TestPoolingHttpClientConnectionManager {
|
|||
mgr.connect(endpoint1, null, context);
|
||||
|
||||
Mockito.verify(dnsResolver, Mockito.times(1)).resolve("somehost");
|
||||
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target);
|
||||
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target.getSchemeName(), target);
|
||||
Mockito.verify(detachedSocketFactory, Mockito.times(1)).create(null);
|
||||
Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 234);
|
||||
Mockito.verify(tlsSocketStrategy).upgrade(socket, "somehost", 8443, tlsConfig, context);
|
||||
|
@ -285,7 +285,7 @@ public class TestPoolingHttpClientConnectionManager {
|
|||
mgr.connect(endpoint1, TimeValue.ofMilliseconds(123), context);
|
||||
|
||||
Mockito.verify(dnsResolver, Mockito.times(2)).resolve("somehost");
|
||||
Mockito.verify(schemePortResolver, Mockito.times(2)).resolve(target);
|
||||
Mockito.verify(schemePortResolver, Mockito.times(2)).resolve(target.getSchemeName(), target);
|
||||
Mockito.verify(detachedSocketFactory, Mockito.times(2)).create(null);
|
||||
Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 123);
|
||||
Mockito.verify(tlsSocketStrategy, Mockito.times(2)).upgrade(socket, "somehost", 8443, tlsConfig, context);
|
||||
|
@ -330,15 +330,15 @@ public class TestPoolingHttpClientConnectionManager {
|
|||
mgr.setDefaultTlsConfig(tlsConfig);
|
||||
|
||||
Mockito.when(dnsResolver.resolve("someproxy")).thenReturn(new InetAddress[] {remote});
|
||||
Mockito.when(schemePortResolver.resolve(proxy)).thenReturn(8080);
|
||||
Mockito.when(schemePortResolver.resolve(target)).thenReturn(8443);
|
||||
Mockito.when(schemePortResolver.resolve(proxy.getSchemeName(), proxy)).thenReturn(8080);
|
||||
Mockito.when(schemePortResolver.resolve(target.getSchemeName(), target)).thenReturn(8443);
|
||||
Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
|
||||
Mockito.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
|
||||
|
||||
mgr.connect(endpoint1, null, context);
|
||||
|
||||
Mockito.verify(dnsResolver, Mockito.times(1)).resolve("someproxy");
|
||||
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(proxy);
|
||||
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(proxy.getSchemeName(), proxy);
|
||||
Mockito.verify(detachedSocketFactory, Mockito.times(1)).create(null);
|
||||
Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8080), 234);
|
||||
|
||||
|
@ -347,7 +347,7 @@ public class TestPoolingHttpClientConnectionManager {
|
|||
|
||||
mgr.upgrade(endpoint1, context);
|
||||
|
||||
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target);
|
||||
Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target.getSchemeName(), target);
|
||||
Mockito.verify(tlsSocketStrategy, Mockito.times(1)).upgrade(
|
||||
socket, "somehost", 8443, tlsConfig, context);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.protocol;
|
||||
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
import org.apache.hc.client5.http.HeadersMatcher;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.HttpVersion;
|
||||
import org.apache.hc.core5.http.message.BasicHeader;
|
||||
import org.apache.hc.core5.http.message.BasicHttpRequest;
|
||||
import org.apache.hc.core5.http.protocol.HttpCoreContext;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
class TestRequestUpgrade {
|
||||
|
||||
private RequestUpgrade interceptor;
|
||||
private HttpClientContext context;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
interceptor = new RequestUpgrade();
|
||||
context = HttpClientContext.create();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpgrade() throws Exception {
|
||||
final HttpRequest get = new BasicHttpRequest("GET", "/");
|
||||
interceptor.process(get, null, context);
|
||||
MatcherAssert.assertThat(get.getHeaders(),
|
||||
HeadersMatcher.same(
|
||||
new BasicHeader(HttpHeaders.UPGRADE, "TLS/1.2"),
|
||||
new BasicHeader(HttpHeaders.CONNECTION, HttpHeaders.UPGRADE)));
|
||||
final HttpRequest options = new BasicHttpRequest("OPTIONS", "/");
|
||||
interceptor.process(options, null, context);
|
||||
MatcherAssert.assertThat(options.getHeaders(),
|
||||
HeadersMatcher.same(
|
||||
new BasicHeader(HttpHeaders.UPGRADE, "TLS/1.2"),
|
||||
new BasicHeader(HttpHeaders.CONNECTION, HttpHeaders.UPGRADE)));
|
||||
final HttpRequest head = new BasicHttpRequest("HEAD", "/");
|
||||
interceptor.process(head, null, context);
|
||||
MatcherAssert.assertThat(head.getHeaders(),
|
||||
HeadersMatcher.same(
|
||||
new BasicHeader(HttpHeaders.UPGRADE, "TLS/1.2"),
|
||||
new BasicHeader(HttpHeaders.CONNECTION, HttpHeaders.UPGRADE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpgradeDisabled() throws Exception {
|
||||
context.setRequestConfig(RequestConfig.custom()
|
||||
.setProtocolUpgradeEnabled(false)
|
||||
.build());
|
||||
final HttpRequest get = new BasicHttpRequest("GET", "/");
|
||||
interceptor.process(get, null, context);
|
||||
Assertions.assertFalse(get.containsHeader(HttpHeaders.UPGRADE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoNotUpgradeHTTP2() throws Exception {
|
||||
context.setProtocolVersion(HttpVersion.HTTP_2);
|
||||
final HttpRequest get = new BasicHttpRequest("GET", "/");
|
||||
interceptor.process(get, null, context);
|
||||
Assertions.assertFalse(get.containsHeader(HttpHeaders.UPGRADE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoNotUpgradeHTTP10() throws Exception {
|
||||
context.setProtocolVersion(HttpVersion.HTTP_1_0);
|
||||
final HttpRequest get = new BasicHttpRequest("GET", "/");
|
||||
interceptor.process(get, null, context);
|
||||
Assertions.assertFalse(get.containsHeader(HttpHeaders.UPGRADE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoUpgradeIfAlreadyTLS() throws Exception {
|
||||
context.setAttribute(HttpCoreContext.SSL_SESSION, Mockito.mock(SSLSession.class));
|
||||
final HttpRequest get = new BasicHttpRequest("GET", "/");
|
||||
interceptor.process(get, null, context);
|
||||
Assertions.assertFalse(get.containsHeader(HttpHeaders.UPGRADE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoUpgradeNonSafeMethodsOrTrace() throws Exception {
|
||||
final HttpRequest post = new BasicHttpRequest("POST", "/");
|
||||
interceptor.process(post, null, context);
|
||||
Assertions.assertFalse(post.containsHeader(HttpHeaders.UPGRADE));
|
||||
|
||||
final HttpRequest put = new BasicHttpRequest("PUT", "/");
|
||||
interceptor.process(put, null, context);
|
||||
Assertions.assertFalse(put.containsHeader(HttpHeaders.UPGRADE));
|
||||
|
||||
final HttpRequest patch = new BasicHttpRequest("PATCH", "/");
|
||||
interceptor.process(patch, null, context);
|
||||
Assertions.assertFalse(patch.containsHeader(HttpHeaders.UPGRADE));
|
||||
|
||||
final HttpRequest trace = new BasicHttpRequest("TRACE", "/");
|
||||
interceptor.process(trace, null, context);
|
||||
Assertions.assertFalse(trace.containsHeader(HttpHeaders.UPGRADE));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue