diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientConfig.java b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientConfig.java index fafe00369eb93..b42bbc23055cb 100644 --- a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientConfig.java +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientConfig.java @@ -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; @@ -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 { @@ -35,6 +38,7 @@ public class RestClientConfig { EMPTY.connectionTTL = Optional.empty(); EMPTY.connectionPoolSize = Optional.empty(); EMPTY.maxRedirects = Optional.empty(); + EMPTY.headers = Collections.emptyMap(); } /** @@ -161,6 +165,12 @@ public class RestClientConfig { @ConfigItem public Optional maxRedirects; + /** + * The HTTP headers that should be applied to all requests of the rest client. + */ + @ConfigItem + public Map headers; + public static RestClientConfig load(String configKey) { final RestClientConfig instance = new RestClientConfig(); @@ -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; } @@ -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; } @@ -237,6 +249,33 @@ private static Optional getConfigValue(Class clientInterface, String f return optional; } + private static Map getConfigValues(String configKey, String fieldName, Class keyType, Class valueType) { + final SmallRyeConfig config = (SmallRyeConfig) ConfigProvider.getConfig(); + Optional> 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 Map getConfigValues(Class clientInterface, String fieldName, Class keyType, + Class valueType) { + final SmallRyeConfig config = (SmallRyeConfig) ConfigProvider.getConfig(); + // first try interface full name + Optional> 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; } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ClassNameScopeOverrideTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ClassNameScopeOverrideTest.java index faf741481caae..d099c885ba2cd 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ClassNameScopeOverrideTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ClassNameScopeOverrideTest.java @@ -36,6 +36,6 @@ void shouldHaveDependentScope() { @Test void shouldConnect() { - assertThat(client.echo("Bob")).isEqualTo("hello, Bob"); + assertThat(client.echo("Bob")).isEqualTo("hello, Bob!!!"); } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ConfigurationTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ConfigurationTest.java index d2bc39d87cdb1..3b196210f91b0 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ConfigurationTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ConfigurationTest.java @@ -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; @@ -44,7 +45,7 @@ void shouldHaveSingletonScope() { @Test void clientShouldRespond() { - assertThat(client.echo("world!")).isEqualTo("hello, world!"); + assertThat(client.echo("world")).isEqualTo("hi, world!"); } @Test @@ -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); diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloClientWithBaseUri.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloClientWithBaseUri.java index f9162df8add58..1a947b9261921 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloClientWithBaseUri.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloClientWithBaseUri.java @@ -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) diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MpClassNameScopeOverrideTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MpClassNameScopeOverrideTest.java index 013e31fe1bab1..550d8f8b725da 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MpClassNameScopeOverrideTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MpClassNameScopeOverrideTest.java @@ -36,6 +36,6 @@ void shouldHaveDependentScope() { @Test void shouldConnect() { - assertThat(client.echo("Bob")).isEqualTo("hello, Bob"); + assertThat(client.echo("Bob")).isEqualTo("hello, Bob!!!"); } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ShortNameScopeOverrideTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ShortNameScopeOverrideTest.java index ce127912ea66c..5125d0e1a431f 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ShortNameScopeOverrideTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ShortNameScopeOverrideTest.java @@ -36,6 +36,6 @@ void shouldHaveDependentScope() { @Test void shouldConnect() { - assertThat(client.echo("Bob")).isEqualTo("hello, Bob"); + assertThat(client.echo("Bob")).isEqualTo("hello, Bob!!!"); } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/configuration/EchoResource.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/configuration/EchoResource.java index 2fe910d4973b8..b023965ff7cab 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/configuration/EchoResource.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/configuration/EchoResource.java @@ -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; @@ -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 : ""); } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/resources/configuration-test-application.properties b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/resources/configuration-test-application.properties index fb26fd6217831..53f7e29799191 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/resources/configuration-test-application.properties +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/resources/configuration-test-application.properties @@ -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 @@ -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 diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java index bc0106ac8d99c..8131cc7f5de2a 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java @@ -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; @@ -92,6 +93,14 @@ private void configureCustomProperties(RestClientBuilder builder) { int connectionTTLSeconds = connectionTTL.get() / 1000; builder.property(QuarkusRestClientProperties.CONNECTION_TTL, connectionTTLSeconds); } + + Map 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) { diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java index f80d41ebff3a8..92f48575c7edd 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java @@ -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; @@ -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"); diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/api/QuarkusRestClientProperties.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/api/QuarkusRestClientProperties.java index 127924a6ca913..9491d9cf21b87 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/api/QuarkusRestClientProperties.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/api/QuarkusRestClientProperties.java @@ -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"; } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java index 7909b823f22e7..7595861b54d11 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java @@ -281,6 +281,7 @@ private QuarkusMultipartFormUpload setMultipartHeadersAndPrepareBody(HttpClientR "Multipart form upload expects an entity of type MultipartForm, got: " + state.getEntity().getEntity()); } MultivaluedMap headerMap = state.getRequestHeaders().asMap(); + updateRequestHeadersFromConfig(state, headerMap); QuarkusMultipartForm multipartForm = (QuarkusMultipartForm) state.getEntity().getEntity(); multipartForm.preparePojos(state); @@ -307,6 +308,8 @@ private Buffer setRequestHeadersAndPrepareBody(HttpClientRequest httpClientReque RestClientRequestContext state) throws IOException { MultivaluedMap headerMap = state.getRequestHeaders().asMap(); + updateRequestHeadersFromConfig(state, headerMap); + Buffer actualEntity = AsyncInvokerImpl.EMPTY_BUFFER; Entity entity = state.getEntity(); if (entity != null) { @@ -321,6 +324,15 @@ private Buffer setRequestHeadersAndPrepareBody(HttpClientRequest httpClientReque return actualEntity; } + private void updateRequestHeadersFromConfig(RestClientRequestContext state, MultivaluedMap headerMap) { + Object staticHeaders = state.getConfiguration().getProperty(QuarkusRestClientProperties.STATIC_HEADERS); + if (staticHeaders instanceof Map) { + for (Map.Entry entry : ((Map) staticHeaders).entrySet()) { + headerMap.putSingle(entry.getKey(), entry.getValue()); + } + } + } + private void setVertxHeaders(HttpClientRequest httpClientRequest, MultivaluedMap headerMap) { MultiMap vertxHttpHeaders = httpClientRequest.headers(); for (Map.Entry> entry : headerMap.entrySet()) {