io.gatling.http.client.impl.DefaultHttpClient Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2011-2024 GatlingCorp (https://gatling.io)
*
* Licensed 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.
*/
package io.gatling.http.client.impl;
import io.gatling.http.client.*;
import io.gatling.http.client.body.is.InputStreamRequestBody;
import io.gatling.http.client.impl.chunk.ForkedChunkedWriteHandler;
import io.gatling.http.client.impl.compression.CustomDelegatingDecompressorFrameListener;
import io.gatling.http.client.impl.compression.CustomHttpContentDecompressor;
import io.gatling.http.client.pool.ChannelPool;
import io.gatling.http.client.pool.ChannelPoolKey;
import io.gatling.http.client.pool.RemoteKey;
import io.gatling.http.client.proxy.HttpProxyServer;
import io.gatling.http.client.proxy.ProxyProtocolHandler;
import io.gatling.http.client.proxy.ProxyServer;
import io.gatling.http.client.ssl.Tls;
import io.gatling.http.client.uri.Uri;
import io.gatling.http.client.util.Pair;
import io.gatling.netty.util.Transports;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpDecoderConfig;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator;
import io.netty.handler.codec.http2.*;
import io.netty.handler.ssl.ApplicationProtocolNames;
import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.resolver.NoopAddressResolverGroup;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.*;
import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.util.internal.logging.Slf4JLoggerFactory;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class DefaultHttpClient implements HttpClient {
private static final Http2Settings DEFAULT_HTTP2_SETTINGS = Http2Settings.defaultSettings();
static {
InternalLoggerFactory.setDefaultFactory(Slf4JLoggerFactory.INSTANCE);
}
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultHttpClient.class);
private static final String PINNED_HANDLER = "pinned";
private static final String PROXY_SSL_HANDLER = "ssl-proxy";
private static final String PROXY_HANDLER = "proxy";
private static final String PROXY_PROTOCOL_HANDLER = "proxy-protocol";
private static final String SSL_HANDLER = "ssl";
public static final String HTTP_CLIENT_CODEC = "http";
private static final String HTTP2_HANDLER = "http2";
private static final String INFLATER_HANDLER = "inflater";
private static final String CHUNKED_WRITER_HANDLER = "chunked-writer";
private static final String WS_OBJECT_AGGREGATOR = "ws-object-aggregator";
private static final String WS_COMPRESSION = "ws-compression";
private static final String WS_FRAME_AGGREGATOR = "ws-frame-aggregator";
private static final String APP_WS_HANDLER = "app-ws";
private static final String ALPN_HANDLER = "alpn";
static final String APP_HTTP2_HANDLER = "app-http2";
public static final String APP_HTTP_HANDLER = "app-http";
private static final HttpDecoderConfig HTTP_DECODER_CONFIG =
new HttpDecoderConfig().setMaxHeaderSize(Integer.MAX_VALUE).setValidateHeaders(false);
private ChannelHandler newHttpClientCodec() {
return new HttpClientCodec(HTTP_DECODER_CONFIG, false, false);
}
private final class EventLoopResources {
private static final int POOL_CLEANER_PERIOD_MS = 1_000;
private final Bootstrap http1Bootstrap;
private final Bootstrap http2Bootstrap;
private final Bootstrap wsBootstrap;
private final ChannelPool channelPool;
private void addHttpHandlers(Channel channel) {
channel
.pipeline()
.addLast(HTTP_CLIENT_CODEC, newHttpClientCodec())
.addLast(INFLATER_HANDLER, new CustomHttpContentDecompressor())
.addLast(CHUNKED_WRITER_HANDLER, new ChunkedWriteHandler())
.addLast(APP_HTTP_HANDLER, new HttpAppHandler(DefaultHttpClient.this, channelPool));
}
private void addWsHandlers(Channel channel) {
channel
.pipeline()
.addLast(HTTP_CLIENT_CODEC, newHttpClientCodec())
.addLast(WS_OBJECT_AGGREGATOR, new HttpObjectAggregator(Integer.MAX_VALUE))
.addLast(WS_COMPRESSION, AllowClientNoContextWebSocketClientCompressionHandler.INSTANCE)
.addLast(WS_FRAME_AGGREGATOR, new WebSocketFrameAggregator(Integer.MAX_VALUE))
.addLast(APP_WS_HANDLER, new WebSocketHandler());
}
private void addProxyHandlers(Channel ch, HttpTx tx, ProxyServer proxyServer) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(PINNED_HANDLER, NoopHandler.INSTANCE);
if (proxyServer instanceof HttpProxyServer) {
if (((HttpProxyServer) proxyServer).isSecured()) {
installSslHandler(tx, ch, proxyServer.getHost(), proxyServer.getPort(), PROXY_SSL_HANDLER)
.addListener(
f -> {
if (tx.requestTimeout.isDone() || !f.isSuccess()) {
ch.close();
}
});
}
if (tx.request.getProxyProtocolSourceIpV4Address() != null
|| tx.request.getProxyProtocolSourceIpV6Address() != null) {
pipeline.addLast(
PROXY_PROTOCOL_HANDLER,
new ProxyProtocolHandler(
tx.request.getProxyProtocolSourceIpV4Address(),
tx.request.getProxyProtocolSourceIpV6Address()));
}
}
if (proxyHandlerSupportsUri(proxyServer, tx.request.getUri())) {
pipeline.addLast(PROXY_HANDLER, proxyServer.newProxyHandler());
}
}
private EventLoopResources(EventLoop eventLoop) {
channelPool = new ChannelPool();
eventLoop.scheduleWithFixedDelay(
() -> channelPool.closeIdleChannels(idleTimeoutNanos),
POOL_CLEANER_PERIOD_MS,
POOL_CLEANER_PERIOD_MS,
TimeUnit.MILLISECONDS);
http1Bootstrap =
new Bootstrap()
.channelFactory(
Transports.newSocketChannelFactory(
config.isUseNativeTransport(), config.isUseIoUring()))
.group(eventLoop)
.resolver(NoopAddressResolverGroup.INSTANCE)
.handler(
new ChannelInitializer<>() {
@Override
protected void initChannel(Channel channel) {
channel.pipeline().addLast(PINNED_HANDLER, NoopHandler.INSTANCE);
addHttpHandlers(channel);
}
});
Transports.configureOptions(
http1Bootstrap,
(int) config.getConnectTimeout(),
config.isTcpNoDelay(),
config.isSoKeepAlive(),
config.isUseNativeTransport() && !config.isUseIoUring() && Epoll.isAvailable());
http2Bootstrap =
http1Bootstrap
.clone()
.handler(
new ChannelInitializer() {
@Override
protected void initChannel(Channel channel) {
channel.pipeline().addLast(PINNED_HANDLER, NoopHandler.INSTANCE);
}
});
wsBootstrap =
http1Bootstrap
.clone()
.handler(
new ChannelInitializer() {
@Override
protected void initChannel(Channel channel) {
channel.pipeline().addLast(PINNED_HANDLER, NoopHandler.INSTANCE);
addWsHandlers(channel);
}
});
}
private Bootstrap getHttp1BootstrapWithProxy(HttpTx tx, ProxyServer proxy) {
return http1Bootstrap
.clone()
.handler(
new ChannelInitializer<>() {
@Override
protected void initChannel(Channel ch) {
addProxyHandlers(ch, tx, proxy);
addHttpHandlers(ch);
}
});
}
private Bootstrap getWsBootstrapWithProxy(HttpTx tx, ProxyServer proxy) {
return wsBootstrap
.clone()
.handler(
new ChannelInitializer<>() {
@Override
protected void initChannel(Channel ch) {
addProxyHandlers(ch, tx, proxy);
addWsHandlers(ch);
}
});
}
}
private final AtomicBoolean closed = new AtomicBoolean();
private final HttpClientConfig config;
private final EventExecutor channelGroupEventExecutor;
private final ChannelGroup channelGroup;
private final FastThreadLocal eventLoopResources = new FastThreadLocal<>();
private final long idleTimeoutNanos;
public DefaultHttpClient(HttpClientConfig config) {
this.config = config;
channelGroupEventExecutor = new DefaultEventExecutor();
channelGroup = new DefaultChannelGroup(channelGroupEventExecutor);
idleTimeoutNanos = config.getChannelPoolIdleTimeout() * 1_000_000;
}
@Override
public void close() {
if (closed.compareAndSet(false, true)) {
channelGroup.close().awaitUninterruptibly();
channelGroupEventExecutor.shutdownGracefully(0, 1, TimeUnit.SECONDS);
SslContextsHolder defaultSslContextsHolder = config.getDefaultSslContextsHolder();
if (defaultSslContextsHolder != null) {
ReferenceCountUtil.release(defaultSslContextsHolder.getSslContext());
ReferenceCountUtil.release(defaultSslContextsHolder.getAlpnSslContext());
}
}
}
@Override
public void sendRequest(
Request request,
long clientId,
EventLoop eventLoop,
HttpListener listener,
SslContextsHolder sslContextsHolder) {
if (isClosed()) {
return;
}
if (sslContextsHolder == null) {
sslContextsHolder = config.getDefaultSslContextsHolder();
}
HttpTx tx = buildTx(request, clientId, listener, sslContextsHolder);
if (eventLoop.inEventLoop()) {
sendTx(tx, eventLoop);
} else if (!eventLoop.isShutdown()) {
eventLoop.execute(() -> sendTx(tx, eventLoop));
}
}
@Override
public void sendHttp2Requests(
Pair[] requestsAndListeners,
long clientId,
EventLoop eventLoop,
SslContextsHolder sslContextsHolder) {
if (isClosed()) {
return;
}
for (Pair pair : requestsAndListeners) {
pair.getRight().onSend();
}
Request headRequest = requestsAndListeners[0].getLeft();
if (headRequest.getUri().isSecured() && headRequest.isHttp2Enabled() && !config.isEnableSni()) {
for (Pair requestAndListener : requestsAndListeners) {
HttpListener listener = requestAndListener.getRight();
listener.onThrowable(
new UnsupportedOperationException("HTTP/2 can't work if SNI is disabled."));
}
return;
}
if (sslContextsHolder == null) {
sslContextsHolder = config.getDefaultSslContextsHolder();
}
List txs = new ArrayList<>(requestsAndListeners.length);
for (Pair requestAndListener : requestsAndListeners) {
Request request = requestAndListener.getLeft();
HttpListener listener = requestAndListener.getRight();
txs.add(buildTx(request, clientId, listener, sslContextsHolder));
}
if (eventLoop.inEventLoop()) {
sendHttp2Txs(txs, eventLoop);
} else if (!eventLoop.isShutdown()) {
eventLoop.execute(() -> sendHttp2Txs(txs, eventLoop));
}
}
//////////////////// EVERYTHING BELOW ONLY HAPPENS IN SAME EVENTLOOP //////////////////
private EventLoopResources eventLoopResources(EventLoop eventLoop) {
EventLoopResources resources = eventLoopResources.get();
if (resources == null) {
resources = new EventLoopResources(eventLoop);
eventLoopResources.set(resources);
}
return resources;
}
private HttpTx buildTx(
Request request, long clientId, HttpListener listener, SslContextsHolder sslContextsHolder) {
RequestTimeout requestTimeout =
RequestTimeout.requestTimeout(request.getRequestTimeout(), listener);
ChannelPoolKey key =
new ChannelPoolKey(clientId, RemoteKey.newKey(request.getUri(), request.getProxyServer()));
return new HttpTx(request, listener, requestTimeout, key, sslContextsHolder);
}
// only retry pooled keep-alive connections = when keep-alive timeout triggered server side while
// we were writing and request can be replayed
boolean canRetry(HttpTx tx) {
return tx.channelState == HttpTx.ChannelState.POOLED
&& !(tx.request.getBody() instanceof InputStreamRequestBody
&& ((InputStreamRequestBody) tx.request.getBody())
.isConsumed()); // InputStreamRequestBody can't be replayed
}
void retry(HttpTx tx, EventLoop eventLoop) {
if (isClosed()) {
return;
}
tx.channelState = HttpTx.ChannelState.RETRY;
LOGGER.debug("Retrying with new HTTP/1.1 connection");
sendTx(tx, eventLoop);
}
void retryHttp2(List txs, EventLoop eventLoop) {
if (isClosed()) {
return;
}
for (HttpTx tx : txs) {
tx.channelState = HttpTx.ChannelState.RETRY;
}
LOGGER.debug("Retrying with new HTTP/2 connection");
sendHttp2Txs(txs, eventLoop);
}
private void sendTx(HttpTx tx, EventLoop eventLoop) {
EventLoopResources resources = eventLoopResources(eventLoop);
Request request = tx.request;
HttpListener listener = tx.listener;
RequestTimeout requestTimeout = tx.requestTimeout;
Uri requestUri = request.getUri();
boolean tryHttp2 =
request.isHttp2Enabled() && requestUri.isSecured() && !requestUri.isWebSocket();
// use a fresh channel for WebSocket
Channel pooledChannel = requestUri.isWebSocket() ? null : resources.channelPool.poll(tx.key);
listener.onSend();
// start timeout
tx.requestTimeout.start(eventLoop);
if (pooledChannel != null && tx.channelState != HttpTx.ChannelState.RETRY) {
sendTxWithChannel(tx, pooledChannel);
} else {
InetSocketAddress proxyHandlerUnresolvedRemoteAddress =
proxyHandlerUnresolvedRemoteAddress(request.getProxyServer(), requestUri);
boolean logProxyAddress = proxyHandlerUnresolvedRemoteAddress != null;
resolveChannelRemoteAddresses(
request, eventLoop, proxyHandlerUnresolvedRemoteAddress, listener, requestTimeout)
.addListener(
(Future> whenRemoteAddresses) -> {
if (requestTimeout.isDone()) {
return;
}
if (whenRemoteAddresses.isSuccess()) {
List remoteAddresses = whenRemoteAddresses.getNow();
if (tryHttp2 && tx.channelState != HttpTx.ChannelState.RETRY) {
String domain = requestUri.getHost();
Channel coalescedChannel =
resources.channelPool.pollCoalescedChannel(
tx.key.clientId, domain, remoteAddresses);
if (coalescedChannel != null) {
tx.listener.onProtocolAwareness(true);
sendTxWithChannel(tx, coalescedChannel);
} else {
sendTxWithNewChannel(
tx, resources, eventLoop, remoteAddresses, logProxyAddress);
}
} else {
sendTxWithNewChannel(
tx, resources, eventLoop, remoteAddresses, logProxyAddress);
}
}
});
}
}
private void sendHttp2Txs(List txs, EventLoop eventLoop) {
HttpTx tx = txs.get(0);
EventLoopResources resources = eventLoopResources(eventLoop);
Request request = tx.request;
HttpListener listener = tx.listener;
RequestTimeout requestTimeout = tx.requestTimeout;
Uri requestUri = request.getUri();
// start timeouts
for (HttpTx t : txs) {
t.requestTimeout.start(eventLoop);
}
ProxyServer proxyServer = request.getProxyServer();
InetSocketAddress proxyHandlerUnresolvedRemoteAddress =
proxyHandlerUnresolvedRemoteAddress(proxyServer, requestUri);
boolean logProxyAddress = proxyHandlerUnresolvedRemoteAddress != null;
resolveChannelRemoteAddresses(
request, eventLoop, proxyHandlerUnresolvedRemoteAddress, listener, requestTimeout)
.addListener(
(Future> whenRemoteAddresses) -> {
if (requestTimeout.isDone()) {
return;
}
if (whenRemoteAddresses.isSuccess()) {
List addresses = whenRemoteAddresses.getNow();
String domain = requestUri.getHost();
Channel pooledChannel = resources.channelPool.poll(tx.key);
if (pooledChannel == null) {
pooledChannel =
resources.channelPool.pollCoalescedChannel(
tx.key.clientId, domain, addresses);
}
if (pooledChannel != null) {
sendHttp2TxsWithChannel(txs, pooledChannel);
} else {
sendHttp2TxsWithNewChannel(txs, resources, eventLoop, addresses, logProxyAddress);
}
}
});
}
private void sendTxWithChannel(HttpTx tx, Channel channel) {
if (isClosed()) {
return;
}
if (ChannelPool.isHttp2(channel)) {
tx.listener.onProtocolAwareness(true);
}
tx.requestTimeout.setChannel(channel);
channel.write(tx);
}
private void sendHttp2TxsWithChannel(List txs, Channel channel) {
if (isClosed()) {
return;
}
for (HttpTx tx : txs) {
tx.requestTimeout.setChannel(channel);
tx.listener.onProtocolAwareness(true);
channel.write(tx);
}
}
private static boolean proxyHandlerSupportsUri(ProxyServer proxyServer, Uri uri) {
// HttpProxyServer only supports CONNECT requests, hence secured requests
return !(proxyServer instanceof HttpProxyServer && !uri.isSecured());
}
private InetSocketAddress proxyHandlerUnresolvedRemoteAddress(
ProxyServer proxyServer, Uri requestUri) {
// HttpProxyHandler doesn't handle clear HTTP requests (absolute URI)
return proxyServer != null && proxyHandlerSupportsUri(proxyServer, requestUri)
? InetSocketAddress.createUnresolved(requestUri.getHost(), requestUri.getExplicitPort())
: null;
}
private Future> resolveChannelRemoteAddresses(
Request request,
EventLoop eventLoop,
InetSocketAddress proxyHandlerUnresolvedRemoteAddress,
HttpListener listener,
RequestTimeout requestTimeout) {
ProxyServer proxyServer = request.getProxyServer();
if (proxyServer != null) {
InetSocketAddress channelRemoteAddress =
proxyHandlerUnresolvedRemoteAddress != null
?
// ProxyHandler will take care of the connect logic
proxyHandlerUnresolvedRemoteAddress
:
// HttpProxyHandler only handles CONNECT requests, not clear HTTP requests with an
// absolute URL that we have to handle ourselves
proxyServer.getAddress();
return ImmediateEventExecutor.INSTANCE.newSucceededFuture(List.of(channelRemoteAddress));
} else {
Promise> p = eventLoop.newPromise();
request
.getNameResolver()
.resolveAll(request.getUri().getHost(), eventLoop.newPromise(), listener)
.addListener(
(Future> whenAddresses) -> {
if (whenAddresses.isSuccess()) {
List remoteInetSocketAddresses =
whenAddresses.getNow().stream()
.map(
address ->
new InetSocketAddress(
address, request.getUri().getExplicitPort()))
.collect(Collectors.toList());
p.setSuccess(remoteInetSocketAddresses);
} else {
if (!requestTimeout.isDone()) {
// only report if we haven't timed out
listener.onThrowable(whenAddresses.cause());
}
p.setFailure(whenAddresses.cause());
requestTimeout.cancel();
}
});
return p;
}
}
private void sendTxWithNewChannel(
HttpTx tx,
EventLoopResources resources,
EventLoop eventLoop,
List remoteAddresses,
boolean logProxyAddress) {
tx.channelState = HttpTx.ChannelState.NEW;
openNewChannel(
tx,
tx.request,
logProxyAddress,
eventLoop,
resources,
remoteAddresses,
tx.listener,
tx.requestTimeout)
.addListener(
(Future whenNewChannel) -> {
if (whenNewChannel.isSuccess()) {
Channel channel = whenNewChannel.getNow();
if (tx.requestTimeout.isDone()) {
channel.close();
return;
}
channelGroup.add(channel);
ChannelPool.registerPoolKey(channel, tx.key);
Uri requestUri = tx.request.getUri();
if (requestUri.isSecured()) {
installSslHandler(
tx,
channel,
requestUri.getHost(),
requestUri.getExplicitPort(),
SSL_HANDLER)
.addListener(
f -> {
if (tx.requestTimeout.isDone() || !f.isSuccess()) {
channel.close();
return;
}
if (!tx.request.isHttp2Enabled()
|| tx.request.getHttp2PriorKnowledge()
== Http2PriorKnowledge.HTTP1_ONLY) {
sendTxWithChannel(tx, channel);
} else {
LOGGER.debug("Installing Http2Handler for {}", requestUri);
installHttp2Handler(tx, channel, resources.channelPool)
.addListener(
f2 -> {
if (tx.requestTimeout.isDone() || !f2.isSuccess()) {
channel.close();
return;
}
sendTxWithChannel(tx, channel);
});
}
});
} else {
sendTxWithChannel(tx, channel);
}
}
});
}
private void sendHttp2TxsWithNewChannel(
List txs,
EventLoopResources resources,
EventLoop eventLoop,
List remoteAddresses,
boolean logProxyAddress) {
HttpTx tx = txs.get(0);
openNewChannel(
tx,
tx.request,
logProxyAddress,
eventLoop,
resources,
remoteAddresses,
tx.listener,
tx.requestTimeout)
.addListener(
(Future whenNewChannel) -> {
if (whenNewChannel.isSuccess()) {
Channel channel = whenNewChannel.getNow();
if (tx.requestTimeout.isDone()) {
channel.close();
return;
}
channelGroup.add(channel);
ChannelPool.registerPoolKey(channel, tx.key);
Uri requestUri = tx.request.getUri();
LOGGER.debug("Installing SslHandler for {}", requestUri);
installSslHandler(
tx,
channel,
requestUri.getHost(),
requestUri.getExplicitPort(),
SSL_HANDLER)
.addListener(
f -> {
if (tx.requestTimeout.isDone() || !f.isSuccess()) {
channel.close();
return;
}
LOGGER.debug("Installing Http2Handler for {}", requestUri);
installHttp2Handler(tx, channel, resources.channelPool)
.addListener(
f2 -> {
if (tx.requestTimeout.isDone() || !f2.isSuccess()) {
channel.close();
return;
}
sendHttp2TxsWithChannel(txs, channel);
});
});
}
});
}
private Bootstrap bootstrap(HttpTx tx, Request request, EventLoopResources resources) {
Uri requestUri = request.getUri();
ProxyServer proxyServer = request.getProxyServer();
if (proxyServer != null) {
if (requestUri.isWebSocket()) {
return resources.getWsBootstrapWithProxy(tx, proxyServer);
} else {
// HttpProxyHandler doesn't handle clear HTTP requests, only CONNECT ones
// FIXME HTTP/2 with proxy
return resources.getHttp1BootstrapWithProxy(tx, proxyServer);
}
}
if (requestUri.isWebSocket()) {
return resources.wsBootstrap;
} else if (requestUri.isSecured()
&& request.isHttp2Enabled()
&& request.getHttp2PriorKnowledge() != Http2PriorKnowledge.HTTP1_ONLY) {
return resources.http2Bootstrap;
} else {
return resources.http1Bootstrap;
}
}
private Future openNewChannel(
HttpTx tx,
Request request,
boolean logProxyAddress,
EventLoop eventLoop,
EventLoopResources resources,
List remoteAddresses,
HttpListener listener,
RequestTimeout requestTimeout) {
LOGGER.debug(
"Opening new channel to remote={} from local={}",
remoteAddresses,
request.getLocalAddresses());
Bootstrap bootstrap = bootstrap(tx, request, resources);
Promise channelPromise = eventLoop.newPromise();
InetSocketAddress loggedProxyAddress =
logProxyAddress ? request.getProxyServer().getAddress() : null;
openNewChannelRec(
remoteAddresses,
loggedProxyAddress,
request.getLocalAddresses(),
0,
channelPromise,
bootstrap,
listener,
requestTimeout);
return channelPromise;
}
private static final Exception IGNORE_REQUEST_TIMEOUT_REACHED_WHILE_TRYING_TO_CONNECT =
new TimeoutException("Request timeout reached while trying to connect, should be ignored") {
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
};
private void openNewChannelRec(
List remoteAddresses,
InetSocketAddress loggedProxyAddress,
LocalAddresses localAddresses,
int i,
Promise channelPromise,
Bootstrap bootstrap,
HttpListener listener,
RequestTimeout requestTimeout) {
if (isClosed()) {
return;
}
InetSocketAddress remoteAddress = remoteAddresses.get(i);
InetSocketAddress localAddress;
boolean forceMoveToNextRemoteAddress = false;
if (localAddresses == null) {
// non explicit local addresses, skip
localAddress = null;
} else {
InetSocketAddress localAddressForRemote =
localAddresses.getLocalAddressForRemote(remoteAddress.getAddress());
if (localAddressForRemote == null) {
// no match
localAddress = null;
forceMoveToNextRemoteAddress = true;
} else {
localAddress = localAddressForRemote;
}
}
if (forceMoveToNextRemoteAddress) {
int nextI = i + 1;
if (nextI < remoteAddresses.size()) {
openNewChannelRec(
remoteAddresses,
loggedProxyAddress,
localAddresses,
nextI,
channelPromise,
bootstrap,
listener,
requestTimeout);
} else {
requestTimeout.cancel();
Exception cause =
new UnsupportedOperationException(
"Can't connect to remote " + remoteAddresses + " + from local " + localAddresses);
listener.onThrowable(cause);
channelPromise.setFailure(cause);
}
} else {
// [e]
//
// [e]
ChannelFuture whenChannel = bootstrap.connect(remoteAddress, localAddress);
whenChannel.addListener(
f -> {
if (f.isSuccess()) {
// [e]
//
//
//
//
//
// [e]
LOGGER.debug(
"Connected to remoteAddress={} from localAddress={}",
remoteAddress,
whenChannel.channel().localAddress());
channelPromise.setSuccess(whenChannel.channel());
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Failed to connect to remoteAddress="
+ remoteAddress
+ " from localAddress="
+ localAddress,
f.cause());
}
// [e]
//
// [e]
if (requestTimeout.isDone()) {
channelPromise.setFailure(IGNORE_REQUEST_TIMEOUT_REACHED_WHILE_TRYING_TO_CONNECT);
return;
}
int nextI = i + 1;
if (nextI < remoteAddresses.size()) {
openNewChannelRec(
remoteAddresses,
loggedProxyAddress,
localAddresses,
nextI,
channelPromise,
bootstrap,
listener,
requestTimeout);
} else {
requestTimeout.cancel();
listener.onThrowable(f.cause());
channelPromise.setFailure(f.cause());
}
}
});
}
}
private Future installSslHandler(
HttpTx tx, Channel channel, String peerHost, int peerPort, String sslHandlerName) {
LOGGER.debug("Installing SslHandler for {}:{}", peerHost, peerPort);
// [e]
//
// [e]
try {
SslHandler sslHandler =
SslHandlers.newSslHandler(tx.sslContext(), channel.alloc(), peerHost, peerPort, config);
ChannelPipeline pipeline = channel.pipeline();
String after = pipeline.get(PROXY_HANDLER) != null ? PROXY_HANDLER : PINNED_HANDLER;
pipeline.addAfter(after, sslHandlerName, sslHandler);
return sslHandler
.handshakeFuture()
.addListener(
f -> {
if (tx.requestTimeout.isDone()) {
return;
}
if (f.isSuccess()) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"TLS handshake successful: peerHost={} peerPort={} protocol={} cipher suite={}",
sslHandler.engine().getSession().getPeerHost(),
sslHandler.engine().getSession().getPeerPort(),
sslHandler.engine().getSession().getProtocol(),
sslHandler.engine().getSession().getCipherSuite());
}
// [e]
//
// [e]
} else {
tx.requestTimeout.cancel();
// [e]
//
// [e]
tx.listener.onThrowable(f.cause());
}
});
} catch (RuntimeException e) {
tx.requestTimeout.cancel();
// [e]
//
// [e]
tx.listener.onThrowable(e);
return new DefaultPromise(ImmediateEventExecutor.INSTANCE).setFailure(e);
}
}
private Future installHttp2Handler(HttpTx tx, Channel channel, ChannelPool channelPool) {
Promise whenAlpn = channel.eventLoop().newPromise();
channel
.pipeline()
.addAfter(
SSL_HANDLER,
ALPN_HANDLER,
new ApplicationProtocolNegotiationHandler(ApplicationProtocolNames.HTTP_1_1) {
@Override
protected void configurePipeline(ChannelHandlerContext ctx, String protocol)
throws Exception {
switch (protocol) {
case ApplicationProtocolNames.HTTP_2:
LOGGER.debug(
"ALPN led to HTTP/2 with remote {}", tx.request.getUri().getHost());
tx.listener.onProtocolAwareness(true);
Http2Connection connection = new DefaultHttp2Connection(false);
ChannelPool.registerHttp2Connection(channel, connection);
HttpToHttp2ConnectionHandler http2Handler =
new HttpToHttp2ConnectionHandlerBuilder()
.initialSettings(DEFAULT_HTTP2_SETTINGS)
.connection(connection)
.frameListener(
new CustomDelegatingDecompressorFrameListener(
connection,
new NotAggregatingInboundHttp2ToHttpAdapter(
connection, whenAlpn)))
.build();
ctx.pipeline()
.addLast(HTTP2_HANDLER, http2Handler)
.addLast(CHUNKED_WRITER_HANDLER, new ForkedChunkedWriteHandler())
.addLast(
APP_HTTP2_HANDLER,
new Http2AppHandler(DefaultHttpClient.this, http2Handler, channelPool));
channelPool.offer(channel);
SslHandler sslHandler = (SslHandler) ctx.pipeline().get(SSL_HANDLER);
Set subjectAlternativeNames =
Tls.extractSubjectAlternativeNames(sslHandler.engine());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"TLS handshake successful: protocol={} cipher suite={}",
sslHandler.engine().getSession().getProtocol(),
sslHandler.engine().getSession().getCipherSuite());
}
if (subjectAlternativeNames.size() > 1) {
channelPool.offerCoalescedChannel(
subjectAlternativeNames,
(InetSocketAddress) channel.remoteAddress(),
channel,
tx.key);
}
break;
case ApplicationProtocolNames.HTTP_1_1:
LOGGER.debug(
"ALPN led to HTTP/1 with remote {}", tx.request.getUri().getHost());
if (tx.request.getHttp2PriorKnowledge()
== Http2PriorKnowledge.HTTP2_SUPPORTED) {
IllegalStateException e =
new IllegalStateException(
"HTTP/2 Prior knowledge was set on host "
+ tx.request.getUri().getHost()
+ " but it only supports HTTP/1");
whenAlpn.setFailure(e);
throw e;
}
tx.listener.onProtocolAwareness(false);
ctx.pipeline()
.addLast(HTTP_CLIENT_CODEC, newHttpClientCodec())
.addLast(INFLATER_HANDLER, new CustomHttpContentDecompressor())
.addLast(CHUNKED_WRITER_HANDLER, new ForkedChunkedWriteHandler())
.addLast(
APP_HTTP_HANDLER,
new HttpAppHandler(DefaultHttpClient.this, channelPool));
whenAlpn.setSuccess(null);
break;
default:
IllegalStateException e =
new IllegalStateException("Unknown protocol: " + protocol);
whenAlpn.setFailure(e);
ctx.close();
// FIXME do we really need to throw?
throw e;
}
}
});
whenAlpn.addListener(
f -> {
if (!f.isSuccess()) {
tx.listener.onThrowable(f.cause());
}
});
return whenAlpn;
}
@Override
public boolean isClosed() {
return closed.get();
}
@Override
public void flushClientIdChannels(long clientId, EventLoop eventLoop) {
if (eventLoop.inEventLoop()) {
eventLoopResources(eventLoop).channelPool.flushClientIdChannelPoolPartitions(clientId);
} else if (!eventLoop.isShutdown()) {
eventLoop.execute(
() ->
eventLoopResources(eventLoop)
.channelPool
.flushClientIdChannelPoolPartitions(clientId));
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy