Affects: 6.1.x
Condition
- spring-cloud-gateway (
ProxyExchange
) - spring-mvc (
RestController
) - spring-test
- with restassured
Bug
getContentLength()
value and length of actual value can be different.
{
"contextContent": {
"aSource": {
"a": "20242024"
},
"bSource": {
"b": "20252025"
}
}
}
- I sent a body as json string like above, the byte array size of raw string is 369.
- And that number filled as
Content-Length
header in MockHttpServletRequestBuilder Content-Length
header is sent as request header in apache httpcore RequestContentHttpComponentsClientHttpRequest$BodyEntity#getContentLength
- But actual length value is 125, which is serialization result of jackson ObjectMapper (AbstractJackson2HttpMessageConverter)
This mismatch cause Connection Reset
-> server wait until receiving 369 bytes, but client sent 125.
Before 6.1.x
This issue didn't exist because HttpComponentsClientHttpRequest
use ByteArrayEntity
that contains a serialization result of jackson ObjectMapper
https://github.com/spring-projects/spring-framework/blob/72835f10b9b07921978da419abf2a182abf67967/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequest.java#L91
Comment From: bclozel
Rather than providing your analysis can you share an example of application that shows the issue? It's hard for us to help you as we are missing the entire context.
We wouldn't need a complete example with the gateway but a simple http client request with invalid content length would be enough.
Comment From: chanhyeong
@bclozel
I made a sample in https://github.com/chanhyeong/headersample You can reproduce this issue by following below.
- Run spring boot application
- Run failedBecauseOfInvalidContentLengthHeader() to see this issue
- boot application server blames SocketTimeoutException
- Run successWithMatchingContentLength() to see success case when content length value matched
Comment From: bclozel
@chanhyeong the project is not accessible.
Comment From: chanhyeong
@bclozel
I'm sorry. I changed visibility as public.
Comment From: bclozel
Thanks for the sample. I don't think this issue is related to Spring Framework.
Here, the request body is deserialized from JSON into a Map, then re-serialized as the request body to the proxied service. The "Content-Length" header is copied over directly. I don't think that this should happen. The "Content-Length" header should be overwritten, or the body should never be deserialized in the first place (this is quite inefficient in this case).
You can consider changing your controller to the following:
@PostMapping("/**")
public ResponseEntity<?> serverProxy(
ProxyExchange<byte[]> proxyExchange,
HttpServletRequest request,
@RequestBody byte[] body
) {
String url = assembleUrl(proxyExchange, request);
return proxyExchange.uri(url).body(body).post();
}
I think this has been raised already in spring-cloud/spring-cloud-gateway#3154 and I'll close this issue in favor of that one. There are other workarounds described there, and probably a future fix for this.
Comment From: chanhyeong
Thanks. I doubted because HttpComponentsClientHttpRequest
was changed.
Also, I'm going to add 'Content-Length' header into sensitive headers list temporarily to prevent propagating to downstream. It will be added by httpcore RequestContent.