From 87be3ed6db6e87624f347d116160fb343ad4d124 Mon Sep 17 00:00:00 2001 From: Jose Date: Tue, 30 Nov 2021 14:46:23 +0100 Subject: [PATCH] Rest Client Reactive: Add reactive flavor for ClientHeadersFactory Added reactive flavor of `ClientHeadersFactory` that allows doing blocking operations. For example: [source, java] ---- package org.acme.rest.client; import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; import javax.enterprise.context.ApplicationScoped; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import java.util.UUID; @ApplicationScoped public class GetTokenReactiveClientHeadersFactory extends ReactiveClientHeadersFactory { @Inject Service service; @Override public Uni> getHeaders(MultivaluedMap incomingHeaders) { return Uni.createFrom().item(() -> { MultivaluedHashMap newHeaders = new MultivaluedHashMap<>(); // perform blocking call newHeaders.add(HEADER_NAME, service.getToken()); return newHeaders; }); } } ---- Fixes https://github.com/quarkusio/quarkus/issues/20001 --- .../main/asciidoc/rest-client-reactive.adoc | 33 ++++++- ...ReactiveClientHeadersFromProviderTest.java | 86 +++++++++++++++++++ .../ReactiveClientHeadersFactory.java | 21 +++++ .../MicroProfileRestClientRequestFilter.java | 17 +++- 4 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/ReactiveClientHeadersFromProviderTest.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/ReactiveClientHeadersFactory.java diff --git a/docs/src/main/asciidoc/rest-client-reactive.adoc b/docs/src/main/asciidoc/rest-client-reactive.adoc index 9a0154176c378..35d76d3770418 100644 --- a/docs/src/main/asciidoc/rest-client-reactive.adoc +++ b/docs/src/main/asciidoc/rest-client-reactive.adoc @@ -515,7 +515,7 @@ More details about this can be found in https://smallrye.io/smallrye-mutiny/#_un There are a few ways in which you can specify custom headers for your REST calls: -- by registering a `ClientHeadersFactory` with the `@RegisterClientHeaders` annotation +- by registering a `ClientHeadersFactory` or a `ReactiveClientHeadersFactory` with the `@RegisterClientHeaders` annotation - by specifying the value of the header with `@ClientHeaderParam` - by specifying the value of the header by `@HeaderParam` @@ -591,6 +591,37 @@ To specify a value for `${header.value}`, simply put the following in your `appl header.value=value of the header ---- +Also, there is a reactive flavor of `ClientHeadersFactory` that allows doing blocking operations. For example: + +[source, java] +---- +package org.acme.rest.client; + +import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; + +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; +import java.util.UUID; + +@ApplicationScoped +public class GetTokenReactiveClientHeadersFactory extends ReactiveClientHeadersFactory { + + @Inject + Service service; + + @Override + public Uni> getHeaders(MultivaluedMap incomingHeaders) { + return Uni.createFrom().item(() -> { + MultivaluedHashMap newHeaders = new MultivaluedHashMap<>(); + // perform blocking call + newHeaders.add(HEADER_NAME, service.getToken()); + return newHeaders; + }); + } +} +---- + === Default header factory The `@RegisterClientHeaders` annotation can also be used without any custom factory specified. In that case the `DefaultClientHeadersFactoryImpl` factory will be used. diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/ReactiveClientHeadersFromProviderTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/ReactiveClientHeadersFromProviderTest.java new file mode 100644 index 0000000000000..dd9f3b2a45793 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/ReactiveClientHeadersFromProviderTest.java @@ -0,0 +1,86 @@ +package io.quarkus.rest.client.reactive.headers; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.Path; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.rest.client.reactive.ReactiveClientHeadersFactory; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.http.TestHTTPResource; +import io.smallrye.common.annotation.Blocking; +import io.smallrye.mutiny.Uni; + +public class ReactiveClientHeadersFromProviderTest { + private static final String HEADER_NAME = "my-header"; + private static final String HEADER_VALUE = "oifajrofijaeoir5gjaoasfaxcvcz"; + + @TestHTTPResource + URI baseUri; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar.addClasses(ReactiveClientHeadersFromProviderTest.Client.class) + .addAsResource( + new StringAsset("my.property-value=" + HEADER_VALUE), + "application.properties")); + + @Test + void shouldSetHeaderFromProperties() { + ReactiveClientHeadersFromProviderTest.Client client = RestClientBuilder.newBuilder().baseUri(baseUri) + .build(ReactiveClientHeadersFromProviderTest.Client.class); + + assertThat(client.getWithHeader()).isEqualTo(HEADER_VALUE); + } + + @Path("/") + @ApplicationScoped + public static class Resource { + @GET + public String returnHeaderValue(@HeaderParam(HEADER_NAME) String header) { + return header; + } + } + + @ApplicationScoped + public static class Service { + @Blocking + public String getValue() { + return HEADER_VALUE; + } + } + + @RegisterClientHeaders(CustomReactiveClientHeadersFactory.class) + public interface Client { + @GET + String getWithHeader(); + } + + public static class CustomReactiveClientHeadersFactory extends ReactiveClientHeadersFactory { + + @Inject + Service service; + + @Override + public Uni> getHeaders(MultivaluedMap incomingHeaders) { + return Uni.createFrom().item(() -> { + MultivaluedHashMap newHeaders = new MultivaluedHashMap<>(); + newHeaders.add(HEADER_NAME, service.getValue()); + return newHeaders; + }); + } + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/ReactiveClientHeadersFactory.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/ReactiveClientHeadersFactory.java new file mode 100644 index 0000000000000..fe0400aa1f751 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/ReactiveClientHeadersFactory.java @@ -0,0 +1,21 @@ +package io.quarkus.rest.client.reactive; + +import javax.ws.rs.core.MultivaluedMap; + +import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; + +import io.smallrye.mutiny.Uni; + +/** + * Reactive ClientHeadersFactory flavor for Quarkus rest-client reactive extension. + */ +public abstract class ReactiveClientHeadersFactory implements ClientHeadersFactory { + public abstract Uni> getHeaders(MultivaluedMap incomingHeaders); + + @Override + public final MultivaluedMap update(MultivaluedMap incomingHeaders, + MultivaluedMap clientOutgoingHeaders) { + throw new RuntimeException( + "Can't call `update` method in a Reactive context. Use `getHeaders` or implement ClientHeadersFactory."); + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientRequestFilter.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientRequestFilter.java index 5dbc0b7289163..6d8eb33ef579e 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientRequestFilter.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/MicroProfileRestClientRequestFilter.java @@ -13,9 +13,11 @@ import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; import org.eclipse.microprofile.rest.client.ext.DefaultClientHeadersFactoryImpl; +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext; import io.quarkus.arc.Arc; import io.quarkus.rest.client.reactive.HeaderFiller; +import io.quarkus.rest.client.reactive.ReactiveClientHeadersFactory; @Priority(Integer.MIN_VALUE) public class MicroProfileRestClientRequestFilter implements ClientRequestFilter { @@ -63,7 +65,20 @@ public void filter(ClientRequestContext requestContext) { } if (clientHeadersFactory != null) { - incomingHeaders = clientHeadersFactory.update(incomingHeaders, headers); + if (clientHeadersFactory instanceof ReactiveClientHeadersFactory) { + // reactive + ResteasyReactiveClientRequestContext reactiveRequestContext = (ResteasyReactiveClientRequestContext) requestContext; + ReactiveClientHeadersFactory reactiveClientHeadersFactory = (ReactiveClientHeadersFactory) clientHeadersFactory; + reactiveRequestContext.suspend(); + MultivaluedMap outgoingHeaders = incomingHeaders; + reactiveClientHeadersFactory.getHeaders(incomingHeaders).subscribe().with(newHeaders -> { + outgoingHeaders.putAll(newHeaders); + reactiveRequestContext.resume(); + }, reactiveRequestContext::resume); + } else { + // blocking + incomingHeaders = clientHeadersFactory.update(incomingHeaders, headers); + } } for (Map.Entry> headerEntry : incomingHeaders.entrySet()) {