Duplicate response headers with Kotlin suspend function returning ResponseEntity after header adapter optimization
Affects: Spring Framework 7.0.5 (Spring Boot 4.0.3)
Maybe introduced in: 7ea11baff9 (Pass-through handling of Servlet headers, gh-36334)
Description
After the Servlet header adapter optimization introduced in gh-36334, response headers are duplicated when a Kotlin suspend fun controller method returns ResponseEntity<T> in Spring WebMVC.
The root cause is in ResponseBodyEmitterReturnValueHandler.handleReturnValue(). In the non-streaming path (single-value reactive type adapted to DeferredResult), headers that are already present in the HttpServletResponse are read back through the adapter and re-added via addHeader(), resulting in every header appearing twice.
First, the header is copied here.
|
this.headers = new HttpHeaders(new ServletResponseHeadersAdapter(servletResponse)); |
The http header is also duplicated in the outputMessage.
|
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class); |
|
Assert.state(response != null, "No HttpServletResponse"); |
|
ServerHttpResponse outputMessage = new ServletServerHttpResponse(response); |
response.addHeader causes a duplicate header in the original response.
|
outputMessage.getHeaders().forEach((headerName, headerValues) -> { |
|
for (String headerValue : headerValues) { |
|
response.addHeader(headerName, headerValue); |
|
} |
|
}); |
Steps to Reproduce
- Configure a
CorsFilter or @CrossOrigin annotation
- Define a Kotlin suspend controller that returns
ResponseEntity<T>:
@RestController
class MyController {
@GetMapping("/example")
suspend fun example(): ResponseEntity<String> {
return ResponseEntity.ok()
.header("X-Custom", "value")
.body("hello")
}
}
- Send a cross-origin request to
/example
- Observe the response headers
Expected Behavior
Each header appears once in the response
Actual Behavior
Headers are duplicated
Duplicate response headers with Kotlin suspend function returning ResponseEntity after header adapter optimization
Affects: Spring Framework 7.0.5 (Spring Boot 4.0.3)
Maybe introduced in:
7ea11baff9(Pass-through handling of Servlet headers, gh-36334)Description
After the Servlet header adapter optimization introduced in gh-36334, response headers are duplicated when a Kotlin
suspend funcontroller method returnsResponseEntity<T>in Spring WebMVC.The root cause is in
ResponseBodyEmitterReturnValueHandler.handleReturnValue(). In the non-streaming path (single-value reactive type adapted toDeferredResult), headers that are already present in theHttpServletResponseare read back through the adapter and re-added viaaddHeader(), resulting in every header appearing twice.First, the header is copied here.
spring-framework/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java
Line 56 in 7ea11ba
The http header is also duplicated in the outputMessage.
spring-framework/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java
Lines 189 to 191 in 188cb9b
response.addHeader causes a duplicate header in the original response.
spring-framework/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java
Lines 218 to 222 in 188cb9b
Steps to Reproduce
CorsFilteror@CrossOriginannotationResponseEntity<T>:/exampleExpected Behavior
Each header appears once in the response
Actual Behavior
Headers are duplicated