It seems like ResponseEntityResultHandler behaves differently compared to ServerResponseResultHandler regarding their handling of already present HTTP headers.
For example: Github
@SpringBootApplication
public class ResponseHandlerIssueApplication {
public static void main(String[] args) {
SpringApplication.run(ResponseHandlerIssueApplication.class, args);
}
}
@Component
class DefaultNoCacheHeaderFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
var headers = exchange.getResponse().getHeaders();
if (headers.getCacheControl() == null) {
headers.setCacheControl(CacheControl.noCache());
}
return chain.filter(exchange);
}
}
@RestController
class TestController {
@GetMapping("/responseEntity")
public Mono<ResponseEntity<String>> cached() {
var rsp = ResponseEntity
.status(HttpStatus.OK)
.cacheControl(CacheControl.maxAge(Duration.ofHours(1)))
.body("supposed to be cached");
return Mono.just(rsp);
}
}
@Configuration
class RouterConfig {
@Bean
public RouterFunction<ServerResponse> routerFunction() {
return route(GET("serverResponse"),
req -> ServerResponse.ok()
.cacheControl(CacheControl.maxAge(Duration.ofHours(1)))
.body(BodyInserters.fromValue("supposed to be cached")
));
}
}
Testing using curl
:
curl -v 'http://localhost:8080/responseEntity'
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /responseEntity HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Cache-Control: max-age=3600
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 21
<
* Connection #0 to host localhost left intact
supposed to be cached* Closing connection 0
curl -v 'http://localhost:8080/serverResponse'
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /serverResponse HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Cache-Control: no-cache
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 21
<
* Connection #0 to host localhost left intact
supposed to be cached* Closing connection 0
As we can see the /responseEntity
endpoint returns max-age=3600
for our Cache-Control header. However, /serverResponse
returns no-cache
.
ResponseEntityResultHandler seems to be overriding existing headers and DefaultServerResponseBuilder not to.
https://github.com/spring-projects/spring-framework/blob/2f32806bcb6cefa6479074d5fb66cb68043cedc9/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandler.java#L150-L155 https://github.com/spring-projects/spring-framework/blob/2f32806bcb6cefa6479074d5fb66cb68043cedc9/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java#L362-L368
The fix might be trivial, but maybe the intended behavior should be discussed.
Comment From: rstoyanchev
I think the headers from the handler should take precedence since it is the main and most specific request handling logic vs a filter which is applies to many requests. A handler can also check for the presence of existing header values if it really needs to, while in the opposite case it would have no way of overriding existing header values.
On the Spring MVC side, HttpEntityMethodProcessor and AbstractServerResponse also seem to write the headers from the handler.
@poutsma maybe this is something that needs to be adjusted in WebFlux.fn?