Skip to content

Commit

Permalink
Merge pull request quarkusio#22236 from geoand/quarkusio#22214
Browse files Browse the repository at this point in the history
Add the ability to configure HTTP headers for REST Client Reactive
  • Loading branch information
geoand authored Dec 16, 2021
2 parents f6917e2 + 464962a commit 12bfcd9
Show file tree
Hide file tree
Showing 12 changed files with 86 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.restclient.config;

import java.util.Collections;
import java.util.Map;
import java.util.Optional;

import org.eclipse.microprofile.config.Config;
Expand All @@ -8,6 +10,7 @@

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;
import io.smallrye.config.SmallRyeConfig;

@ConfigGroup
public class RestClientConfig {
Expand Down Expand Up @@ -35,6 +38,7 @@ public class RestClientConfig {
EMPTY.connectionTTL = Optional.empty();
EMPTY.connectionPoolSize = Optional.empty();
EMPTY.maxRedirects = Optional.empty();
EMPTY.headers = Collections.emptyMap();
}

/**
Expand Down Expand Up @@ -161,6 +165,12 @@ public class RestClientConfig {
@ConfigItem
public Optional<Integer> maxRedirects;

/**
* The HTTP headers that should be applied to all requests of the rest client.
*/
@ConfigItem
public Map<String, String> headers;

public static RestClientConfig load(String configKey) {
final RestClientConfig instance = new RestClientConfig();

Expand All @@ -183,6 +193,7 @@ public static RestClientConfig load(String configKey) {
instance.connectionTTL = getConfigValue(configKey, "connection-ttl", Integer.class);
instance.connectionPoolSize = getConfigValue(configKey, "connection-pool-size", Integer.class);
instance.maxRedirects = getConfigValue(configKey, "max-redirects", Integer.class);
instance.headers = getConfigValues(configKey, "headers", String.class, String.class);

return instance;
}
Expand All @@ -209,6 +220,7 @@ public static RestClientConfig load(Class<?> interfaceClass) {
instance.connectionTTL = getConfigValue(interfaceClass, "connection-ttl", Integer.class);
instance.connectionPoolSize = getConfigValue(interfaceClass, "connection-pool-size", Integer.class);
instance.maxRedirects = getConfigValue(interfaceClass, "max-redirects", Integer.class);
instance.headers = getConfigValues(interfaceClass, "headers", String.class, String.class);

return instance;
}
Expand Down Expand Up @@ -237,6 +249,33 @@ private static <T> Optional<T> getConfigValue(Class<?> clientInterface, String f
return optional;
}

private static <K, V> Map<K, V> getConfigValues(String configKey, String fieldName, Class<K> keyType, Class<V> valueType) {
final SmallRyeConfig config = (SmallRyeConfig) ConfigProvider.getConfig();
Optional<Map<K, V>> optional = config.getOptionalValues(composePropertyKey(configKey, fieldName), keyType, valueType);
if (optional.isEmpty()) { // try to find property with quoted configKey
optional = config.getOptionalValues(composePropertyKey('"' + configKey + '"', fieldName), keyType, valueType);
}
return optional.isPresent() ? optional.get() : Collections.emptyMap();
}

private static <K, V> Map<K, V> getConfigValues(Class<?> clientInterface, String fieldName, Class<K> keyType,
Class<V> valueType) {
final SmallRyeConfig config = (SmallRyeConfig) ConfigProvider.getConfig();
// first try interface full name
Optional<Map<K, V>> optional = config.getOptionalValues(
composePropertyKey('"' + clientInterface.getName() + '"', fieldName),
keyType, valueType);
if (optional.isEmpty()) { // then interface simple name
optional = config.getOptionalValues(composePropertyKey(clientInterface.getSimpleName(), fieldName), keyType,
valueType);
}
if (optional.isEmpty()) { // lastly quoted interface simple name
optional = config.getOptionalValues(composePropertyKey('"' + clientInterface.getSimpleName() + '"', fieldName),
keyType, valueType);
}
return optional.isPresent() ? optional.get() : Collections.emptyMap();
}

private static String composePropertyKey(String key, String fieldName) {
return Constants.QUARKUS_CONFIG_PREFIX + key + "." + fieldName;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ void shouldHaveDependentScope() {

@Test
void shouldConnect() {
assertThat(client.echo("Bob")).isEqualTo("hello, Bob");
assertThat(client.echo("Bob")).isEqualTo("hello, Bob!!!");
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.rest.client.reactive;

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

import java.util.Set;

Expand Down Expand Up @@ -44,7 +45,7 @@ void shouldHaveSingletonScope() {

@Test
void clientShouldRespond() {
assertThat(client.echo("world!")).isEqualTo("hello, world!");
assertThat(client.echo("world")).isEqualTo("hi, world!");
}

@Test
Expand All @@ -61,10 +62,12 @@ void configurationShouldBeLoaded() {
verifyClientConfig(clientConfig, true);
assertThat(clientConfig.proxyAddress.isPresent()).isTrue();
assertThat(clientConfig.proxyAddress.get()).isEqualTo("localhost:8080");
assertThat(clientConfig.headers).containsOnly(entry("user-agent", "MP REST Client"), entry("foo", "bar"));

clientConfig = RestClientConfig.load("quoted-client-prefix");
assertThat(clientConfig.url.isPresent()).isTrue();
assertThat(clientConfig.url.get()).endsWith("/hello");
assertThat(clientConfig.headers).containsOnly(entry("foo", "bar"));

clientConfig = RestClientConfig.load("mp-client-prefix");
verifyClientConfig(clientConfig, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@
import javax.ws.rs.client.ClientResponseFilter;
import javax.ws.rs.core.MediaType;

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

@RegisterRestClient(baseUri = "http://localhost:8081/invalid-endpoint") // should be overridden by application.properties
@ClientHeaderParam(name = "suffix", value = "!!!") // should be overridden by application properties
@ClientHeaderParam(name = "comma", value = ",")
public interface HelloClientWithBaseUri {
@POST
@Consumes(MediaType.TEXT_PLAIN)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ void shouldHaveDependentScope() {

@Test
void shouldConnect() {
assertThat(client.echo("Bob")).isEqualTo("hello, Bob");
assertThat(client.echo("Bob")).isEqualTo("hello, Bob!!!");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ void shouldHaveDependentScope() {

@Test
void shouldConnect() {
assertThat(client.echo("Bob")).isEqualTo("hello, Bob");
assertThat(client.echo("Bob")).isEqualTo("hello, Bob!!!");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;

Expand All @@ -13,7 +14,11 @@
@Consumes(MediaType.TEXT_PLAIN)
public class EchoResource {
@POST
public String echo(String name, @Context Request request) {
return "hello, " + name;
public String echo(String name, @Context Request request, @Context HttpHeaders httpHeaders) {
var message = httpHeaders.getHeaderString("message");
var comma = httpHeaders.getHeaderString("comma");
var suffix = httpHeaders.getHeaderString("suffix");
return (message != null ? message : "hello") + (comma != null ? comma : "_") + " " + name
+ (suffix != null ? suffix : "");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ quarkus.rest-client."io.quarkus.rest.client.reactive.HelloClientWithBaseUri".hos
quarkus.rest-client."io.quarkus.rest.client.reactive.HelloClientWithBaseUri".connection-ttl=30000
quarkus.rest-client."io.quarkus.rest.client.reactive.HelloClientWithBaseUri".connection-pool-size=10
quarkus.rest-client."io.quarkus.rest.client.reactive.HelloClientWithBaseUri".max-redirects=5
quarkus.rest-client."io.quarkus.rest.client.reactive.HelloClientWithBaseUri".headers.message=hi
quarkus.rest-client."io.quarkus.rest.client.reactive.HelloClientWithBaseUri".headers.suffix=!

# client identified by a configKey
quarkus.rest-client.client-prefix.url=http://localhost:${quarkus.http.test-port:8081}/hello
Expand All @@ -40,9 +42,12 @@ quarkus.rest-client.client-prefix.hostname-verifier=io.quarkus.rest.client.react
quarkus.rest-client.client-prefix.connection-ttl=30000
quarkus.rest-client.client-prefix.connection-pool-size=10
quarkus.rest-client.client-prefix.max-redirects=5
quarkus.rest-client.client-prefix.headers.user-agent=MP REST Client
quarkus.rest-client.client-prefix.headers.foo=bar

# client identified by a quoted prefix
quarkus.rest-client."quoted-client-prefix".url=http://localhost:${quarkus.http.test-port:8081}/hello
quarkus.rest-client."quoted-client-prefix".headers.foo=bar

# invalid - unquoted prefix containing dot character will not be read
quarkus.rest-client.client.prefix.url=http://localhost:${quarkus.http.test-port:8081}/hello
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

Expand Down Expand Up @@ -92,6 +93,14 @@ private void configureCustomProperties(RestClientBuilder builder) {
int connectionTTLSeconds = connectionTTL.get() / 1000;
builder.property(QuarkusRestClientProperties.CONNECTION_TTL, connectionTTLSeconds);
}

Map<String, String> headers = clientConfigByClassName().headers;
if (headers.isEmpty()) {
headers = clientConfigByConfigKey().headers;
}
if ((headers != null) && !headers.isEmpty()) {
builder.property(QuarkusRestClientProperties.STATIC_HEADERS, headers);
}
}

private void configureProxy(RestClientBuilder builder) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

Expand Down Expand Up @@ -118,6 +119,7 @@ private static RestClientsConfig createSampleConfiguration() {
clientConfig.connectionTTL = Optional.of(30000); // value in milliseconds
clientConfig.connectionPoolSize = Optional.of(103);
clientConfig.maxRedirects = Optional.of(104);
clientConfig.headers = Collections.emptyMap();

RestClientsConfig configRoot = new RestClientsConfig();
configRoot.multipartPostEncoderMode = Optional.of("HTML5");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ public class QuarkusRestClientProperties {
* The size of the rest client connection pool.
*/
public static final String CONNECTION_POOL_SIZE = "io.quarkus.rest.client.connection-pool-size";

public static final String STATIC_HEADERS = "io.quarkus.rest.client.static-headers";
}
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ private QuarkusMultipartFormUpload setMultipartHeadersAndPrepareBody(HttpClientR
"Multipart form upload expects an entity of type MultipartForm, got: " + state.getEntity().getEntity());
}
MultivaluedMap<String, String> headerMap = state.getRequestHeaders().asMap();
updateRequestHeadersFromConfig(state, headerMap);
QuarkusMultipartForm multipartForm = (QuarkusMultipartForm) state.getEntity().getEntity();
multipartForm.preparePojos(state);

Expand All @@ -307,6 +308,8 @@ private Buffer setRequestHeadersAndPrepareBody(HttpClientRequest httpClientReque
RestClientRequestContext state)
throws IOException {
MultivaluedMap<String, String> headerMap = state.getRequestHeaders().asMap();
updateRequestHeadersFromConfig(state, headerMap);

Buffer actualEntity = AsyncInvokerImpl.EMPTY_BUFFER;
Entity<?> entity = state.getEntity();
if (entity != null) {
Expand All @@ -321,6 +324,15 @@ private Buffer setRequestHeadersAndPrepareBody(HttpClientRequest httpClientReque
return actualEntity;
}

private void updateRequestHeadersFromConfig(RestClientRequestContext state, MultivaluedMap<String, String> headerMap) {
Object staticHeaders = state.getConfiguration().getProperty(QuarkusRestClientProperties.STATIC_HEADERS);
if (staticHeaders instanceof Map) {
for (Map.Entry<String, String> entry : ((Map<String, String>) staticHeaders).entrySet()) {
headerMap.putSingle(entry.getKey(), entry.getValue());
}
}
}

private void setVertxHeaders(HttpClientRequest httpClientRequest, MultivaluedMap<String, String> headerMap) {
MultiMap vertxHttpHeaders = httpClientRequest.headers();
for (Map.Entry<String, List<String>> entry : headerMap.entrySet()) {
Expand Down

0 comments on commit 12bfcd9

Please sign in to comment.