From 004cd012e1cf94f8bb948d7c350f8c06df44af72 Mon Sep 17 00:00:00 2001 From: Gian Merlino Date: Tue, 29 Aug 2023 01:58:04 -0700 Subject: [PATCH] HttpClient: Include error handler on all connection attempts. (#14915) Currently we have an error handler for https connection attempts, but not for plaintext connection attempts. This leads to warnings like the following for plaintext connection errors: EXCEPTION, please implement org.jboss.netty.handler.codec.http.HttpContentDecompressor.exceptionCaught() for proper handling. This happens because if we don't add our own error handler, the last handler in the chain during a connection attempt is HttpContentDecompressor, which doesn't handle errors. The new error handler for plaintext doesn't do much: it just closes the channel. --- .../client/pool/ChannelResourceFactory.java | 87 ++++++++++++++----- .../util/http/client/JankyServersTest.java | 72 +++++++++++++++ 2 files changed, 135 insertions(+), 24 deletions(-) diff --git a/processing/src/main/java/org/apache/druid/java/util/http/client/pool/ChannelResourceFactory.java b/processing/src/main/java/org/apache/druid/java/util/http/client/pool/ChannelResourceFactory.java index d6465be305e..c6798945884 100644 --- a/processing/src/main/java/org/apache/druid/java/util/http/client/pool/ChannelResourceFactory.java +++ b/processing/src/main/java/org/apache/druid/java/util/http/client/pool/ChannelResourceFactory.java @@ -44,6 +44,7 @@ import org.jboss.netty.handler.codec.http.HttpVersion; import org.jboss.netty.handler.ssl.SslHandler; import org.jboss.netty.util.Timer; +import javax.annotation.Nullable; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; @@ -53,13 +54,15 @@ import java.net.URL; import java.util.concurrent.TimeUnit; /** + * */ public class ChannelResourceFactory implements ResourceFactory { private static final Logger log = new Logger(ChannelResourceFactory.class); private static final long DEFAULT_SSL_HANDSHAKE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10); - private static final String DRUID_PROXY_HANDLER = "druid_proxyHandler"; + private static final String PROXY_HANDLER_NAME = "druid-proxy"; + private static final String ERROR_HANDLER_NAME = "druid-connection-error"; private final ClientBootstrap bootstrap; private final SSLContext sslContext; @@ -128,7 +131,7 @@ public class ChannelResourceFactory implements ResourceFactory response = client + .go( + new Request(HttpMethod.GET, new URL(StringUtils.format("http://localhost:%d/", port))), + StatusResponseHandler.getInstance() + ); + + Throwable e = null; + try { + response.get(); + } + catch (ExecutionException e1) { + e = e1.getCause(); + e1.printStackTrace(); + } + + Assert.assertTrue("ChannelException thrown by 'get'", isChannelClosedException(e)); + } + finally { + lifecycle.stop(); + } + } + + @Test + public void testHttpsConnectionRefused() throws Throwable + { + final Lifecycle lifecycle = new Lifecycle(); + try { + final HttpClientConfig config = HttpClientConfig.builder().withSslContext(SSLContext.getDefault()).build(); + final HttpClient client = HttpClientInit.createClient(config, lifecycle); + + // Need to select a port that isn't being listened to. This approach finds an unused port in a racey way. + // Hopefully it works most of the time. + final ServerSocket sock = new ServerSocket(0); + final int port = sock.getLocalPort(); + sock.close(); + + final ListenableFuture response = client + .go( + new Request(HttpMethod.GET, new URL(StringUtils.format("https://localhost:%d/", port))), + StatusResponseHandler.getInstance() + ); + + Throwable e = null; + try { + response.get(); + } + catch (ExecutionException e1) { + e = e1.getCause(); + e1.printStackTrace(); + } + + Assert.assertTrue("ChannelException thrown by 'get'", isChannelClosedException(e)); + } + finally { + lifecycle.stop(); + } + } + public boolean isChannelClosedException(Throwable e) { return e instanceof ChannelException ||