Affects: v5.3.2
I have ExchangeFilterFunction which should retain only allowed response headers:
private static final Collection<String> ALLOWED_RESPONSE_HEADERS = Arrays.asList(HttpHeaders.CONTENT_TYPE, HttpHeaders.CONTENT_DISPOSITION);
private ExchangeFilterFunction filterResponseHeaders() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> Mono.just(ClientResponse.from(clientResponse)
.headers(httpHeaders -> httpHeaders.keySet().removeIf(headerName -> !ALLOWED_RESPONSE_HEADERS.contains(headerName)))
.body(clientResponse.bodyToFlux(DataBuffer.class))
.build()));
}
This one is working correctly.
Because of ClientResponse.from() deprecation, the method is rewritten to:
private ExchangeFilterFunction filterResponseHeaders() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> Mono.just(clientResponse.mutate()
.headers(httpHeaders -> httpHeaders.keySet().removeIf(headerName -> !ALLOWED_RESPONSE_HEADERS.contains(headerName)))
.build()));
}
That one is not working. Response headers are not affected.
Comment From: rstoyanchev
I've tried to reproduce this but couldn't as follows:
@Test
public void mutateCanRemoveHeaders() {
MockClientHttpResponse httpResponse = new MockClientHttpResponse(HttpStatus.OK);
httpResponse.getHeaders().add("foo", "bar");
httpResponse.getHeaders().add("bar", "baz");
DefaultClientResponse otherResponse = new DefaultClientResponse(
httpResponse, ExchangeStrategies.withDefaults(), "", "",
() -> new MockClientHttpRequest(HttpMethod.GET, "/path"));
ClientResponse result = otherResponse.mutate()
.statusCode(HttpStatus.BAD_REQUEST)
.headers(headers -> headers.remove("foo"))
.build();
assertThat(result.headers().asHttpHeaders().size()).isEqualTo(1);
assertThat(result.headers().asHttpHeaders().getFirst("bar")).isEqualTo("baz");
}
Comment From: zeljko-mirovic
I added two tests matching scenario in my application. RemoveHeaderClientResponseFromIT.java.txt RemoveHeaderClientResponseMutateIT.java.txt
Comment From: bclozel
I think this issue is in fact in NettyHeadersAdapter
. Its keySet()
method is using io.netty.handler.codec.http.HttpHeaders.names()
, which claims:
Note that modifying the returned Set will not affect the state of this object.
Comment From: zeljko-mirovic
If original keySet()
can not be modified, we should always use a copy.
Changing DefaultClientResponseBuilder(ClientResponse, boolean)
constructor like this is solving the issue:
DefaultClientResponseBuilder(ClientResponse other, boolean mutate) {
Assert.notNull(other, "ClientResponse must not be null");
this.strategies = other.strategies();
this.statusCode = other.rawStatusCode();
this.headers = new HttpHeaders();
this.headers.addAll(other.headers().asHttpHeaders());
if (mutate) {
this.body = other.bodyToFlux(DataBuffer.class);
}
this.originalResponse = other;
this.request = (other instanceof DefaultClientResponse ?
((DefaultClientResponse) other).request() : EMPTY_REQUEST);
}