Skip to content

HttpServerOperations::onOutboundComplete violates HTTP/2 trailer frame spec #3822

@lsc4719

Description

@lsc4719

HTTP/2 doesn't recommend adding Trailer header for trailer HEADERS frame.
But HttpServerOperations::onOutboundComplete mandates it.
HttpServerOperations::onOutboundComplete is RECOMMENDED in HTTP/1.1.
But it's not true for HTTP/2.

HTTP/1.1 recommends it.
HTTP/2 do not recommend it.
Please do not force Trailer header for trailer fields.

I have problems due to this reactor-netty's behavior. 😢
I'm making a proxy server with reactor-netty.
Proxy server must know all the trailer fields before actually reading it.
But HTTP/2 proxy clients don't use the Trailer header because it's not recommended by HTTP/2 spec.
So, it's impossible to make a proxy server with reactor-netty that deals with trailer fields properly.

Expected Behavior

Do not mandate Trailer header for trailer field(HTTP/1.1 : chunked, HTTP/2: trailer frame).

Actual Behavior

Do manadate Trailer header for trailer fields(HTTP/1.1: chunked, HTTP/2: trailer frame).

Steps to Reproduce

Following code snippet is a part of HttpServerOperations::onOutboundComplete.

	@Override
	protected void onOutboundComplete() {
		if (isWebsocket()) {
			// There is no need to proceed for 'HTTP/1.1 101 Switching Protocols',
			// a full response has been sent
			return;
		}

		final ChannelFuture f;
		if (log.isDebugEnabled()) {
			log.debug(format(channel(), "Last HTTP response frame"));
		}
		if (markSentHeaderAndBody()) {
			if (log.isDebugEnabled()) {
				log.debug(format(channel(), "Headers are not sent before onComplete()."));
			}

			f = channel().writeAndFlush(fullHttpResponse != null ? fullHttpResponse : newFullBodyMessage(EMPTY_BUFFER));
		}
		else if (markSentBody()) {
			HttpHeaders trailerHeaders = null;
			// https://datatracker.ietf.org/doc/html/rfc7230#section-4.1.2
			// A trailer allows the sender to include additional fields at the end
			// of a chunked message in order to supply metadata that might be
			// dynamically generated while the message body is sent, such as a
			// message integrity check, digital signature, or post-processing
			// status.
			if (trailerHeadersConsumer != null && isTransferEncodingChunked(nettyResponse)) {
				// https://datatracker.ietf.org/doc/html/rfc7230#section-4.4
				// When a message includes a message body encoded with the chunked
				// transfer coding and the sender desires to send metadata in the form
				// of trailer fields at the end of the message, the sender SHOULD
				// generate a Trailer header field before the message body to indicate
				// which fields will be present in the trailers.
				String declaredHeaderNames = responseHeaders.get(HttpHeaderNames.TRAILER);
				if (declaredHeaderNames != null) {
					trailerHeaders = new TrailerHeaders(declaredHeaderNames);
					try {
						trailerHeadersConsumer.accept(trailerHeaders);
					}
					catch (IllegalArgumentException e) {
						// A sender MUST NOT generate a trailer when header names are
						// HttpServerOperations.TrailerHeaders.DISALLOWED_TRAILER_HEADER_NAMES
						log.error(format(channel(), "Cannot apply trailer headers [{}]"), declaredHeaderNames, e);
					}
				}
			}

			f = channel().writeAndFlush(trailerHeaders != null && !trailerHeaders.isEmpty() ?
					new DefaultLastHttpContent(Unpooled.buffer(0), trailerHeaders) :
					EMPTY_LAST_CONTENT);
		}
		else {
			discard();
			terminate();
			return;
		}
		f.addListener(this);
	}

Possible Solution

Do not force Trailer header.

Your Environment

  • Reactor version(s) used: io.projectreactor.netty:reactor-netty-http:1.2.4
  • Other relevant libraries versions (eg. netty, ...):
  • JVM version (java -version): 17
openjdk version "17.0.15" 2025-04-15 LTS
OpenJDK Runtime Environment Zulu17.58+21-CA (build 17.0.15+6-LTS)
OpenJDK 64-Bit Server VM Zulu17.58+21-CA (build 17.0.15+6-LTS, mixed mode, sharing)
  • OS and version (eg. uname -a): Linux 6.11.0-26-generic #26~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Apr 17 19:20:47 UTC 2 x86_64 x86_64 x86_64 GNU/Linux

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions