diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java index 54c5da2c052..98021ce6dd4 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java @@ -90,6 +90,7 @@ class HttpComponentsClientHttpResponse extends AbstractClientHttpResponse { .maxAge(getMaxAgeSeconds(cookie)) .secure(cookie.isSecure()) .httpOnly(cookie.containsAttribute("httponly")) + .partitioned(cookie.containsAttribute("partitioned")) .sameSite(cookie.getAttribute("samesite")) .build()); } diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java index 411fda8f20a..e6e3d445dab 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java @@ -148,6 +148,7 @@ class ReactorClientHttpResponse implements ClientHttpResponse { .secure(cookie.isSecure()) .httpOnly(cookie.isHttpOnly()) .sameSite(getSameSite(cookie)) + .partitioned(getPartitioned(cookie)) .build())); return CollectionUtils.unmodifiableMultiValueMap(result); } @@ -160,6 +161,13 @@ class ReactorClientHttpResponse implements ClientHttpResponse { return null; } + private static boolean getPartitioned(Cookie cookie) { + if (cookie instanceof DefaultCookie defaultCookie) { + return defaultCookie.isPartitioned(); + } + return false; + } + /** * Called by {@link ReactorClientHttpConnector} when a cancellation is detected * but the content has not been subscribed to. If the subscription never diff --git a/spring-web/src/test/java/org/springframework/http/client/reactive/ClientHttpConnectorTests.java b/spring-web/src/test/java/org/springframework/http/client/reactive/ClientHttpConnectorTests.java index dcd98a835ff..7095feb3153 100644 --- a/spring-web/src/test/java/org/springframework/http/client/reactive/ClientHttpConnectorTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/reactive/ClientHttpConnectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -41,6 +41,7 @@ import okhttp3.mockwebserver.RecordedRequest; import okio.Buffer; import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Test; @@ -201,6 +202,25 @@ class ClientHttpConnectorTests { .verifyComplete(); } + @ParameterizedConnectorTest + void partitionedCookieSupport(ClientHttpConnector connector) { + Assumptions.assumeFalse(connector instanceof JettyClientHttpConnector, "Jetty client does not support partitioned cookies"); + Assumptions.assumeFalse(connector instanceof JdkClientHttpConnector, "JDK client does not support partitioned cookies"); + prepareResponse(response -> { + response.setResponseCode(200); + response.addHeader("Set-Cookie", "id=test; Partitioned;"); + }); + Mono futureResponse = + connector.connect(HttpMethod.GET, this.server.url("/").uri(), ReactiveHttpOutputMessage::setComplete); + StepVerifier.create(futureResponse) + .assertNext(response -> { + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getCookies().getFirst("id").isPartitioned()).isTrue(); + } + ) + .verifyComplete(); + } + @Test void disableCookieWithHttpComponents() { ClientHttpConnector connector = new HttpComponentsClientHttpConnector(