diff --git a/src/main/java/io/vertx/core/http/impl/HttpChannelConnector.java b/src/main/java/io/vertx/core/http/impl/HttpChannelConnector.java index 84a6bd5079e..e5b4900a8a7 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpChannelConnector.java +++ b/src/main/java/io/vertx/core/http/impl/HttpChannelConnector.java @@ -27,6 +27,7 @@ import io.vertx.core.http.impl.pool.ConnectionListener; import io.vertx.core.http.impl.pool.ConnectionProvider; import io.vertx.core.impl.ContextImpl; +import io.vertx.core.net.ProxyOptions; import io.vertx.core.net.ProxyType; import io.vertx.core.net.SocketAddress; import io.vertx.core.net.impl.ChannelProvider; @@ -116,6 +117,13 @@ public void deactivate(HttpClientConnection conn) { } } + private boolean isExcluded(ProxyOptions options, String targetProxy) { + if(options.getExcludedHosts() == null || options.getExcludedHosts().isEmpty()) { + return false; + } + return options.getExcludedHosts().contains(targetProxy); + } + private void doConnect( ConnectionListener listener, ContextImpl context, @@ -129,7 +137,7 @@ private void doConnect( ChannelProvider channelProvider; // http proxy requests are handled in HttpClientImpl, everything else can use netty proxy handler - if (options.getProxyOptions() == null || !ssl && options.getProxyOptions().getType()== ProxyType.HTTP ) { + if (options.getProxyOptions() == null || !ssl && options.getProxyOptions().getType() == ProxyType.HTTP || isExcluded(options.getProxyOptions(), host)) { channelProvider = ChannelProvider.INSTANCE; } else { channelProvider = ProxyChannelProvider.INSTANCE; diff --git a/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java b/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java index e433b1cea3e..8eceff9542f 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java +++ b/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java @@ -1005,7 +1005,7 @@ private HttpClientRequest createRequest(HttpMethod method, String protocol, Stri } checkClosed(); HttpClientRequest req; - boolean useProxy = !useSSL && proxyType == ProxyType.HTTP; + boolean useProxy = useProxy(useSSL, options.getProxyOptions(), host); if (useProxy) { final int defaultPort = protocol.equals("ftp") ? 21 : 80; final String addPort = (port != -1 && port != defaultPort) ? (":" + port) : ""; @@ -1030,6 +1030,17 @@ private HttpClientRequest createRequest(HttpMethod method, String protocol, Stri return req; } + private boolean useProxy(boolean isSsl, ProxyOptions proxyOptions, String targetHost) { + if(!isSsl && proxyType == ProxyType.HTTP) { + if(proxyOptions.getExcludedHosts().isEmpty()) { + return true; + } else { + return !proxyOptions.getExcludedHosts().contains(targetHost); + } + } + return false; + } + private synchronized void checkClosed() { if (closed) { throw new IllegalStateException("Client is closed"); diff --git a/src/main/java/io/vertx/core/net/ProxyOptions.java b/src/main/java/io/vertx/core/net/ProxyOptions.java index f246dc0bff6..876455efd25 100644 --- a/src/main/java/io/vertx/core/net/ProxyOptions.java +++ b/src/main/java/io/vertx/core/net/ProxyOptions.java @@ -11,7 +11,7 @@ package io.vertx.core.net; -import java.util.Objects; +import java.util.*; import io.vertx.codegen.annotations.DataObject; import io.vertx.core.json.JsonObject; @@ -46,6 +46,7 @@ public class ProxyOptions { private String username; private String password; private ProxyType type; + private Set exludedHosts; /** * Default constructor. @@ -54,6 +55,7 @@ public ProxyOptions() { host = DEFAULT_HOST; port = DEFAULT_PORT; type = DEFAULT_TYPE; + exludedHosts = new HashSet<>(0); } /** @@ -67,6 +69,7 @@ public ProxyOptions(ProxyOptions other) { username = other.getUsername(); password = other.getPassword(); type = other.getType(); + exludedHosts = other.getExcludedHosts(); } /** @@ -199,6 +202,39 @@ public ProxyOptions setType(ProxyType type) { return this; } + /** + * Set list of excluded hosts. + * + * @param excludedHosts list of hosts to exclude from proxy logic + * @return a reference to this, so the API can be used fluently + */ + public ProxyOptions setExcludedHosts(final Set excludedHosts) { + Objects.requireNonNull(excludedHosts, "Excluded host cannot be null"); + this.exludedHosts = excludedHosts; + return this; + } + + /** + * Add host to existing excludedHost list. + * + * @param excludedHost host excluded from proxy logic + * @return a reference to this, so the API can be used fluently + */ + public ProxyOptions addExcludedHost(final String excludedHost) { + Objects.requireNonNull(excludedHost, "Excluded host cannot be null"); + this.exludedHosts.add(excludedHost); + return this; + } + + /** + * Get excluded hosts. + * + * @return list of hosts that should not be proxied. + */ + public Set getExcludedHosts() { + return exludedHosts; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -212,6 +248,7 @@ public boolean equals(Object o) { if (port != that.port) return false; if (!Objects.equals(password, that.password)) return false; if (!Objects.equals(username, that.username)) return false; + if (!Objects.equals(exludedHosts, that.exludedHosts)) return false; return true; } @@ -224,6 +261,7 @@ public int hashCode() { result = 31 * result + port; result = 31 * result + (password != null ? password.hashCode() : 0); result = 31 * result + (username != null ? username.hashCode() : 0); + result = 31 * result + exludedHosts.hashCode(); return result; } } diff --git a/src/test/java/io/vertx/test/core/Http1xTest.java b/src/test/java/io/vertx/test/core/Http1xTest.java index eec90a1ba6b..e05981c2b23 100644 --- a/src/test/java/io/vertx/test/core/Http1xTest.java +++ b/src/test/java/io/vertx/test/core/Http1xTest.java @@ -2955,6 +2955,32 @@ public void testHttpProxyRequest() throws Exception { testHttpProxyRequest2(client.get(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, "/")); } + @Test + public void testHttpHostExclusionOfProxyRequest() throws Exception { + startProxy(null, ProxyType.HTTP); + client.close(); + client = vertx.createHttpClient(new HttpClientOptions() + .setProxyOptions(new ProxyOptions().setType(ProxyType.HTTP).addExcludedHost("localhost").setHost("google.com").setPort(proxy.getPort()))); + + HttpClientRequest clientReq = client.get(DEFAULT_HTTP_PORT, DEFAULT_HTTP_HOST, "/"); + + server.requestHandler(req -> { + req.response().end(); + }); + + //because we excluded localhost from proxying logic, it will not reach google.com but localhost. + server.listen(onSuccess(s -> { + clientReq.handler(resp -> { + assertEquals(200, resp.statusCode()); + testComplete(); + }); + clientReq.exceptionHandler(this::fail); + clientReq.end(); + })); + + await(); + } + @Test public void testHttpProxyRequestOverrideClientSsl() throws Exception { startProxy(null, ProxyType.HTTP);