-
Notifications
You must be signed in to change notification settings - Fork 38.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ServerHttpRequest content-type cannot be mutated #26615
Comments
See #21783 for background. |
@bclozel thanks, that's useful background. Might be good to put a reference in the code to that discussion. :) So is there a way to have our cake and eat it too? Or should it be a documentation fix that one should never attempt to change the Content-Type/Accepts headers with a |
Besides the specific reasons mentioned in #21783, we favor immutable data structures in WebFlux in general, because mutable variants introduce a whole new range of potential concurrency issues. |
You're currently changing the exchange and trying to modify the (immutable) request. What you need is to change the exchange, and change the request: ServerHttpRequest updatedRequest = request.mutate().headers(headers-> ...).build();
exchange.mutate().request(updatedRequest).build(); |
@rstoyanchev That is incorrect. I am using Per the Javadoc:
|
True,
So is Spring Cloud Gateway using the original exchange somehow instead of the mutated one? |
I think I'm getting this now. So this is not about changing the behavior of The following test case fails with the second assertion.
Here's what's happening:
This means that we're copying the right header values in the mutated request but we're keeping the original read-only value. Changing @rstoyanchev do you think that we've missed something with our mutability story here in the implementation hierarchy? |
I think the problem is the "ownership" of the original The problem as I see it is that As an analogy, this would be like if |
@TomRK1089 I don't think we can frame this like any collections wrapping problem. Here, So I've found and fixed the actual issue: instead of creating a new request using the writable view of the HTTP headers we were using during request mutation, we passed the original (so read-only) headers. Changing that solves the problem and it looks like it's aligning better with the intended design. This also fixes a similar problem on the "Accept" headers which are also cached by the read-only version. |
Prior to this commit, `ServerHttpRequest.mutate()` would not reflect changes made on the "Accept" and "Content-Type" HTTP headers. This was due to the fact that the instantiation of a new request based on the mutated values would not use the writable HTTP headers used during the mutation, but rather a read-only view of the headers backed by `ReadOnlyHttpHeaders`. `ReadOnlyHttpHeaders` caches those values for performance reasons, so getting those from the new request would not reflect the changes made during the mutation phase. This commit ensures that the new request uses the mutated headers. Fixes spring-projectsgh-26615
Affects: 5.3.3
Library:
spring-web
The
ReadOnlyHttpHeaders
type wraps an existing (mutable)MultiValueMap
. That map can be updated independently without going through theReadOnlyHttpHeaders
interface. This makes it inappropriate to cache theAccept
andContent-Type
headers.For example: I am working on a Spring Cloud Gateway project. Because the multipart/form-data parser does not correctly handle quoted boundary values (a separate issue) I tried to write both a
GlobalFilter
and aWebFilter
that would unwrap the boundary value before I attempt to use the built-in decoders for form data. This doesn't work, though, because the original quoted value is cached in aReadOnlyHttpHeaders
instance, even though its backingMultiValueMap
was updated by my filter.See an example snippet below:
The headers passed to the builder method are mutable and are intended to allow for this use case -- great! But then when I get to the actual boundary detection code in the multipart codec shipped with Spring Cloud Gateway, it does the following:
That
HttpMessage#getHeaders()
returns aReadOnlyHttpHeaders
instance, which someone else has already calledgetContentType()
on before my filter ran. So I'm stuck with the 'broken' value of the boundary and my code dies. I have no other way to rewrite that request short of running a whole separate gateway instance in front of my actual gateway to rewrite that header and forward it.It's not obvious why those two headers require caching. There are no comments describing reasons why they are particularly expensive to compute, for example.
The text was updated successfully, but these errors were encountered: