From f03b76a53dfd87e53db0c4c72f37e2a87c94f7c2 Mon Sep 17 00:00:00 2001 From: Hyunwoo Jung Date: Sat, 3 Oct 2026 21:14:09 +0900 Subject: [PATCH] Copy cookies in RenderingResponse.from() Prior to this commit, RenderingResponse.from() did not copy the cookies of the given response, unlike ServerResponse.from(). See gh-22481 Closes gh-37378 Signed-off-by: Hyunwoo Jung --- .../DefaultRenderingResponseBuilder.java | 1 + .../function/server/RenderingResponse.java | 3 ++- .../server/DefaultRenderingResponseTests.java | 21 ++++++++++++++++++ .../DefaultRenderingResponseBuilder.java | 1 + .../servlet/function/RenderingResponse.java | 3 ++- .../DefaultRenderingResponseTests.java | 22 +++++++++++++++++++ 6 files changed, 49 insertions(+), 2 deletions(-) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseBuilder.java index b16f77e6a8b..ee3066b39e2 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseBuilder.java @@ -71,6 +71,7 @@ final class DefaultRenderingResponseBuilder implements RenderingResponse.Builder this.status = other.statusCode(); this.headers.putAll(other.headers()); this.model.putAll(other.model()); + this.cookies.addAll(other.cookies()); } public DefaultRenderingResponseBuilder(String name) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RenderingResponse.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RenderingResponse.java index 965253e06ff..8aa26c2b250 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RenderingResponse.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RenderingResponse.java @@ -51,7 +51,8 @@ public interface RenderingResponse extends ServerResponse { // Builder /** - * Create a builder with the template name, status code, headers and model of the given response. + * Create a builder with the template name, status code, headers, cookies, + * and model of the given response. * @param other the response to copy the values from * @return the created builder */ diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseTests.java index 3d3f7950196..61a14b4faf3 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultRenderingResponseTests.java @@ -273,5 +273,26 @@ class DefaultRenderingResponseTests { .verify(); } + @Test // gh-37378 + void from() { + ResponseCookie cookie = ResponseCookie.from("currency", "USD").build(); + RenderingResponse other = RenderingResponse.create("foo") + .status(HttpStatus.CREATED) + .header("foo", "bar") + .cookie(cookie) + .modelAttribute("model", "value") + .build().block(); + + Mono result = RenderingResponse.from(other).build(); + StepVerifier.create(result) + .assertNext(response -> { + assertThat(response.name()).isEqualTo("foo"); + assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED); + assertThat(response.headers().getFirst("foo")).isEqualTo("bar"); + assertThat(response.cookies()).containsEntry("currency", List.of(cookie)); + assertThat(response.model()).containsEntry("model", "value"); + }) + .verifyComplete(); + } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultRenderingResponseBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultRenderingResponseBuilder.java index f0025c7fa84..e9d7cae039a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultRenderingResponseBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultRenderingResponseBuilder.java @@ -62,6 +62,7 @@ final class DefaultRenderingResponseBuilder implements RenderingResponse.Builder this.status = other.statusCode(); this.headers.putAll(other.headers()); this.model.putAll(other.model()); + this.cookies.addAll(other.cookies()); } public DefaultRenderingResponseBuilder(String name) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RenderingResponse.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RenderingResponse.java index d3897c1c188..6e4575f7b77 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RenderingResponse.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/RenderingResponse.java @@ -49,7 +49,8 @@ public interface RenderingResponse extends ServerResponse { // Builder /** - * Create a builder with the template name, status code, headers and model of the given response. + * Create a builder with the template name, status code, headers, cookies, + * and model of the given response. * @param other the response to copy the values from * @return the created builder */ diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultRenderingResponseTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultRenderingResponseTests.java index d823eefe15f..df960c930cd 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultRenderingResponseTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/DefaultRenderingResponseTests.java @@ -179,5 +179,27 @@ class DefaultRenderingResponseTests { assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_MODIFIED.value()); } + @Test // gh-37378 + void from() throws Exception { + Cookie cookie = new Cookie("currency", "USD"); + RenderingResponse other = RenderingResponse.create("foo") + .status(HttpStatus.CREATED) + .header("foo", "bar") + .cookie(cookie) + .modelAttribute("model", "value") + .build(); + RenderingResponse result = RenderingResponse.from(other).build(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + ModelAndView mav = result.writeTo(request, response, EMPTY_CONTEXT); + assertThat(mav).isNotNull(); + assertThat(mav.getViewName()).isEqualTo("foo"); + assertThat(mav.getModel()).containsEntry("model", "value"); + assertThat(response.getStatus()).isEqualTo(HttpStatus.CREATED.value()); + assertThat(response.getHeader("foo")).isEqualTo("bar"); + assertThat(response.getCookies()).containsExactly(cookie); + } }