Skip to content

HTTP/2 and HTTP/3 server response should not send an empty trailers frame - #6319

Closed
jnbdz wants to merge 1 commit into
eclipse-vertx:masterfrom
SiteNetSoft:issue-3985-empty-response-trailers
Closed

HTTP/2 and HTTP/3 server response should not send an empty trailers frame#6319
jnbdz wants to merge 1 commit into
eclipse-vertx:masterfrom
SiteNetSoft:issue-3985-empty-response-trailers

Conversation

@jnbdz

@jnbdz jnbdz commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Motivation

HttpServerResponse#trailers() lazily creates the trailers map, and HttpServerResponseImpl only checks whether the map exists when the response ends:

Future<Void> future = write_(chunk, end && trailedMap == null);
if (end && trailedMap != null) {
  future = stream.writeHeaders(trailedMap, true);
}

A handler that accesses trailers() without adding anything therefore sends the last DATA frame without END_STREAM, followed by an empty HEADERS frame carrying it. An empty trailers frame is valid HTTP/2, but it is an extra frame that serves no purpose, and as reported in #3985 some clients do not handle it well.

Fixes #3985 (the code discussed there has since been replaced by HttpServerResponseImpl, shared by HTTP/2 and HTTP/3; the behaviour is the same).

Changes

  • HttpServerResponseImpl#write: an empty trailers map is treated like no trailers, so END_STREAM rides on the last DATA frame. Non-empty trailers are sent as before.
  • Http2ServerTest#testEmptyTrailers: with the raw test client, asserts that exactly one HEADERS frame is received and that the last DATA frame ends the stream when the trailers are accessed but left empty. Without the change it fails with Unexpected trailers frame expected:<0> but was:<1> / Expected the last data frame to end the stream.

HTTP/1.1 is unaffected: Http1ServerResponse has its own path where an empty trailer map already produces the same bytes as no trailers.

Http2ServerTest, Http2Test, Http2MultiplexTest, Http3Test, Http3ServerTest and the existing testResponseTrailers* / testResponseNoTrailers tests on HTTP/1.1, HTTP/2 and HTTP/3 pass locally.

Independent from #6278 (request trailers), which does not touch the response path.

…rame

Motivation:

HttpServerResponse#trailers() lazily creates the trailers map, and the
response end logic only checks whether the map exists. A handler that
accesses the trailers without adding any makes the response send the last
data frame without the end of stream flag, followed by an empty HEADERS
frame that carries it. An empty trailers frame is valid, but it is an
extra frame that serves no purpose and some clients do not handle it well.
Fixes eclipse-vertx#3985.

Changes:

Treat an empty trailers map like no trailers when ending the response, so
that the last data frame ends the stream.

Add Http2ServerTest#testEmptyTrailers asserting that a single HEADERS frame
is sent and that the last DATA frame ends the stream when the trailers are
accessed but left empty.
@vietj

vietj commented Aug 16, 2026

Copy link
Copy Markdown
Member

I think it is valid for HTTP/2 to end a stream with an empty headers frame and we should not prevent that, so I'm not if favor of this change. I think instead we should clearly document the side effect of calling the trailers() methods

@vietj vietj closed this Aug 16, 2026
@jnbdz

jnbdz commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

Fair enough, thanks for the review. I'll send a small follow-up documenting on HttpServerResponse#trailers() (and the putTrailer variants) that calling it commits the response to sending a trailers frame, so an empty one is sent if nothing is added — which was the source of the confusion in #3985.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants