diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/RestClientUriParameterTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/RestClientUriParameterTest.java new file mode 100644 index 0000000000000..c1fe6f1afcee3 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/RestClientUriParameterTest.java @@ -0,0 +1,97 @@ +package io.quarkus.micrometer.deployment.binder; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.search.Search; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.quarkus.rest.client.reactive.Url; +import io.quarkus.test.QuarkusUnitTest; + +public class RestClientUriParameterTest { + + final static SimpleMeterRegistry registry = new SimpleMeterRegistry(); + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot( + jar -> jar.addClasses(Resource.class, Client.class)) + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") + .overrideConfigKey("quarkus.rest-client.\"client\".url", "http://does-not-exist.io"); + + @RestClient + Client client; + + @ConfigProperty(name = "quarkus.http.test-port") + Integer testPort; + + @BeforeAll + static void setRegistry() { + Metrics.addRegistry(registry); + } + + @AfterAll() + static void removeRegistry() { + Metrics.removeRegistry(registry); + } + + @Test + public void testOverride() { + String result = client.getById("http://localhost:" + testPort, "bar"); + assertEquals("bar", result); + + Timer clientTimer = registry.find("http.client.requests").timer(); + assertNotNull(clientTimer); + assertEquals("/example/{id}", clientTimer.getId().getTag("uri")); + } + + private Search getMeter(String name) { + return registry.find(name); + } + + @Path("/example") + @RegisterRestClient(baseUri = "http://dummy") + public interface Client { + + @GET + @Path("/{id}") + String getById(@Url String baseUri, @PathParam("id") String id); + } + + @Path("/example") + public static class Resource { + + @RestClient + Client client; + + @GET + @Path("/{id}") + @Produces(MediaType.TEXT_PLAIN) + public String example() { + return "bar"; + } + + @GET + @Path("/call") + @Produces(MediaType.TEXT_PLAIN) + public String call() { + return client.getById("http://localhost:8080", "1"); + } + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpRootTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpRootTest.java index d153fdb65fc5b..8bca89242a0e7 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpRootTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagWithHttpRootTest.java @@ -44,6 +44,14 @@ public class UriTagWithHttpRootTest { @Inject MeterRegistry registry; + @Test + public void testClient() throws InterruptedException { + when().get("/ping/one").then().statusCode(200); + Util.waitForMeters(registry.find("http.server.requests").timers(), 1); + Util.waitForMeters(registry.find("http.client.requests").timers(), 1); + Assertions.assertEquals(1, registry.find("http.client.requests").tag("uri", "/pong/{message}").timers().size()); + } + @Test public void testRequestUris() throws Exception { RestAssured.basePath = "/"; diff --git a/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index 2f4ac2e80302f..bf1a4a4099909 100644 --- a/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -955,8 +955,7 @@ A more full example of generated client (with sub-resource) can is at the bottom classContext.constructor.getThis(), baseTarget)); if (observabilityIntegrationNeeded) { - String templatePath = MULTIPLE_SLASH_PATTERN.matcher(restClientInterface.getPath() + method.getPath()) - .replaceAll("/"); + String templatePath = templatePath(restClientInterface, method); classContext.constructor.invokeVirtualMethod( MethodDescriptor.ofMethod(WebTargetImpl.class, "setPreClientSendHandler", void.class, ClientRestHandler.class), @@ -1012,11 +1011,25 @@ A more full example of generated client (with sub-resource) can is at the bottom + jandexMethod.name()); } - ResultHandle newInputTarget = methodParamNotNull.invokeVirtualMethod( - MethodDescriptor.ofMethod(WebTargetImpl.class, "withNewUri", WebTargetImpl.class, - java.net.URI.class), - methodParamNotNull.readInstanceField(inputTargetField, methodParamNotNull.getThis()), - newUri); + ResultHandle newInputTarget; + if (observabilityIntegrationNeeded) { + // we need to apply the ClientObservabilityHandler to the inputTarget field without altering it + newInputTarget = methodParamNotNull.invokeVirtualMethod( + MethodDescriptor.ofMethod(WebTargetImpl.class, "withNewUri", WebTargetImpl.class, + java.net.URI.class, ClientRestHandler.class), + methodParamNotNull.readInstanceField(inputTargetField, methodParamNotNull.getThis()), + newUri, + methodParamNotNull.newInstance( + MethodDescriptor.ofConstructor(ClientObservabilityHandler.class, String.class), + methodParamNotNull.load(templatePath(restClientInterface, method)))); + } else { + // just read the inputTarget field and call withNewUri on it + newInputTarget = methodParamNotNull.invokeVirtualMethod( + MethodDescriptor.ofMethod(WebTargetImpl.class, "withNewUri", WebTargetImpl.class, + java.net.URI.class), + methodParamNotNull.readInstanceField(inputTargetField, methodParamNotNull.getThis()), + newUri); + } ResultHandle newBaseTarget = methodParamNotNull.invokeVirtualMethod( baseTargetProducer.getMethodDescriptor(), methodParamNotNull.getThis(), newInputTarget); @@ -1247,6 +1260,11 @@ A more full example of generated client (with sub-resource) can is at the bottom } + private String templatePath(RestClientInterface restClientInterface, ResourceMethod method) { + return MULTIPLE_SLASH_PATTERN.matcher(restClientInterface.getPath() + method.getPath()) + .replaceAll("/"); + } + /** * The @Encoded annotation is only supported in path/query/matrix/form params. */ diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/WebTargetImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/WebTargetImpl.java index 5ef736be1dd04..b96030232bf3a 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/WebTargetImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/WebTargetImpl.java @@ -259,6 +259,11 @@ public WebTargetImpl withNewUri(URI uri) { return newInstance(client, UriBuilder.fromUri(uri), configuration); } + @SuppressWarnings("unused") // this is used in the REST Client to support @BaseUrl and observability is enabled + public WebTargetImpl withNewUri(URI uri, ClientRestHandler preClientSendHandler) { + return newInstance(client, UriBuilder.fromUri(uri), configuration, preClientSendHandler); + } + @SuppressWarnings("unused") public WebTargetImpl queryParams(MultivaluedMap parameters) throws IllegalArgumentException, NullPointerException { @@ -297,6 +302,12 @@ public WebTargetImpl queryParamNoTemplate(String name, Object... values) throws protected WebTargetImpl newInstance(HttpClient client, UriBuilder uriBuilder, ConfigurationImpl configuration) { + return newInstance(client, uriBuilder, configuration, preClientSendHandler); + } + + protected WebTargetImpl newInstance(HttpClient client, UriBuilder uriBuilder, + ConfigurationImpl configuration, + ClientRestHandler preClientSendHandler) { WebTargetImpl result = new WebTargetImpl(restClient, client, uriBuilder, configuration, handlerChain.setPreClientSendHandler(preClientSendHandler), requestContext);