diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/observation/ServerRequestObservationContext.java b/spring-web/src/main/java/org/springframework/http/server/reactive/observation/ServerRequestObservationContext.java index 3ef6c39e13..fa7454afac 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/observation/ServerRequestObservationContext.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/observation/ServerRequestObservationContext.java @@ -25,11 +25,12 @@ import io.micrometer.observation.transport.RequestReplyReceiverContext; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.lang.Nullable; -import org.springframework.web.server.ServerWebExchange; /** * Context that holds information for metadata collection regarding - * {@link ServerHttpObservationDocumentation#HTTP_REACTIVE_SERVER_REQUESTS reactive HTTP requests} observations. + * {@link ServerHttpObservationDocumentation#HTTP_REACTIVE_SERVER_REQUESTS reactive HTTP requests} + * observations. + * *

This context also extends {@link RequestReplyReceiverContext} for propagating * tracing information during HTTP request processing. * @@ -39,10 +40,11 @@ import org.springframework.web.server.ServerWebExchange; public class ServerRequestObservationContext extends RequestReplyReceiverContext { /** - * Name of the request attribute holding the {@link ServerRequestObservationContext context} for the current observation. + * Name of the request attribute holding the {@link ServerRequestObservationContext context} + * for the current observation. * @since 6.1 */ - public static final String CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE = ServerRequestObservationContext.class.getName() + ".context"; + public static final String CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE = ServerRequestObservationContext.class.getName(); private final Map attributes; @@ -53,7 +55,15 @@ public class ServerRequestObservationContext extends RequestReplyReceiverContext private boolean connectionAborted; - public ServerRequestObservationContext(ServerHttpRequest request, ServerHttpResponse response, Map attributes) { + /** + * Create a new {@code ServerRequestObservationContext} instance. + * @param request the current request + * @param response the current response + * @param attributes the current attributes + */ + public ServerRequestObservationContext( + ServerHttpRequest request, ServerHttpResponse response, Map attributes) { + super((req, key) -> req.getHeaders().getFirst(key)); setCarrier(request); setResponse(response); @@ -89,8 +99,8 @@ public class ServerRequestObservationContext extends RequestReplyReceiverContext } /** - * Whether the current connection was aborted by the client, resulting - * in a {@link reactor.core.publisher.SignalType#CANCEL cancel signal} on the reactive chain, + * Whether the current connection was aborted by the client, resulting in a + * {@link reactor.core.publisher.SignalType#CANCEL cancel signal} on the reactive chain, * or an {@code AbortedException} when reading the request. * @return if the connection has been aborted */ @@ -99,8 +109,8 @@ public class ServerRequestObservationContext extends RequestReplyReceiverContext } /** - * Set whether the current connection was aborted by the client, resulting - * in a {@link reactor.core.publisher.SignalType#CANCEL cancel signal} on the reactive chain, + * Set whether the current connection was aborted by the client, resulting in a + * {@link reactor.core.publisher.SignalType#CANCEL cancel signal} on the reactive chain, * or an {@code AbortedException} when reading the request. * @param connectionAborted if the connection has been aborted */ @@ -110,13 +120,15 @@ public class ServerRequestObservationContext extends RequestReplyReceiverContext /** - * Get the current {@link ServerRequestObservationContext observation context} from the given exchange, if available. - * @param exchange the current exchange + * Get the current {@link ServerRequestObservationContext observation context} + * from the given attributes, if available. + * @param attributes the current exchange attributes * @return the current observation context * @since 6.1 */ - public static Optional findCurrent(ServerWebExchange exchange) { - return Optional.ofNullable(exchange.getAttribute(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE)); + public static Optional findCurrent(Map attributes) { + return Optional.ofNullable( + (ServerRequestObservationContext) attributes.get(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE)); } } diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java b/spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java index 2072a25da7..fca440fc1f 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java @@ -296,9 +296,10 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa exchange.getLogPrefix() + formatRequest(exchange.getRequest()) + (traceOn ? ", headers=" + formatHeaders(exchange.getRequest().getHeaders()) : "")); - ServerRequestObservationContext observationContext = new ServerRequestObservationContext(exchange.getRequest(), - exchange.getResponse(), exchange.getAttributes()); - exchange.getAttributes().put(ServerRequestObservationContext.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observationContext); + ServerRequestObservationContext observationContext = new ServerRequestObservationContext( + exchange.getRequest(), exchange.getResponse(), exchange.getAttributes()); + exchange.getAttributes().put( + ServerRequestObservationContext.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observationContext); return getDelegate().handle(exchange) .transformDeferred(call -> transform(exchange, observationContext, call)) diff --git a/spring-web/src/test/java/org/springframework/web/server/adapter/HttpWebHandlerAdapterObservabilityTests.java b/spring-web/src/test/java/org/springframework/web/server/adapter/HttpWebHandlerAdapterObservabilityTests.java index 94d8c8b46d..f79956d567 100644 --- a/spring-web/src/test/java/org/springframework/web/server/adapter/HttpWebHandlerAdapterObservabilityTests.java +++ b/spring-web/src/test/java/org/springframework/web/server/adapter/HttpWebHandlerAdapterObservabilityTests.java @@ -51,6 +51,7 @@ class HttpWebHandlerAdapterObservabilityTests { private final MockServerHttpResponse response = new MockServerHttpResponse(); + @Test void handlerShouldSetObservationContextOnExchange() { HttpStatusSuccessStubWebHandler targetHandler = new HttpStatusSuccessStubWebHandler(HttpStatus.OK); @@ -97,6 +98,7 @@ class HttpWebHandlerAdapterObservabilityTests { .hasObservationWithNameEqualTo("http.server.requests").that(); } + private static class HttpStatusSuccessStubWebHandler implements WebHandler { private final HttpStatus responseStatus; @@ -109,12 +111,13 @@ class HttpWebHandlerAdapterObservabilityTests { @Override public Mono handle(ServerWebExchange exchange) { - this.observationContext = ServerRequestObservationContext.findCurrent(exchange); + this.observationContext = ServerRequestObservationContext.findCurrent(exchange.getAttributes()); exchange.getResponse().setStatusCode(this.responseStatus); return Mono.empty(); } } + private static class ReactorContextWebHandler implements WebHandler { ContextView contextView; @@ -129,6 +132,7 @@ class HttpWebHandlerAdapterObservabilityTests { } } + private static class ThrowingExceptionWebHandler implements WebHandler { private final Throwable exception; @@ -141,11 +145,12 @@ class HttpWebHandlerAdapterObservabilityTests { @Override public Mono handle(ServerWebExchange exchange) { - this.observationContext = ServerRequestObservationContext.findCurrent(exchange); + this.observationContext = ServerRequestObservationContext.findCurrent(exchange.getAttributes()); return Mono.error(this.exception); } } + private static class BadRequestExceptionHandler implements WebExceptionHandler { @Override diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/RouterFunctionMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/RouterFunctionMapping.java index 723173a649..e36e1af898 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/RouterFunctionMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/RouterFunctionMapping.java @@ -174,7 +174,7 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini org.springframework.web.filter.reactive.ServerHttpObservationFilter .findObservationContext(serverRequest.exchange()) .ifPresent(context -> context.setPathPattern(matchingPattern.toString())); - ServerRequestObservationContext.findCurrent(serverRequest.exchange()) + ServerRequestObservationContext.findCurrent(serverRequest.exchange().getAttributes()) .ifPresent(context -> context.setPathPattern(matchingPattern.toString())); } Map uriVariables = diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java index 604caf088a..5eccca3494 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java @@ -170,7 +170,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { org.springframework.web.filter.reactive.ServerHttpObservationFilter .findObservationContext(exchange) .ifPresent(context -> context.setPathPattern(pattern.toString())); - ServerRequestObservationContext.findCurrent(exchange) + ServerRequestObservationContext.findCurrent(exchange.getAttributes()) .ifPresent(context -> context.setPathPattern(pattern.toString())); exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping); exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, matchInfo.getUriVariables()); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java index 31164f291a..99fc0518e7 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/RequestMappingInfoHandlerMapping.java @@ -145,7 +145,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe org.springframework.web.filter.reactive.ServerHttpObservationFilter .findObservationContext(exchange) .ifPresent(context -> context.setPathPattern(bestPattern.toString())); - ServerRequestObservationContext.findCurrent(exchange) + ServerRequestObservationContext.findCurrent(exchange.getAttributes()) .ifPresent(context -> context.setPathPattern(bestPattern.toString())); exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables); exchange.getAttributes().put(MATRIX_VARIABLES_ATTRIBUTE, matrixVariables); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/support/RouterFunctionMappingTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/support/RouterFunctionMappingTests.java index 8f347d3a97..b979e13302 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/support/RouterFunctionMappingTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/support/RouterFunctionMappingTests.java @@ -137,7 +137,7 @@ class RouterFunctionMappingTests { assertThat(matchingPattern.getPatternString()).isEqualTo("/match"); assertThat(org.springframework.web.filter.reactive.ServerHttpObservationFilter.findObservationContext(exchange)) .hasValueSatisfying(context -> assertThat(context.getPathPattern()).isEqualTo(matchingPattern.getPatternString())); - assertThat(ServerRequestObservationContext.findCurrent(exchange)) + assertThat(ServerRequestObservationContext.findCurrent(exchange.getAttributes())) .hasValueSatisfying(context -> assertThat(context.getPathPattern()).isEqualTo(matchingPattern.getPatternString())); ServerRequest serverRequest = exchange.getAttribute(RouterFunctions.REQUEST_ATTRIBUTE);