Skip to content

WebFlux ServerResponse does not overwrite already present response headers #27741

Closed
@rlndd

Description

@rlndd

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.

HttpHeaders entityHeaders = httpEntity.getHeaders();
HttpHeaders responseHeaders = exchange.getResponse().getHeaders();
if (!entityHeaders.isEmpty()) {
entityHeaders.entrySet().stream()
.forEach(entry -> responseHeaders.put(entry.getKey(), entry.getValue()));
}

private static <K,V> void copy(MultiValueMap<K,V> src, MultiValueMap<K,V> dst) {
if (!src.isEmpty()) {
src.entrySet().stream()
.filter(entry -> !dst.containsKey(entry.getKey()))
.forEach(entry -> dst.put(entry.getKey(), entry.getValue()));
}
}

The fix might be trivial, but maybe the intended behavior should be discussed.

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)type: bugA general bug

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions