From fe2113f5a3e3d763b8269602bd8526e685f2c401 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Fri, 16 Jan 2026 17:21:27 +0000 Subject: [PATCH] Clear Netty channel attribute Closes gh-36158 --- .../reactive/ReactorClientHttpConnector.java | 18 +++++++++++++- .../client/WebClientIntegrationTests.java | 24 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpConnector.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpConnector.java index 0c8eb18cf43..ad7eed9bce0 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpConnector.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpConnector.java @@ -21,12 +21,15 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; +import io.netty.channel.Channel; +import io.netty.util.Attribute; import io.netty.util.AttributeKey; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import reactor.netty.NettyOutbound; +import reactor.netty.channel.ChannelOperations; import reactor.netty.http.client.HttpClient; import reactor.netty.http.client.HttpClientRequest; import reactor.netty.resources.ConnectionProvider; @@ -160,10 +163,14 @@ public class ReactorClientHttpConnector implements ClientHttpConnector, SmartLif .request(io.netty.handler.codec.http.HttpMethod.valueOf(method.name())); requestSender = setUri(requestSender, uri); + AtomicReference channelRef = new AtomicReference<>(); AtomicReference responseRef = new AtomicReference<>(); return requestSender - .send((request, outbound) -> requestCallback.apply(adaptRequest(method, uri, request, outbound))) + .send((request, outbound) -> { + channelRef.set(((ChannelOperations) request).channel()); + return requestCallback.apply(adaptRequest(method, uri, request, outbound)); + }) .responseConnection((response, connection) -> { ReactorClientHttpResponse clientResponse = new ReactorClientHttpResponse(response, connection); responseRef.set(clientResponse); @@ -175,6 +182,15 @@ public class ReactorClientHttpConnector implements ClientHttpConnector, SmartLif if (response != null) { response.releaseAfterCancel(method); } + }) + .doOnTerminate(() -> { + Channel channel = channelRef.get(); + if (channel != null) { + Attribute> attribute = channel.attr(ATTRIBUTES_KEY); + if (attribute != null) { + attribute.set(null); + } + } }); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java index e870c28a6eb..92d464e6772 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java @@ -39,6 +39,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import io.netty.channel.Channel; import io.netty.util.Attribute; import mockwebserver3.MockResponse; import mockwebserver3.MockWebServer; @@ -54,6 +55,7 @@ import reactor.core.publisher.Mono; import reactor.core.publisher.Sinks; import reactor.netty.channel.ChannelOperations; import reactor.netty.http.client.HttpClient; +import reactor.netty.http.client.HttpClientRequest; import reactor.netty.resources.ConnectionProvider; import reactor.test.StepVerifier; @@ -1320,6 +1322,28 @@ class WebClientIntegrationTests { .verify(Duration.ofSeconds(3)); } + @Test // gh-36158 + void reactorNettyAttributes() throws IOException { + startServer(new ReactorClientHttpConnector()); + + prepareResponse(builder -> + builder.setHeader("Content-Type", "text/plain").body("Hello Spring!")); + + AtomicReference channelRef = new AtomicReference<>(); + + Mono result = this.webClient.get().uri("/greeting") + .httpRequest(request -> { + HttpClientRequest reactorRequest = request.getNativeRequest(); + channelRef.set(((ChannelOperations) reactorRequest).channel()); + }) + .retrieve() + .bodyToMono(String.class); + + StepVerifier.create(result).expectNext("Hello Spring!").expectComplete().verify(Duration.ofSeconds(3)); + + assertThat(channelRef.get().attr(ReactorClientHttpConnector.ATTRIBUTES_KEY).get()).isNull(); + } + private Mono doMalformedChunkedResponseTest( ClientHttpConnector connector, Function> handler) {