diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsConfigurationTests.java index 197c3e0681..69232dafe1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,6 +63,16 @@ class RestTemplateMetricsConfigurationTests { }); } + @Test + void restTemplateWithRootUriIsInstrumented() { + this.contextRunner.run((context) -> { + MeterRegistry registry = context.getBean(MeterRegistry.class); + RestTemplateBuilder builder = context.getBean(RestTemplateBuilder.class); + builder = builder.rootUri("/root"); + validateRestTemplate(builder, registry, "/root"); + }); + } + @Test void restTemplateCanBeCustomizedManually() { this.contextRunner.run((context) -> { @@ -130,17 +140,22 @@ class RestTemplateMetricsConfigurationTests { } private void validateRestTemplate(RestTemplateBuilder builder, MeterRegistry registry) { - RestTemplate restTemplate = mockRestTemplate(builder); + this.validateRestTemplate(builder, registry, ""); + } + + private void validateRestTemplate(RestTemplateBuilder builder, MeterRegistry registry, String rootUri) { + RestTemplate restTemplate = mockRestTemplate(builder, rootUri); assertThat(registry.find("http.client.requests").meter()).isNull(); assertThat(restTemplate.getForEntity("/projects/{project}", Void.class, "spring-boot").getStatusCode()) .isEqualTo(HttpStatus.OK); - assertThat(registry.get("http.client.requests").tags("uri", "/projects/{project}").meter()).isNotNull(); + assertThat(registry.get("http.client.requests").tags("uri", rootUri + "/projects/{project}").meter()) + .isNotNull(); } - private RestTemplate mockRestTemplate(RestTemplateBuilder builder) { + private RestTemplate mockRestTemplate(RestTemplateBuilder builder, String rootUri) { RestTemplate restTemplate = builder.build(); MockRestServiceServer server = MockRestServiceServer.createServer(restTemplate); - server.expect(requestTo("/projects/spring-boot")).andRespond(withStatus(HttpStatus.OK)); + server.expect(requestTo(rootUri + "/projects/spring-boot")).andRespond(withStatus(HttpStatus.OK)); return restTemplate; } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/MetricsClientHttpRequestInterceptor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/MetricsClientHttpRequestInterceptor.java index fabcbb322a..a5e4a540db 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/MetricsClientHttpRequestInterceptor.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/client/MetricsClientHttpRequestInterceptor.java @@ -29,6 +29,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.boot.actuate.metrics.AutoTimer; +import org.springframework.boot.web.client.RootUriTemplateHandler; import org.springframework.core.NamedThreadLocal; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; @@ -100,21 +101,10 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto } UriTemplateHandler createUriTemplateHandler(UriTemplateHandler delegate) { - return new UriTemplateHandler() { - - @Override - public URI expand(String url, Map arguments) { - urlTemplate.get().push(url); - return delegate.expand(url, arguments); - } - - @Override - public URI expand(String url, Object... arguments) { - urlTemplate.get().push(url); - return delegate.expand(url, arguments); - } - - }; + if (delegate instanceof RootUriTemplateHandler) { + return ((RootUriTemplateHandler) delegate).withHandlerWrapper(CapturingUriTemplateHandler::new); + } + return new CapturingUriTemplateHandler(delegate); } private Timer.Builder getTimeBuilder(HttpRequest request, ClientHttpResponse response) { @@ -123,6 +113,28 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto .description("Timer of RestTemplate operation"); } + private static final class CapturingUriTemplateHandler implements UriTemplateHandler { + + private final UriTemplateHandler delegate; + + private CapturingUriTemplateHandler(UriTemplateHandler delegate) { + this.delegate = delegate; + } + + @Override + public URI expand(String url, Map arguments) { + urlTemplate.get().push(url); + return this.delegate.expand(url, arguments); + } + + @Override + public URI expand(String url, Object... arguments) { + urlTemplate.get().push(url); + return this.delegate.expand(url, arguments); + } + + } + private static final class UrlTemplateThreadLocal extends NamedThreadLocal> { private UrlTemplateThreadLocal() { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java index ac684b828e..c6091c36ab 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/RootUriTemplateHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.boot.web.client; import java.net.URI; import java.util.Map; +import java.util.function.Function; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -84,6 +85,17 @@ public class RootUriTemplateHandler implements UriTemplateHandler { return this.rootUri; } + /** + * Derives a new {@code RootUriTemplateHandler} from this one, wrapping its delegate + * {link UriTemplateHandler} by applying the given {@code wrapper}. + * @param wrapper the wrapper to apply to the delegate URI template handler + * @return the new handler + * @since 2.3.10 + */ + public RootUriTemplateHandler withHandlerWrapper(Function wrapper) { + return new RootUriTemplateHandler(this.rootUri, wrapper.apply(this.handler)); + } + /** * Add a {@link RootUriTemplateHandler} instance to the given {@link RestTemplate}. * @param restTemplate the {@link RestTemplate} to add the handler to