HTTPCLIENT-751: Support for RFC 2817 (Upgrading to TLS Within HTTP/1.1)

This commit is contained in:
Oleg Kalnichevski 2024-01-25 15:10:01 +01:00
parent 1111c62dd3
commit 3235f009d5
18 changed files with 554 additions and 61 deletions

View File

@ -62,13 +62,14 @@ public class RequestConfig implements Cloneable {
private final TimeValue connectionKeepAlive; private final TimeValue connectionKeepAlive;
private final boolean contentCompressionEnabled; private final boolean contentCompressionEnabled;
private final boolean hardCancellationEnabled; private final boolean hardCancellationEnabled;
private final boolean protocolUpgradeEnabled;
/** /**
* Intended for CDI compatibility * Intended for CDI compatibility
*/ */
protected RequestConfig() { protected RequestConfig() {
this(false, null, null, false, false, 0, false, null, null, 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( RequestConfig(
@ -86,7 +87,8 @@ public class RequestConfig implements Cloneable {
final Timeout responseTimeout, final Timeout responseTimeout,
final TimeValue connectionKeepAlive, final TimeValue connectionKeepAlive,
final boolean contentCompressionEnabled, final boolean contentCompressionEnabled,
final boolean hardCancellationEnabled) { final boolean hardCancellationEnabled,
final boolean protocolUpgradeEnabled) {
super(); super();
this.expectContinueEnabled = expectContinueEnabled; this.expectContinueEnabled = expectContinueEnabled;
this.proxy = proxy; this.proxy = proxy;
@ -103,6 +105,7 @@ public class RequestConfig implements Cloneable {
this.connectionKeepAlive = connectionKeepAlive; this.connectionKeepAlive = connectionKeepAlive;
this.contentCompressionEnabled = contentCompressionEnabled; this.contentCompressionEnabled = contentCompressionEnabled;
this.hardCancellationEnabled = hardCancellationEnabled; this.hardCancellationEnabled = hardCancellationEnabled;
this.protocolUpgradeEnabled = protocolUpgradeEnabled;
} }
/** /**
@ -217,6 +220,13 @@ public class RequestConfig implements Cloneable {
return hardCancellationEnabled; return hardCancellationEnabled;
} }
/**
* @see Builder#setProtocolUpgradeEnabled(boolean) (boolean)
*/
public boolean isProtocolUpgradeEnabled() {
return protocolUpgradeEnabled;
}
@Override @Override
protected RequestConfig clone() throws CloneNotSupportedException { protected RequestConfig clone() throws CloneNotSupportedException {
return (RequestConfig) super.clone(); return (RequestConfig) super.clone();
@ -241,6 +251,7 @@ public class RequestConfig implements Cloneable {
builder.append(", connectionKeepAlive=").append(connectionKeepAlive); builder.append(", connectionKeepAlive=").append(connectionKeepAlive);
builder.append(", contentCompressionEnabled=").append(contentCompressionEnabled); builder.append(", contentCompressionEnabled=").append(contentCompressionEnabled);
builder.append(", hardCancellationEnabled=").append(hardCancellationEnabled); builder.append(", hardCancellationEnabled=").append(hardCancellationEnabled);
builder.append(", protocolUpgradeEnabled=").append(protocolUpgradeEnabled);
builder.append("]"); builder.append("]");
return builder.toString(); return builder.toString();
} }
@ -265,7 +276,8 @@ public class RequestConfig implements Cloneable {
.setResponseTimeout(config.getResponseTimeout()) .setResponseTimeout(config.getResponseTimeout())
.setConnectionKeepAlive(config.getConnectionKeepAlive()) .setConnectionKeepAlive(config.getConnectionKeepAlive())
.setContentCompressionEnabled(config.isContentCompressionEnabled()) .setContentCompressionEnabled(config.isContentCompressionEnabled())
.setHardCancellationEnabled(config.isHardCancellationEnabled()); .setHardCancellationEnabled(config.isHardCancellationEnabled())
.setProtocolUpgradeEnabled(config.isProtocolUpgradeEnabled());
} }
public static class Builder { public static class Builder {
@ -285,6 +297,7 @@ public class RequestConfig implements Cloneable {
private TimeValue connectionKeepAlive; private TimeValue connectionKeepAlive;
private boolean contentCompressionEnabled; private boolean contentCompressionEnabled;
private boolean hardCancellationEnabled; private boolean hardCancellationEnabled;
private boolean protocolUpgradeEnabled;
Builder() { Builder() {
super(); super();
@ -294,6 +307,7 @@ public class RequestConfig implements Cloneable {
this.connectionRequestTimeout = DEFAULT_CONNECTION_REQUEST_TIMEOUT; this.connectionRequestTimeout = DEFAULT_CONNECTION_REQUEST_TIMEOUT;
this.contentCompressionEnabled = true; this.contentCompressionEnabled = true;
this.hardCancellationEnabled = true; this.hardCancellationEnabled = true;
this.protocolUpgradeEnabled = true;
} }
/** /**
@ -570,6 +584,23 @@ public class RequestConfig implements Cloneable {
return this; 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() { public RequestConfig build() {
return new RequestConfig( return new RequestConfig(
expectContinueEnabled, expectContinueEnabled,
@ -586,7 +617,8 @@ public class RequestConfig implements Cloneable {
responseTimeout, responseTimeout,
connectionKeepAlive != null ? connectionKeepAlive : DEFAULT_CONN_KEEP_ALIVE, connectionKeepAlive != null ? connectionKeepAlive : DEFAULT_CONN_KEEP_ALIVE,
contentCompressionEnabled, contentCompressionEnabled,
hardCancellationEnabled); hardCancellationEnabled,
protocolUpgradeEnabled);
} }
} }

View File

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

View File

@ -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.RequestAddCookies;
import org.apache.hc.client5.http.protocol.RequestDefaultHeaders; import org.apache.hc.client5.http.protocol.RequestDefaultHeaders;
import org.apache.hc.client5.http.protocol.RequestExpectContinue; 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.RequestValidateTrace;
import org.apache.hc.client5.http.protocol.ResponseProcessCookies; import org.apache.hc.client5.http.protocol.ResponseProcessCookies;
import org.apache.hc.client5.http.routing.HttpRoutePlanner; import org.apache.hc.client5.http.routing.HttpRoutePlanner;
@ -843,7 +844,8 @@ public class HttpAsyncClientBuilder {
new H2RequestContent(), new H2RequestContent(),
new H2RequestConnControl(), new H2RequestConnControl(),
new RequestUserAgent(userAgentCopy), new RequestUserAgent(userAgentCopy),
new RequestExpectContinue()); new RequestExpectContinue(),
new RequestUpgrade());
if (!cookieManagementDisabled) { if (!cookieManagementDisabled) {
b.add(RequestAddCookies.INSTANCE); b.add(RequestAddCookies.INSTANCE);
} }

View File

@ -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.AsyncExecChain;
import org.apache.hc.client5.http.async.AsyncExecChainHandler; import org.apache.hc.client5.http.async.AsyncExecChainHandler;
import org.apache.hc.client5.http.async.AsyncExecRuntime; 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.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.concurrent.CancellableDependency; 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.EntityDetails;
import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus; 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.message.RequestLine;
import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler;
import org.apache.hc.core5.http.nio.AsyncDataConsumer; import org.apache.hc.core5.http.nio.AsyncDataConsumer;
@ -82,6 +86,7 @@ class HttpAsyncMainClientExec implements AsyncExecChainHandler {
private final HttpProcessor httpProcessor; private final HttpProcessor httpProcessor;
private final ConnectionKeepAliveStrategy keepAliveStrategy; private final ConnectionKeepAliveStrategy keepAliveStrategy;
private final UserTokenHandler userTokenHandler; private final UserTokenHandler userTokenHandler;
private final ProtocolSwitchStrategy protocolSwitchStrategy;
HttpAsyncMainClientExec(final HttpProcessor httpProcessor, HttpAsyncMainClientExec(final HttpProcessor httpProcessor,
final ConnectionKeepAliveStrategy keepAliveStrategy, final ConnectionKeepAliveStrategy keepAliveStrategy,
@ -89,6 +94,7 @@ class HttpAsyncMainClientExec implements AsyncExecChainHandler {
this.httpProcessor = Args.notNull(httpProcessor, "HTTP protocol processor"); this.httpProcessor = Args.notNull(httpProcessor, "HTTP protocol processor");
this.keepAliveStrategy = keepAliveStrategy; this.keepAliveStrategy = keepAliveStrategy;
this.userTokenHandler = userTokenHandler; this.userTokenHandler = userTokenHandler;
this.protocolSwitchStrategy = new ProtocolSwitchStrategy();
} }
@Override @Override
@ -195,7 +201,35 @@ class HttpAsyncMainClientExec implements AsyncExecChainHandler {
public void consumeInformation( public void consumeInformation(
final HttpResponse response, final HttpResponse response,
final HttpContext context) throws HttpException, IOException { final HttpContext context) throws HttpException, IOException {
asyncExecCallback.handleInformationResponse(response); 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 @Override

View File

@ -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.RequestClientConnControl;
import org.apache.hc.client5.http.protocol.RequestDefaultHeaders; import org.apache.hc.client5.http.protocol.RequestDefaultHeaders;
import org.apache.hc.client5.http.protocol.RequestExpectContinue; 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.RequestValidateTrace;
import org.apache.hc.client5.http.protocol.ResponseProcessCookies; import org.apache.hc.client5.http.protocol.ResponseProcessCookies;
import org.apache.hc.client5.http.routing.HttpRoutePlanner; import org.apache.hc.client5.http.routing.HttpRoutePlanner;
@ -824,7 +825,8 @@ public class HttpClientBuilder {
new RequestContent(), new RequestContent(),
new RequestClientConnControl(), new RequestClientConnControl(),
new RequestUserAgent(userAgentCopy), new RequestUserAgent(userAgentCopy),
new RequestExpectContinue()); new RequestExpectContinue(),
new RequestUpgrade());
if (!cookieManagementDisabled) { if (!cookieManagementDisabled) {
b.add(RequestAddCookies.INSTANCE); b.add(RequestAddCookies.INSTANCE);
} }

View File

@ -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.ExecChainHandler;
import org.apache.hc.client5.http.classic.ExecRuntime; import org.apache.hc.client5.http.classic.ExecRuntime;
import org.apache.hc.client5.http.impl.ConnectionShutdownException; 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.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.annotation.Contract; 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.ConnectionReuseStrategy;
import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpException; 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.message.RequestLine;
import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpCoreContext;
import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.HttpProcessor;
@ -74,6 +78,7 @@ public final class MainClientExec implements ExecChainHandler {
private final ConnectionReuseStrategy reuseStrategy; private final ConnectionReuseStrategy reuseStrategy;
private final ConnectionKeepAliveStrategy keepAliveStrategy; private final ConnectionKeepAliveStrategy keepAliveStrategy;
private final UserTokenHandler userTokenHandler; private final UserTokenHandler userTokenHandler;
private final ProtocolSwitchStrategy protocolSwitchStrategy;
/** /**
* @since 4.4 * @since 4.4
@ -89,6 +94,7 @@ public final class MainClientExec implements ExecChainHandler {
this.reuseStrategy = Args.notNull(reuseStrategy, "Connection reuse strategy"); this.reuseStrategy = Args.notNull(reuseStrategy, "Connection reuse strategy");
this.keepAliveStrategy = Args.notNull(keepAliveStrategy, "Connection keep alive strategy"); this.keepAliveStrategy = Args.notNull(keepAliveStrategy, "Connection keep alive strategy");
this.userTokenHandler = Args.notNull(userTokenHandler, "User token handler"); this.userTokenHandler = Args.notNull(userTokenHandler, "User token handler");
this.protocolSwitchStrategy = new ProtocolSwitchStrategy();
} }
@Override @Override
@ -113,7 +119,27 @@ public final class MainClientExec implements ExecChainHandler {
httpProcessor.process(request, request.getEntity(), context); 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); context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
httpProcessor.process(response, response.getEntity(), context); httpProcessor.process(response, response.getEntity(), context);

View File

@ -53,6 +53,7 @@ import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.ConnectionClosedException; import org.apache.hc.core5.http.ConnectionClosedException;
import org.apache.hc.core5.http.HttpHost; 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.config.Lookup;
import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
@ -184,7 +185,7 @@ public class DefaultHttpClientConnectionOperator implements HttpClientConnection
final Timeout soTimeout = socketConfig.getSoTimeout(); final Timeout soTimeout = socketConfig.getSoTimeout();
final SocketAddress socksProxyAddress = socketConfig.getSocksProxyAddress(); final SocketAddress socksProxyAddress = socketConfig.getSocksProxyAddress();
final Proxy socksProxy = socksProxyAddress != null ? new Proxy(Proxy.Type.SOCKS, socksProxyAddress) : null; 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++) { for (int i = 0; i < remoteAddresses.length; i++) {
final InetAddress address = remoteAddresses[i]; final InetAddress address = remoteAddresses[i];
final boolean last = i == remoteAddresses.length - 1; final boolean last = i == remoteAddresses.length - 1;
@ -269,13 +270,14 @@ public class DefaultHttpClientConnectionOperator implements HttpClientConnection
if (socket == null) { if (socket == null) {
throw new ConnectionClosedException("Connection is closed"); 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) { 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); final SSLSocket upgradedSocket = tlsSocketStrategy.upgrade(socket, host.getHostName(), port, attachment, context);
conn.bind(upgradedSocket); conn.bind(upgradedSocket);
} else { } else {
throw new UnsupportedSchemeException(host.getSchemeName() + " protocol is not supported"); throw new UnsupportedSchemeException(newProtocol + " protocol is not supported");
} }
} }

View File

@ -505,10 +505,9 @@ public class PoolingHttpClientConnectionManager
Args.notNull(endpoint, "Managed endpoint"); Args.notNull(endpoint, "Managed endpoint");
final InternalConnectionEndpoint internalEndpoint = cast(endpoint); final InternalConnectionEndpoint internalEndpoint = cast(endpoint);
final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = internalEndpoint.getValidatedPoolEntry(); final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = internalEndpoint.getValidatedPoolEntry();
final HttpRoute route = poolEntry.getRoute(); final HttpHost target = poolEntry.getRoute().getTargetHost();
final HttpHost host = route.getProxyHost() != null ? route.getProxyHost() : route.getTargetHost(); final TlsConfig tlsConfig = resolveTlsConfig(target);
final TlsConfig tlsConfig = resolveTlsConfig(host); this.connectionOperator.upgrade(poolEntry.getConnection(), target, tlsConfig, context);
this.connectionOperator.upgrade(poolEntry.getConnection(), route.getTargetHost(), tlsConfig, context);
} }
@Override @Override

View File

@ -45,6 +45,7 @@ import org.apache.hc.core5.concurrent.ComplexFuture;
import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.concurrent.FutureContribution; import org.apache.hc.core5.concurrent.FutureContribution;
import org.apache.hc.core5.http.HttpHost; 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.config.Lookup;
import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.protocol.HttpContext;
@ -174,11 +175,13 @@ final class DefaultAsyncClientConnectionOperator implements AsyncClientConnectio
final Object attachment, final Object attachment,
final HttpContext context, final HttpContext context,
final FutureCallback<ManagedAsyncClientConnection> callback) { 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) { if (tlsStrategy != null) {
tlsStrategy.upgrade( tlsStrategy.upgrade(
connection, connection,
host, remoteEndpoint,
attachment, attachment,
null, null,
new CallbackContribution<TransportSecurityLayer>(callback) { new CallbackContribution<TransportSecurityLayer>(callback) {
@ -192,7 +195,7 @@ final class DefaultAsyncClientConnectionOperator implements AsyncClientConnectio
}); });
} else { } else {
callback.failed(new UnsupportedSchemeException(host.getSchemeName() + " protocol is not supported")); callback.failed(new UnsupportedSchemeException(newProtocol + " protocol is not supported"));
} }
} }

View File

@ -245,13 +245,21 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio
return connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT; return connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT;
} }
private TlsConfig resolveTlsConfig(final HttpHost host, final Object attachment) { private TlsConfig resolveTlsConfig(final HttpHost host) {
if (attachment instanceof TlsConfig) {
return (TlsConfig) attachment;
}
final Resolver<HttpHost, TlsConfig> resolver = this.tlsConfigResolver; final Resolver<HttpHost, TlsConfig> resolver = this.tlsConfigResolver;
final TlsConfig tlsConfig = resolver != null ? resolver.resolve(host) : null; TlsConfig tlsConfig = resolver != null ? resolver.resolve(host) : null;
return tlsConfig != null ? tlsConfig : TlsConfig.DEFAULT; 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 @Override
@ -441,7 +449,6 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio
} }
final InetSocketAddress localAddress = route.getLocalSocketAddress(); final InetSocketAddress localAddress = route.getLocalSocketAddress();
final ConnectionConfig connectionConfig = resolveConnectionConfig(route); final ConnectionConfig connectionConfig = resolveConnectionConfig(route);
final TlsConfig tlsConfig = resolveTlsConfig(host, attachment);
final Timeout connectTimeout = timeout != null ? timeout : connectionConfig.getConnectTimeout(); final Timeout connectTimeout = timeout != null ? timeout : connectionConfig.getConnectTimeout();
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
@ -452,9 +459,7 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio
host, host,
localAddress, localAddress,
connectTimeout, connectTimeout,
route.isTunnelled() ? TlsConfig.copy(tlsConfig) route.isTunnelled() ? null : resolveTlsConfig(host),
.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)
.build() : tlsConfig,
context, context,
new FutureCallback<ManagedAsyncClientConnection>() { new FutureCallback<ManagedAsyncClientConnection>() {
@ -499,12 +504,11 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio
final InternalConnectionEndpoint internalEndpoint = cast(endpoint); final InternalConnectionEndpoint internalEndpoint = cast(endpoint);
final PoolEntry<HttpRoute, ManagedAsyncClientConnection> poolEntry = internalEndpoint.getValidatedPoolEntry(); final PoolEntry<HttpRoute, ManagedAsyncClientConnection> poolEntry = internalEndpoint.getValidatedPoolEntry();
final HttpRoute route = poolEntry.getRoute(); final HttpRoute route = poolEntry.getRoute();
final HttpHost host = route.getProxyHost() != null ? route.getProxyHost() : route.getTargetHost(); final HttpHost target = route.getTargetHost();
final TlsConfig tlsConfig = resolveTlsConfig(host, attachment);
connectionOperator.upgrade( connectionOperator.upgrade(
poolEntry.getConnection(), poolEntry.getConnection(),
route.getTargetHost(), target,
attachment != null ? attachment : tlsConfig, attachment != null ? attachment : resolveTlsConfig(target),
context, context,
new CallbackContribution<ManagedAsyncClientConnection>(callback) { new CallbackContribution<ManagedAsyncClientConnection>(callback) {

View File

@ -66,11 +66,11 @@ public class RequestExpectContinue implements HttpRequestInterceptor {
Args.notNull(request, "HTTP request"); Args.notNull(request, "HTTP request");
if (!request.containsHeader(HttpHeaders.EXPECT)) { 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 // Do not send the expect header if request body is known to be empty
if (entity != null if (entity != null
&& entity.getContentLength() != 0 && !version.lessEquals(HttpVersion.HTTP_1_0)) { && entity.getContentLength() != 0 && !version.lessEquals(HttpVersion.HTTP_1_0)) {
final HttpClientContext clientContext = HttpClientContext.adapt(context);
final RequestConfig config = clientContext.getRequestConfig(); final RequestConfig config = clientContext.getRequestConfig();
if (config.isExpectContinueEnabled()) { if (config.isExpectContinueEnabled()) {
request.addHeader(HttpHeaders.EXPECT, HeaderElements.CONTINUE); request.addHeader(HttpHeaders.EXPECT, HeaderElements.CONTINUE);

View File

@ -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);
}
}
}
}
}

View File

@ -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));
}
}

View File

@ -56,7 +56,6 @@ import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
@SuppressWarnings({"boxing","static-access"}) // test code
public class TestMainClientExec { public class TestMainClientExec {
@Mock @Mock
@ -104,7 +103,7 @@ public class TestMainClientExec {
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null); final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null);
Mockito.verify(httpProcessor).process(request, null, context); 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); Mockito.verify(httpProcessor).process(response, responseEntity, context);
Assertions.assertEquals(route, context.getHttpRoute()); 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 ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null); 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.times(1)).markConnectionNonReusable();
Mockito.verify(execRuntime, Mockito.never()).releaseEndpoint(); 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 ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null); 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).markConnectionNonReusable();
Mockito.verify(execRuntime).releaseEndpoint(); Mockito.verify(execRuntime).releaseEndpoint();
@ -200,7 +199,7 @@ public class TestMainClientExec {
final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context); final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null); 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).markConnectionReusable(null, TimeValue.ofMilliseconds(678L));
Mockito.verify(execRuntime, Mockito.never()).releaseEndpoint(); 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 ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null); 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(); Mockito.verify(execRuntime).releaseEndpoint();
Assertions.assertNotNull(finalResponse); Assertions.assertNotNull(finalResponse);
@ -261,7 +260,7 @@ public class TestMainClientExec {
final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context); final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, execRuntime, context);
final ClassicHttpResponse finalResponse = mainClientExec.execute(request, scope, null); 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()).disconnectEndpoint();
Mockito.verify(execRuntime, Mockito.never()).releaseEndpoint(); Mockito.verify(execRuntime, Mockito.never()).releaseEndpoint();

View File

@ -385,7 +385,7 @@ public class TestBasicHttpClientConnectionManager {
mgr.setTlsConfig(tlsConfig); mgr.setTlsConfig(tlsConfig);
Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[] {remote}); 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(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy); Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
@ -399,7 +399,7 @@ public class TestBasicHttpClientConnectionManager {
mgr.connect(endpoint1, null, context); mgr.connect(endpoint1, null, context);
Mockito.verify(dnsResolver, Mockito.times(1)).resolve("somehost"); 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(detachedSocketFactory, Mockito.times(1)).create(null);
Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 234); Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 234);
Mockito.verify(tlsSocketStrategy).upgrade(socket, "somehost", 8443, tlsConfig, context); Mockito.verify(tlsSocketStrategy).upgrade(socket, "somehost", 8443, tlsConfig, context);
@ -407,7 +407,7 @@ public class TestBasicHttpClientConnectionManager {
mgr.connect(endpoint1, TimeValue.ofMilliseconds(123), context); mgr.connect(endpoint1, TimeValue.ofMilliseconds(123), context);
Mockito.verify(dnsResolver, Mockito.times(2)).resolve("somehost"); 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(detachedSocketFactory, Mockito.times(2)).create(null);
Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 123); Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 123);
Mockito.verify(tlsSocketStrategy, Mockito.times(2)).upgrade(socket, "somehost", 8443, tlsConfig, context); Mockito.verify(tlsSocketStrategy, Mockito.times(2)).upgrade(socket, "somehost", 8443, tlsConfig, context);
@ -442,15 +442,15 @@ public class TestBasicHttpClientConnectionManager {
mgr.setTlsConfig(tlsConfig); mgr.setTlsConfig(tlsConfig);
Mockito.when(dnsResolver.resolve("someproxy")).thenReturn(new InetAddress[] {remote}); Mockito.when(dnsResolver.resolve("someproxy")).thenReturn(new InetAddress[] {remote});
Mockito.when(schemePortResolver.resolve(proxy)).thenReturn(8080); Mockito.when(schemePortResolver.resolve(proxy.getSchemeName(), proxy)).thenReturn(8080);
Mockito.when(schemePortResolver.resolve(target)).thenReturn(8443); Mockito.when(schemePortResolver.resolve(target.getSchemeName(), target)).thenReturn(8443);
Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy); Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
Mockito.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket); Mockito.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
mgr.connect(endpoint1, null, context); mgr.connect(endpoint1, null, context);
Mockito.verify(dnsResolver, Mockito.times(1)).resolve("someproxy"); 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(detachedSocketFactory, Mockito.times(1)).create(null);
Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8080), 234); Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8080), 234);
@ -458,7 +458,7 @@ public class TestBasicHttpClientConnectionManager {
mgr.upgrade(endpoint1, context); 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( Mockito.verify(tlsSocketStrategy, Mockito.times(1)).upgrade(
socket, "somehost", 8443, tlsConfig, context); socket, "somehost", 8443, tlsConfig, context);
} }

View File

@ -91,7 +91,7 @@ public class TestHttpClientConnectionOperator {
final InetAddress ip2 = InetAddress.getByAddress(new byte[] {127, 0, 0, 2}); final InetAddress ip2 = InetAddress.getByAddress(new byte[] {127, 0, 0, 2});
Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[] { ip1, ip2 }); 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.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
final SocketConfig socketConfig = SocketConfig.custom() final SocketConfig socketConfig = SocketConfig.custom()
@ -129,7 +129,7 @@ public class TestHttpClientConnectionOperator {
.build(); .build();
Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[] { ip1, ip2 }); 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(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy); 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}); final InetAddress ip2 = InetAddress.getByAddress(new byte[] {10, 0, 0, 2});
Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[] { ip1, ip2 }); 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.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
Mockito.doThrow(new ConnectException()).when(socket).connect( Mockito.doThrow(new ConnectException()).when(socket).connect(
Mockito.eq(new InetSocketAddress(ip1, 80)), 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 InetAddress ip = InetAddress.getByAddress(new byte[] {127, 0, 0, 23});
final HttpHost host = new HttpHost(ip); 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); Mockito.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
final InetSocketAddress localAddress = new InetSocketAddress(local, 0); final InetSocketAddress localAddress = new InetSocketAddress(local, 0);
@ -242,7 +242,7 @@ public class TestHttpClientConnectionOperator {
Mockito.when(conn.isOpen()).thenReturn(true); Mockito.when(conn.isOpen()).thenReturn(true);
Mockito.when(conn.getSocket()).thenReturn(socket); Mockito.when(conn.getSocket()).thenReturn(socket);
Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy); 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); final SSLSocket upgradedSocket = Mockito.mock(SSLSocket.class);
Mockito.when(tlsSocketStrategy.upgrade( Mockito.when(tlsSocketStrategy.upgrade(

View File

@ -263,7 +263,7 @@ public class TestPoolingHttpClientConnectionManager {
mgr.setDefaultTlsConfig(tlsConfig); mgr.setDefaultTlsConfig(tlsConfig);
Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[]{remote}); 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(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy); Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
@ -277,7 +277,7 @@ public class TestPoolingHttpClientConnectionManager {
mgr.connect(endpoint1, null, context); mgr.connect(endpoint1, null, context);
Mockito.verify(dnsResolver, Mockito.times(1)).resolve("somehost"); 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(detachedSocketFactory, Mockito.times(1)).create(null);
Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 234); Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 234);
Mockito.verify(tlsSocketStrategy).upgrade(socket, "somehost", 8443, tlsConfig, context); Mockito.verify(tlsSocketStrategy).upgrade(socket, "somehost", 8443, tlsConfig, context);
@ -285,7 +285,7 @@ public class TestPoolingHttpClientConnectionManager {
mgr.connect(endpoint1, TimeValue.ofMilliseconds(123), context); mgr.connect(endpoint1, TimeValue.ofMilliseconds(123), context);
Mockito.verify(dnsResolver, Mockito.times(2)).resolve("somehost"); 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(detachedSocketFactory, Mockito.times(2)).create(null);
Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 123); Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 123);
Mockito.verify(tlsSocketStrategy, Mockito.times(2)).upgrade(socket, "somehost", 8443, tlsConfig, context); Mockito.verify(tlsSocketStrategy, Mockito.times(2)).upgrade(socket, "somehost", 8443, tlsConfig, context);
@ -330,15 +330,15 @@ public class TestPoolingHttpClientConnectionManager {
mgr.setDefaultTlsConfig(tlsConfig); mgr.setDefaultTlsConfig(tlsConfig);
Mockito.when(dnsResolver.resolve("someproxy")).thenReturn(new InetAddress[] {remote}); Mockito.when(dnsResolver.resolve("someproxy")).thenReturn(new InetAddress[] {remote});
Mockito.when(schemePortResolver.resolve(proxy)).thenReturn(8080); Mockito.when(schemePortResolver.resolve(proxy.getSchemeName(), proxy)).thenReturn(8080);
Mockito.when(schemePortResolver.resolve(target)).thenReturn(8443); Mockito.when(schemePortResolver.resolve(target.getSchemeName(), target)).thenReturn(8443);
Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy); Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
Mockito.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket); Mockito.when(detachedSocketFactory.create(Mockito.any())).thenReturn(socket);
mgr.connect(endpoint1, null, context); mgr.connect(endpoint1, null, context);
Mockito.verify(dnsResolver, Mockito.times(1)).resolve("someproxy"); 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(detachedSocketFactory, Mockito.times(1)).create(null);
Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8080), 234); Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8080), 234);
@ -347,7 +347,7 @@ public class TestPoolingHttpClientConnectionManager {
mgr.upgrade(endpoint1, context); 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( Mockito.verify(tlsSocketStrategy, Mockito.times(1)).upgrade(
socket, "somehost", 8443, tlsConfig, context); socket, "somehost", 8443, tlsConfig, context);
} }

View File

@ -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));
}
}