Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support non-proxy hosts in REST Client Reactive #22649

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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