spring-projects / spring-projects/spring-framework
JettyClientHttpRequest has high data frame overhead on http2
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 60.2k
- Forks
- 38.8k
- Avg merge
- 5d 2h
- Merged PRs (30d)
- 27
Description
When sending a http request over http2 with jetty client, JettyClientHttpRequest uses org.eclipse.jetty.client.OutputStreamRequestContent to provide the body content. Unfortunately, OutputStreamRequestContent generates some data frame overhead, since it sends all data frames that contains actual data without the END_STREAM flag set - and then when the OutputStreamRequestContent is closed, sends a final empty data frame with the END_STREAM flag set. The more efficient way would be to set the END_STREAM flag in the last data frame that actually contained data ofc.
In cases where the body is relatively small and fit in a single data frame, the effect is that the client sends twice as many data frames as necessary (the first frame with the data, and then the "unnecessary" frame with the END_STREAM flag).
Furthermore, if the bodies are really small, and the server is Tomcat - there is a high risk that the client will trigger Tomcat's "overhead protection", which will make Tomcat close the connection. In my microservice setup using spring-boot with tomcat on the server side and spring-boot with jetty-based RestClient on the client side I'm running into issues similar to https://stackoverflow.com/questions/60049290/closenowexception-this-stream-is-not-writeable and https://stackoverflow.com/questions/61413934/http2-err-connection-closed-too-much-overhead due to this.
To some extent this is a limitation in the Jetty client, but I still believe spring-web can mitigate it to some extent. At least in my case (and I think it is the most common) I have the request executed via a InterceptingClientHttpRequest, which is buffering the body in a byte array. The body is then fed via org.springframework.http.StreamingHttpOutputMessage#setBody(byte[]) to JettyClientHttpRequest which then send it to OutputStreamRequestContent via Body#writeTo. However, since we in practice already have the body in a byte array in this case, we could just as well use e.g. a org.eclipse.jetty.client.BytesRequestContent and thereby avoid the unnecessary data frame. Though, a way to access the byte array from the JettyClientHttpRequest is needed (maybe just a "ByteArrayBody" class implementing StreamingHttpOutputMessage.Body and exposing the byte array?). Or an alternative ClientHttpRequestFactory that creates non-streaming requests for jetty client?
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with JettyClientHttpRequest and the InterceptingClientHttpRequest path described in the issue, then trace StreamingHttpOutputMessage.Body#writeTo into Jetty's OutputStreamRequestContent. Compare that buffered byte-array path with Jetty's BytesRequestContent and verify the completed HTTP/2 request does not emit an unnecessary empty terminal data frame.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, spring
- Domain
- api, backend, networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100