From 9f77f401adf86b88288788461f929d193fef001d Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 8 Dec 2025 11:07:30 +0100 Subject: [PATCH] Complete Propagator.Getter implementation As of Micrometer Tracing 1.6.0, the `Propagator.Getter` interface adds a new `getAll` method with a default implementation return a singleton collection. This commit adds the missing implementation override in both Servlet and Reactor web server contexts. Fixes gh-35965 --- .../ServerRequestObservationContext.java | 20 +++++++- .../ServerRequestObservationContext.java | 17 ++++++- .../ServerRequestObservationContextTests.java | 46 +++++++++++++++++++ .../ServerRequestObservationContextTests.java | 44 ++++++++++++++++++ 4 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 spring-web/src/test/java/org/springframework/http/server/observation/ServerRequestObservationContextTests.java create mode 100644 spring-web/src/test/java/org/springframework/http/server/reactive/observation/ServerRequestObservationContextTests.java diff --git a/spring-web/src/main/java/org/springframework/http/server/observation/ServerRequestObservationContext.java b/spring-web/src/main/java/org/springframework/http/server/observation/ServerRequestObservationContext.java index 4095e029ee3..4e57f36472f 100644 --- a/spring-web/src/main/java/org/springframework/http/server/observation/ServerRequestObservationContext.java +++ b/spring-web/src/main/java/org/springframework/http/server/observation/ServerRequestObservationContext.java @@ -16,6 +16,9 @@ package org.springframework.http.server.observation; +import java.util.Collections; + +import io.micrometer.observation.transport.Propagator; import io.micrometer.observation.transport.RequestReplyReceiverContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -32,10 +35,12 @@ import org.jspecify.annotations.Nullable; */ public class ServerRequestObservationContext extends RequestReplyReceiverContext { + private static final HeaderGetter GETTER = new HeaderGetter(); + private @Nullable String pathPattern; public ServerRequestObservationContext(HttpServletRequest request, HttpServletResponse response) { - super(HttpServletRequest::getHeader); + super(GETTER); setCarrier(request); setResponse(response); } @@ -47,4 +52,17 @@ public class ServerRequestObservationContext extends RequestReplyReceiverContext public void setPathPattern(@Nullable String pathPattern) { this.pathPattern = pathPattern; } + + static final class HeaderGetter implements Propagator.Getter { + @Override + public String get(HttpServletRequest carrier, String key) { + return carrier.getHeader(key); + } + + @Override + public Iterable getAll(HttpServletRequest carrier, String key) { + return Collections.list(carrier.getHeaders(key)); + } + } + } 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 2a3a0b7407e..e59e5d99278 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 @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.Map; import java.util.Optional; +import io.micrometer.observation.transport.Propagator; import io.micrometer.observation.transport.RequestReplyReceiverContext; import org.jspecify.annotations.Nullable; @@ -46,6 +47,7 @@ public class ServerRequestObservationContext extends RequestReplyReceiverContext */ public static final String CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE = ServerRequestObservationContext.class.getName(); + private static final HeaderGetter GETTER = new HeaderGetter(); private final Map attributes; @@ -63,7 +65,7 @@ public class ServerRequestObservationContext extends RequestReplyReceiverContext public ServerRequestObservationContext( ServerHttpRequest request, ServerHttpResponse response, Map attributes) { - super((req, key) -> req.getHeaders().getFirst(key)); + super(GETTER); setCarrier(request); setResponse(response); this.attributes = Collections.unmodifiableMap(attributes); @@ -129,4 +131,17 @@ public class ServerRequestObservationContext extends RequestReplyReceiverContext (ServerRequestObservationContext) attributes.get(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE)); } + static final class HeaderGetter implements Propagator.Getter { + + @Override + public @Nullable String get(ServerHttpRequest carrier, String key) { + return carrier.getHeaders().getFirst(key); + } + + @Override + public Iterable getAll(ServerHttpRequest carrier, String key) { + return carrier.getHeaders().getOrEmpty(key); + } + } + } diff --git a/spring-web/src/test/java/org/springframework/http/server/observation/ServerRequestObservationContextTests.java b/spring-web/src/test/java/org/springframework/http/server/observation/ServerRequestObservationContextTests.java new file mode 100644 index 00000000000..e37253d501c --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/server/observation/ServerRequestObservationContextTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2025-present 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.server.observation; + + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.web.testfixture.servlet.MockHttpServletRequest; +import org.springframework.web.testfixture.servlet.MockHttpServletResponse; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ServerRequestObservationContext}. + */ +class ServerRequestObservationContextTests { + + private MockHttpServletRequest request = new MockHttpServletRequest(); + + private MockHttpServletResponse response = new MockHttpServletResponse(); + + private ServerRequestObservationContext context = new ServerRequestObservationContext(request, response); + + @Test + void shouldGetMultipleHeaderValues() { + request.addHeader("test", List.of("spring", "framework")); + assertThat(context.getGetter().getAll(request, "test")).contains("spring", "framework"); + } + +} diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/observation/ServerRequestObservationContextTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/observation/ServerRequestObservationContextTests.java new file mode 100644 index 00000000000..3b93bb867c0 --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/observation/ServerRequestObservationContextTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2025-present 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.server.reactive.observation; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest; +import org.springframework.web.testfixture.server.MockServerWebExchange; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ServerRequestObservationContext}. + */ +class ServerRequestObservationContextTests { + + + @Test + void shouldGetMultipleHeaderValues() { + MockServerHttpRequest request = MockServerHttpRequest.get("/") + .header("test", "spring", "framework").build(); + MockServerWebExchange exchange = MockServerWebExchange.builder(request).build(); + + ServerRequestObservationContext context = new ServerRequestObservationContext(request, exchange.getResponse(), Map.of()); + assertThat(context.getGetter().getAll(request, "test")).contains("spring", "framework"); + } + +}