Skip to content

Commit

Permalink
Merge pull request #22649 from michalszynkiewicz/rest-client-reactive…
Browse files Browse the repository at this point in the history
…-no-proxy-hosts

Support non-proxy hosts in REST Client Reactive
  • Loading branch information
michalszynkiewicz authored Jan 5, 2022
2 parents 8a12e50 + 6de74ac commit 99049e5
Show file tree
Hide file tree
Showing 16 changed files with 219 additions and 33 deletions.
11 changes: 9 additions & 2 deletions docs/src/main/asciidoc/rest-client-reactive.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -702,9 +702,15 @@ All the available modes are described in the link:https://netty.io/4.1/api/io/ne
REST Client Reactive supports sending requests through a proxy.
It honors the JVM settings for it but also allows to specify both:

* global client proxy settings, with `quarkus.rest-client.proxy-address`, `quarkus.rest-client.proxy-user`, `quarkus.rest-client.proxy-password`
* global client proxy settings, with `quarkus.rest-client.proxy-address`, `quarkus.rest-client.proxy-user`, `quarkus.rest-client.proxy-password`, `quarkus.rest-client.non-proxy-hosts`

* per-client proxy settings, with `quarkus.rest-client.<my-client>.proxy-address`, etc. These are applied only to clients injected with CDI, that is the ones created with `@RegisterRestClient`

If `proxy-address` is set on the client level, the client uses its specific proxy settings. No proxy settings are propagated from the global configuration or JVM properties.

If `proxy-address` is not set for the client but is set on the global level, the client uses the global settings.
Otherwise, the client uses the JVM settings.

* per-client proxy settings, with `quarkus.rest-client.<my-client>.proxy-address`, etc

An example configuration for setting proxy:

Expand All @@ -714,6 +720,7 @@ An example configuration for setting proxy:
quarkus.rest-client.proxy-address=localhost:8182
quarkus.rest-client.proxy-user=<proxy user name>
quarkus.rest-client.proxy-password=<proxy password>
quarkus.rest-client.non-proxy-hosts=example.com
# per-client configuration overrides the global settings for a specific client
quarkus.rest-client.my-client.proxy-address=localhost:8183
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class RestClientConfig {
EMPTY.proxyAddress = Optional.empty();
EMPTY.proxyUser = Optional.empty();
EMPTY.proxyPassword = Optional.empty();
EMPTY.nonProxyHosts = Optional.empty();
EMPTY.queryParamStyle = Optional.empty();
EMPTY.trustStore = Optional.empty();
EMPTY.trustStorePassword = Optional.empty();
Expand Down Expand Up @@ -93,6 +94,8 @@ public class RestClientConfig {
/**
* A string value in the form of `<proxyHost>:<proxyPort>` that specifies the HTTP proxy server hostname
* (or IP address) and port for requests of this client to use.
*
* Use `none` to disable proxy
*/
@ConfigItem
public Optional<String> proxyAddress;
Expand All @@ -113,6 +116,14 @@ public class RestClientConfig {
@ConfigItem
public Optional<String> proxyPassword;

/**
* Hosts to access without proxy
*
* This property is applicable to reactive REST clients only.
*/
@ConfigItem
public Optional<String> nonProxyHosts;

/**
* An enumerated type string value with possible values of "MULTI_PAIRS" (default), "COMMA_SEPARATED",
* or "ARRAY_PAIRS" that specifies the format in which multiple values for the same query parameter is used.
Expand Down Expand Up @@ -202,6 +213,7 @@ public static RestClientConfig load(String configKey) {
instance.proxyAddress = getConfigValue(configKey, "proxy-address", String.class);
instance.proxyUser = getConfigValue(configKey, "proxy-user", String.class);
instance.proxyPassword = getConfigValue(configKey, "proxy-password", String.class);
instance.nonProxyHosts = getConfigValue(configKey, "non-proxy-hosts", String.class);
instance.queryParamStyle = getConfigValue(configKey, "query-param-style", QueryParamStyle.class);
instance.trustStore = getConfigValue(configKey, "trust-store", String.class);
instance.trustStorePassword = getConfigValue(configKey, "trust-store-password", String.class);
Expand Down Expand Up @@ -231,6 +243,7 @@ public static RestClientConfig load(Class<?> interfaceClass) {
instance.proxyAddress = getConfigValue(interfaceClass, "proxy-address", String.class);
instance.proxyUser = getConfigValue(interfaceClass, "proxy-user", String.class);
instance.proxyPassword = getConfigValue(interfaceClass, "proxy-password", String.class);
instance.nonProxyHosts = getConfigValue(interfaceClass, "non-proxy-hosts", String.class);
instance.queryParamStyle = getConfigValue(interfaceClass, "query-param-style", QueryParamStyle.class);
instance.trustStore = getConfigValue(interfaceClass, "trust-store", String.class);
instance.trustStorePassword = getConfigValue(interfaceClass, "trust-store-password", String.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,30 @@ public class RestClientsConfig {
public Optional<String> proxyAddress;

/**
* Proxy username, equivalent to the http.proxy or https.proxy system property
* Proxy username, equivalent to the http.proxy or https.proxy JVM settings.
*
* This property is applicable to reactive REST clients only.
*/
@ConfigItem
public Optional<String> proxyUser;

/**
* Proxy password, equivalent to the http.proxyPassword or https.proxyPassword system property
* Proxy password, equivalent to the http.proxyPassword or https.proxyPassword JVM settings.
*
* This property is applicable to reactive REST clients only.
*/
@ConfigItem
public Optional<String> proxyPassword;

/**
* Hosts to access without proxy, similar to the http.nonProxyHosts or https.nonProxyHosts JVM settings.
* Please note that unlike the JVM settings, this property is empty by default
*
* This property is applicable to reactive REST clients only.
*/
@ConfigItem
public Optional<String> nonProxyHosts;

public RestClientLoggingConfig logging;

public RestClientConfig getClientConfig(String configKey) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.quarkus.rest.client.reactive.proxy;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

@RegisterRestClient(configKey = "client5")
@Path("/resource")
public interface Client5 {
@GET
Response get();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.quarkus.rest.client.reactive.proxy;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

@RegisterRestClient(configKey = "client6")
@Path("/resource")
public interface Client6 {
@GET
Response get();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.quarkus.rest.client.reactive.proxy;

import static org.assertj.core.api.Assertions.assertThat;

import javax.ws.rs.core.Response;

import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;

public class GlobalNonProxyTest extends ProxyTestBase {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot(
jar -> jar.addClasses(Client1.class, Client2.class, Client3.class, Client4.class, Client5.class,
Client6.class, ViaHeaderReturningResource.class))
.withConfigurationResource("global-non-proxy-test-application.properties");

@RestClient
Client1 client1;

@Test
void shouldNotApplyProxyIfNonProxyMatches() {
assertThat(client1.get().readEntity(String.class)).isEqualTo(NO_PROXY);
}

@Test
void shouldProxyBuilderWithPerClientSettings() {
Response response = RestClientBuilder.newBuilder().baseUri(appUri)
.build(Client1.class).get();
assertThat(response.readEntity(String.class)).isEqualTo(NO_PROXY);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ public class ProxyTest extends ProxyTestBase {
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot(
jar -> jar.addClasses(Client1.class, Client2.class, Client3.class, Client4.class,
ViaHeaderReturningResource.class))
jar -> jar.addClasses(Client1.class, Client2.class, Client3.class, Client4.class, Client5.class,
Client6.class, ViaHeaderReturningResource.class))
.withConfigurationResource("proxy-test-application.properties");

@RestClient
Expand All @@ -32,27 +32,45 @@ public class ProxyTest extends ProxyTestBase {
Client3 client3;
@RestClient
Client4 client4;
@RestClient
Client5 clientWithNonProxyHost;
@RestClient
Client6 clientWithProxyHostNone;

@Test
void shouldProxyCDIWithPerClientSettings() {
assertThat(client1.get().readEntity(String.class)).isEqualTo(PROXY_8181);
assertThat(client2.get().readEntity(String.class)).isEqualTo(PROXY_8181);
assertThat(client3.get().readEntity(String.class)).isEqualTo(PROXY_8182);
assertThat(client4.get().readEntity(String.class)).isEqualTo(AUTHENTICATED_PROXY);
assertThat(clientWithNonProxyHost.get().readEntity(String.class)).isEqualTo(NO_PROXY);
assertThat(clientWithProxyHostNone.get().readEntity(String.class)).isEqualTo(NO_PROXY);
}

@Test
void shouldProxyBuilderWithPerClientSettings() {
Response response1 = RestClientBuilder.newBuilder().baseUri(appUri).proxyAddress("localhost", 8181)
Response response = RestClientBuilder.newBuilder().baseUri(appUri).proxyAddress("localhost", 8181)
.build(Client1.class).get();
assertThat(response1.readEntity(String.class)).isEqualTo(PROXY_8181);
Response response2 = RestClientBuilder.newBuilder().baseUri(appUri).build(Client2.class).get();
assertThat(response2.readEntity(String.class)).isEqualTo(PROXY_8182);
assertThat(response.readEntity(String.class)).isEqualTo(PROXY_8181);

response = RestClientBuilder.newBuilder().baseUri(appUri).build(Client2.class).get();
assertThat(response.readEntity(String.class)).isEqualTo(PROXY_8182);

RestClientBuilderImpl restClientBuilder = (RestClientBuilderImpl) RestClientBuilder.newBuilder();
Response response3 = restClientBuilder.baseUri(appUri).proxyAddress("localhost", 8183)
response = restClientBuilder.baseUri(appUri).proxyAddress("localhost", 8183)
.proxyUser("admin").proxyPassword("r00t")
.build(Client1.class).get();
assertThat(response3.readEntity(String.class)).isEqualTo(AUTHENTICATED_PROXY);
assertThat(response.readEntity(String.class)).isEqualTo(AUTHENTICATED_PROXY);

restClientBuilder = (RestClientBuilderImpl) RestClientBuilder.newBuilder();
response = restClientBuilder.baseUri(appUri).proxyAddress("localhost", 8183)
.proxyUser("admin").proxyPassword("r00t").nonProxyHosts("example.com|localhost")
.build(Client1.class).get();
assertThat(response.readEntity(String.class)).isEqualTo(NO_PROXY);

restClientBuilder = (RestClientBuilderImpl) RestClientBuilder.newBuilder();
response = restClientBuilder.baseUri(appUri).proxyAddress("none", -1)
.build(Client1.class).get();
assertThat(response.readEntity(String.class)).isEqualTo(NO_PROXY);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

public abstract class ProxyTestBase {

public static final String NO_PROXY = "noProxy";

public static final String PROXY_8181 = "proxyServer1";
public static final String PROXY_8182 = "proxyServer2";
public static final String AUTHENTICATED_PROXY = "authenticatedProxy";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junitpioneer.jupiter.SetSystemProperty;

import io.quarkus.rest.client.reactive.runtime.RestClientBuilderImpl;
import io.quarkus.test.QuarkusUnitTest;

public class SystemPropertyProxyTest extends ProxyTestBase {
Expand All @@ -32,11 +33,21 @@ public class SystemPropertyProxyTest extends ProxyTestBase {
@Test
@SetSystemProperty(key = "http.proxyHost", value = "localhost")
@SetSystemProperty(key = "http.proxyPort", value = "8182")
// the default nonProxyHosts skip proxying localhost
@SetSystemProperty(key = "http.nonProxyHosts", value = "example.com")
void shouldProxyWithSystemProperties() {
assertThat(client1.get().readEntity(String.class)).isEqualTo(PROXY_8182);
assertThat(client2.get().readEntity(String.class)).isEqualTo(PROXY_8181);

Response response2 = RestClientBuilder.newBuilder().baseUri(appUri).build(Client2.class).get();
assertThat(response2.readEntity(String.class)).isEqualTo(PROXY_8182);
Response response = RestClientBuilder.newBuilder().baseUri(appUri).build(Client2.class).get();
assertThat(response.readEntity(String.class)).isEqualTo(PROXY_8182);

response = RestClientBuilder.newBuilder().baseUri(appUri).build(Client2.class).get();
assertThat(response.readEntity(String.class)).isEqualTo(PROXY_8182);

RestClientBuilderImpl restClientBuilder = (RestClientBuilderImpl) RestClientBuilder.newBuilder();
response = restClientBuilder.baseUri(appUri).proxyAddress("none", -1)
.build(Client2.class).get();
assertThat(response.readEntity(String.class)).isEqualTo(NO_PROXY);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class SystemPropertyProxyWithAuthTest extends ProxyTestBase {
@SetSystemProperty(key = "http.proxyPort", value = "8183")
@SetSystemProperty(key = "http.proxyUser", value = "admin")
@SetSystemProperty(key = "http.proxyPassword", value = "r00t")
@SetSystemProperty(key = "http.nonProxyHosts", value = "example.com")
void shouldProxyWithCredentialsFromProperties() {
assertThat(client1.get().readEntity(String.class)).isEqualTo(AUTHENTICATED_PROXY);
assertThat(client2.get().readEntity(String.class)).isEqualTo(PROXY_8181);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
quarkus.rest-client.proxy-address=localhost:8182
quarkus.rest-client.non-proxy-hosts=localhost

quarkus.rest-client.client1.url=http://localhost:${quarkus.http.test-port:8081}/
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,13 @@ quarkus.rest-client.client4.proxy-user=admin
quarkus.rest-client.client4.proxy-password=r00t
quarkus.rest-client.client4.url=http://localhost:${quarkus.http.test-port:8081}/

quarkus.rest-client.client5.proxy-address=localhost:8183
quarkus.rest-client.client5.proxy-user=admin
quarkus.rest-client.client5.proxy-password=r00t
quarkus.rest-client.client5.url=http://localhost:${quarkus.http.test-port:8081}/
quarkus.rest-client.client5.non-proxy-hosts=localhost

quarkus.rest-client.client6.proxy-address=none
quarkus.rest-client.client6.url=http://localhost:${quarkus.http.test-port:8081}/

quarkus.rest-client.proxy-address=localhost:8182
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public class RestClientBuilderImpl implements RestClientBuilder {
private Integer proxyPort;
private String proxyUser;
private String proxyPassword;
private String nonProxyHosts;

@Override
public RestClientBuilderImpl baseUrl(URL url) {
Expand Down Expand Up @@ -116,7 +117,7 @@ public RestClientBuilderImpl proxyAddress(final String proxyHost, final int prox
if (proxyHost == null) {
throw new IllegalArgumentException("proxyHost must not be null");
}
if (proxyPort <= 0 || proxyPort > 65535) {
if ((proxyPort <= 0 || proxyPort > 65535) && !proxyHost.equals("none")) {
throw new IllegalArgumentException("Invalid port number");
}
this.proxyHost = proxyHost;
Expand All @@ -135,6 +136,11 @@ public RestClientBuilderImpl proxyUser(String proxyUser) {
return this;
}

public RestClientBuilderImpl nonProxyHosts(String nonProxyHosts) {
this.nonProxyHosts = nonProxyHosts;
return this;
}

@Override
public RestClientBuilderImpl executorService(ExecutorService executor) {
throw new IllegalArgumentException("Specifying executor service is not supported. " +
Expand Down Expand Up @@ -309,11 +315,11 @@ public <T> T build(Class<T> aClass) throws IllegalStateException, RestClientDefi
clientBuilder.trustAll(trustAll);

if (proxyHost != null) {
configureProxy(proxyHost, proxyPort, proxyUser, proxyPassword);
configureProxy(proxyHost, proxyPort, proxyUser, proxyPassword, nonProxyHosts);
} else if (restClientsConfig.proxyAddress.isPresent()) {
HostAndPort globalProxy = ProxyAddressUtil.parseAddress(restClientsConfig.proxyAddress.get());
configureProxy(globalProxy.host, globalProxy.port, restClientsConfig.proxyUser.orElse(null),
restClientsConfig.proxyPassword.orElse(null));
restClientsConfig.proxyPassword.orElse(null), restClientsConfig.nonProxyHosts.orElse(null));
}
ClientImpl client = clientBuilder.build();
WebTargetImpl target = (WebTargetImpl) client.target(uri);
Expand All @@ -325,13 +331,18 @@ public <T> T build(Class<T> aClass) throws IllegalStateException, RestClientDefi
}
}

private void configureProxy(String proxyHost, Integer proxyPort, String proxyUser, String proxyPassword) {
private void configureProxy(String proxyHost, Integer proxyPort, String proxyUser, String proxyPassword,
String nonProxyHosts) {
if (proxyHost != null) {
clientBuilder.proxy(proxyHost, proxyPort);
if (proxyUser != null && proxyPassword != null) {
clientBuilder.proxyUser(proxyUser);
clientBuilder.proxyPassword(proxyPassword);
}

if (nonProxyHosts != null) {
clientBuilder.nonProxyHosts(nonProxyHosts);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,21 @@ private void configureProxy(RestClientBuilderImpl builder) {
if (maybeProxy.isEmpty()) {
return;
}
ProxyAddressUtil.HostAndPort hostAndPort = ProxyAddressUtil.parseAddress(maybeProxy.get());
builder.proxyAddress(hostAndPort.host, hostAndPort.port);

oneOf(clientConfigByClassName().proxyUser, clientConfigByConfigKey().proxyUser)
.ifPresent(builder::proxyUser);
oneOf(clientConfigByClassName().proxyPassword, clientConfigByConfigKey().proxyPassword)
.ifPresent(builder::proxyPassword);
String proxyAddress = maybeProxy.get();
if (proxyAddress.equals("none")) {
builder.proxyAddress("none", 0);
} else {
ProxyAddressUtil.HostAndPort hostAndPort = ProxyAddressUtil.parseAddress(proxyAddress);
builder.proxyAddress(hostAndPort.host, hostAndPort.port);

oneOf(clientConfigByClassName().proxyUser, clientConfigByConfigKey().proxyUser)
.ifPresent(builder::proxyUser);
oneOf(clientConfigByClassName().proxyPassword, clientConfigByConfigKey().proxyPassword)
.ifPresent(builder::proxyPassword);
oneOf(clientConfigByClassName().nonProxyHosts, clientConfigByConfigKey().nonProxyHosts)
.ifPresent(builder::nonProxyHosts);
}
}

private void configureQueryParamStyle(RestClientBuilder builder) {
Expand Down
Loading

0 comments on commit 99049e5

Please sign in to comment.