spring-projects / spring-projects/spring-framework

JettyClientHttpRequest has high data frame overhead on http2

Open
#36,752 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

in: web status: waiting-for-triage
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.