spring-projects/spring-framework

ServerWebExchange.mutate() does not delegate modified headers back to calling instance

Aafi01 opened this issue · 3 comments

Prior to spring-web-6.2.0 calling the mutate() method of ServerWebExchange and modifying the request headers would mutate the values in the calling instance of exchange object. This behavior matches the documentation of mutate() method. But this no longer works with version 6.2.0.

Dependency package: spring-web
Last working version: 6.1.13
Issue found in version: 6.2.0

MRE

Java version: 17
Dependencies: spring-boot-starter-webflux:3.4.0 (spring-web:6.2.0), spring-boot-starter-test:3.4.0

Following test fails:

@Test
void mutateShouldDelegateBackModifiedHeadersToThisInstance() {
    var mockServerHttpRequest = MockServerHttpRequest.get("/resource").build();
    var exchange = MockServerWebExchange.from(mockServerHttpRequest);

    String transactionId = UUID.randomUUID().toString();
    exchange.mutate().request(builder -> builder
            .header("Transaction-Id", transactionId));

    String transactionIdFromExchange = exchange.getRequest().getHeaders().getFirst("Transaction-Id");
    Assertions.assertEquals(transactionId, transactionIdFromExchange);
}

Using mutate().build() and using the returned exchange object still works as it did in version 6.1.13.

@Test
void mutateThenBuildShouldReturnModifiedHeaders() {
    var mockServerHttpRequest = MockServerHttpRequest.get("/resource").build();
    var exchange = MockServerWebExchange.from(mockServerHttpRequest);

    String transactionId = UUID.randomUUID().toString();
    var mutatedExchange = exchange.mutate().request(builder -> builder
            .header("Transaction-Id", transactionId)
    ).build();

    String transactionIdFromExchange = mutatedExchange.getRequest().getHeaders().getFirst("Transaction-Id");
    Assertions.assertEquals(transactionId, transactionIdFromExchange);
}

I think this was a bug and this was fixed in #32097 by chance.

The second behavior is the expected one and mutating a new instance of the exchange should not mutate the original headers. It's a bit late in the 6.1.x cycle to consider a backport so we'll leave it at that for now.

Thanks for the detailed report and the repro. Unfortunately, we can't reinstate this behavior as this was incorrect.
This behavior is now tested with #33666 so there's no need for additional tests. I'll close this issue now.

Thanks!

Thanks for the triage. Quite a pesky bug to remain undetected from spring 5.x.x to all the way till 6.2.0. I can live with the second behavior, but it might be good idea to update the javadoc for this method. It specifically mentions "returning either mutated values or delegating back to this instance". Or I misunderstood what that meant.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/server/ServerWebExchange.html#mutate()

I think this means that the decorator will either return a new value configured with the mutated version or return the original value. I guess it's an implementation details and we can improve that bit.