PrematureCloseException from exceeding header limit after H1 upgrade to H2
samueldlightfoot opened this issue · comments
After performing successful upgrade from H1 to H2, if second request exceeds headers limit a PrematureCloseException is seen by the client rather than a 413.
It also appears future requests even with headers within the size limit do not detect the bad connection and also fail with PrematureCloseException, exacerbating the issue.
Expected Behavior
Client should receive 413 exception rather than PrematureCloseException.,
Actual Behavior
PrematureCloseException is seen on the client-side. We see the real error HeaderListSizeException propagates to ChannelOperations::onError, but at this point isDisposed == true (as channel::isActive == false), so therefore the error is not propagated further.
@Override
public final void onError(Throwable t) {
if (isDisposed()) {
if (log.isDebugEnabled()) {
log.debug(format(channel(), "An outbound error could not be processed"), t);
}
return;
}
OUTBOUND_CLOSE.set(this, Operators.cancelledSubscription());
onOutboundError(t);
}
2023-10-06T20:26:38.107-04:00 DEBUG 24876 --- [ctor-http-nio-5] reactor.netty.http.client.Http2Pool : [f0195865-1, L:/127.0.0.1:63494 - R:localhost/127.0.0.1:9995] Channel activated
2023-10-06T20:26:38.107-04:00 DEBUG 24876 --- [ctor-http-nio-4] reactor.netty.http.client.Http2Pool : [f0195865-1, L:/127.0.0.1:63494 - R:localhost/127.0.0.1:9995] Channel deactivated
2023-10-06T20:26:38.109-04:00 DEBUG 24876 --- [ctor-http-nio-4] r.n.http.client.HttpClientOperations : [f0195865, L:/127.0.0.1:63494 - R:localhost/127.0.0.1:9995](H2 - -1) New HTTP/2 stream
2023-10-06T20:26:38.109-04:00 DEBUG 24876 --- [ctor-http-nio-4] r.netty.http.client.HttpClientConfig : [f0195865, L:/127.0.0.1:63494 - R:localhost/127.0.0.1:9995](H2 - -1) Initialized HTTP/2 stream pipeline AbstractHttp2StreamChannel$3{(reactor.left.h2ToHttp11Codec = io.netty.handler.codec.http2.Http2StreamFrameToHttpObjectCodec), (reactor.left.httpTrafficHandler = reactor.netty.http.client.Http2StreamBridgeClientHandler), (reactor.left.httpMetricsHandler = reactor.netty.http.client.MicrometerHttpClientMetricsHandler), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
2023-10-06T20:26:38.109-04:00 DEBUG 24876 --- [ctor-http-nio-4] r.netty.http.client.HttpClientConnect : [f0195865/2-1, L:/127.0.0.1:63494 - R:localhost/127.0.0.1:9995] Handler is being applied: {uri=http://localhost:9995/callSuccess, method=POST}
2023-10-06T20:26:39.937-04:00 DEBUG 24876 --- [ctor-http-nio-4] r.n.http.client.Http2ConnectionProvider : [f0195865/2-1, L:/127.0.0.1:63494 ! R:localhost/127.0.0.1:9995] Stream closed, now: 0 active streams and 2147483647 max active streams.
2023-10-06T20:26:41.587-04:00 DEBUG 24876 --- [ctor-http-nio-4] r.netty.channel.ChannelOperations : [f0195865/2-1, L:/127.0.0.1:63494 ! R:localhost/127.0.0.1:9995] An outbound error could not be processed
io.netty.handler.codec.http2.Http2Exception$HeaderListSizeException: Header size exceeded max allowed size (1024)
at io.netty.handler.codec.http2.Http2Exception.headerListSizeError(Http2Exception.java:195) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.http2.Http2CodecUtil.headerListSizeExceeded(Http2CodecUtil.java:233) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.http2.HpackEncoder.encodeHeadersEnforceMaxHeaderListSize(HpackEncoder.java:140) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.http2.HpackEncoder.encodeHeaders(HpackEncoder.java:124) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.http2.DefaultHttp2HeadersEncoder.encodeHeaders(DefaultHttp2HeadersEncoder.java:74) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.http2.DefaultHttp2FrameWriter.writeHeadersInternal(DefaultHttp2FrameWriter.java:501) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.http2.DefaultHttp2FrameWriter.writeHeaders(DefaultHttp2FrameWriter.java:260) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder.sendHeaders(DefaultHttp2ConnectionEncoder.java:184) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder.writeHeaders0(DefaultHttp2ConnectionEncoder.java:233) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder.writeHeaders(DefaultHttp2ConnectionEncoder.java:151) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.http2.DecoratingHttp2FrameWriter.writeHeaders(DecoratingHttp2FrameWriter.java:45) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.http2.Http2FrameCodec.writeHeadersFrame(Http2FrameCodec.java:404) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.http2.Http2FrameCodec.write(Http2FrameCodec.java:303) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeWrite0(AbstractChannelHandlerContext.java:881) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeWrite(AbstractChannelHandlerContext.java:863) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:968) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:856) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.http2.AbstractHttp2StreamChannel.write0(AbstractHttp2StreamChannel.java:1108) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.http2.AbstractHttp2StreamChannel$Http2ChannelUnsafe.writeHttp2StreamFrame(AbstractHttp2StreamChannel.java:961) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.http2.AbstractHttp2StreamChannel$Http2ChannelUnsafe.write(AbstractHttp2StreamChannel.java:932) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.write(DefaultChannelPipeline.java:1367) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeWrite0(AbstractChannelHandlerContext.java:877) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeWrite(AbstractChannelHandlerContext.java:863) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:968) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:856) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:851) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.MessageToMessageEncoder.writePromiseCombiner(MessageToMessageEncoder.java:140) ~[netty-codec-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.MessageToMessageEncoder.write(MessageToMessageEncoder.java:120) ~[netty-codec-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.MessageToMessageCodec.write(MessageToMessageCodec.java:116) ~[netty-codec-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeWrite0(AbstractChannelHandlerContext.java:879) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeWrite(AbstractChannelHandlerContext.java:863) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:968) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:856) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at reactor.netty.http.client.Http2StreamBridgeClientHandler.write(Http2StreamBridgeClientHandler.java:51) ~[reactor-netty-http-1.1.7.jar:1.1.7]
at io.netty.channel.AbstractChannelHandlerContext.invokeWrite0(AbstractChannelHandlerContext.java:879) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeWrite(AbstractChannelHandlerContext.java:863) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:968) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:856) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at reactor.netty.http.client.AbstractHttpClientMetricsHandler.write(AbstractHttpClientMetricsHandler.java:116) ~[reactor-netty-http-1.1.7.jar:1.1.7]
at io.netty.channel.AbstractChannelHandlerContext.invokeWrite0(AbstractChannelHandlerContext.java:879) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeWriteAndFlush(AbstractChannelHandlerContext.java:940) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:966) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.writeAndFlush(AbstractChannelHandlerContext.java:934) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.AbstractChannelHandlerContext.writeAndFlush(AbstractChannelHandlerContext.java:984) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.DefaultChannelPipeline.writeAndFlush(DefaultChannelPipeline.java:1025) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.handler.codec.http2.AbstractHttp2StreamChannel.writeAndFlush(AbstractHttp2StreamChannel.java:490) ~[netty-codec-http2-4.1.92.Final.jar:4.1.92.Final]
at reactor.netty.http.HttpOperations.lambda$send$0(HttpOperations.java:143) ~[reactor-netty-http-1.1.7.jar:1.1.7]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:132) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2545) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.request(FluxContextWrite.java:136) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:171) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.request(MonoFlatMap.java:194) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.request(FluxContextWrite.java:136) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2341) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2215) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onSubscribe(FluxContextWrite.java:101) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onSubscribe(MonoFlatMap.java:117) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:96) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onSubscribe(FluxContextWrite.java:101) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:55) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.Mono.subscribe(Mono.java:4485) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxConcatIterable$ConcatIterableSubscriber.onComplete(FluxConcatIterable.java:147) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxConcatIterable.subscribe(FluxConcatIterable.java:60) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.MonoFromFluxOperator.subscribe(MonoFromFluxOperator.java:81) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.Operators$MonoInnerProducerBase.complete(Operators.java:2811) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.MonoSingle$SingleSubscriber.onComplete(MonoSingle.java:180) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onComplete(FluxMapFuseable.java:152) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2547) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:171) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.MonoSingle$SingleSubscriber.doOnRequest(MonoSingle.java:103) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.Operators$MonoInnerProducerBase.request(Operators.java:2878) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2341) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2215) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.MonoSingle$SingleSubscriber.onSubscribe(MonoSingle.java:115) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:96) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:55) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxFromMonoOperator.subscribe(FluxFromMonoOperator.java:83) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxDeferContextual.subscribe(FluxDeferContextual.java:57) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.netty.http.client.HttpClientConnect$HttpIOHandlerObserver.onStateChange(HttpClientConnect.java:445) ~[reactor-netty-http-1.1.7.jar:1.1.7]
at reactor.netty.ReactorNetty$CompositeConnectionObserver.onStateChange(ReactorNetty.java:710) ~[reactor-netty-core-1.1.7.jar:1.1.7]
at reactor.netty.http.client.Http2ConnectionProvider$DisposableAcquire.operationComplete(Http2ConnectionProvider.java:381) ~[reactor-netty-http-1.1.7.jar:1.1.7]
at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:590) ~[netty-common-4.1.92.Final.jar:4.1.92.Final]
at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:557) ~[netty-common-4.1.92.Final.jar:4.1.92.Final]
at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:492) ~[netty-common-4.1.92.Final.jar:4.1.92.Final]
at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:185) ~[netty-common-4.1.92.Final.jar:4.1.92.Final]
at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:35) ~[netty-common-4.1.92.Final.jar:4.1.92.Final]
at reactor.netty.http.client.Http2ConnectionProvider$DisposableAcquire.onNext(Http2ConnectionProvider.java:317) ~[reactor-netty-http-1.1.7.jar:1.1.7]
at reactor.netty.http.client.Http2ConnectionProvider$DisposableAcquire.onNext(Http2ConnectionProvider.java:210) ~[reactor-netty-http-1.1.7.jar:1.1.7]
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107) ~[reactor-core-3.5.6.jar:3.5.6]
at reactor.netty.http.client.Http2Pool$Borrower.deliver(Http2Pool.java:805) ~[reactor-netty-http-1.1.7.jar:1.1.7]
at reactor.netty.http.client.Http2Pool.lambda$drainLoop$1(Http2Pool.java:387) ~[reactor-netty-http-1.1.7.jar:1.1.7]
at io.netty.util.concurrent.AbstractEventExecutor.runTask$$$capture(AbstractEventExecutor.java:174) ~[netty-common-4.1.92.Final.jar:4.1.92.Final]
at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java) ~[netty-common-4.1.92.Final.jar:4.1.92.Final]
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute$$$capture(AbstractEventExecutor.java:167) ~[netty-common-4.1.92.Final.jar:4.1.92.Final]
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java) ~[netty-common-4.1.92.Final.jar:4.1.92.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470) ~[netty-common-4.1.92.Final.jar:4.1.92.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:569) ~[netty-transport-4.1.92.Final.jar:4.1.92.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[netty-common-4.1.92.Final.jar:4.1.92.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.92.Final.jar:4.1.92.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.92.Final.jar:4.1.92.Final]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
2023-10-06T20:26:41.589-04:00 DEBUG 24876 --- [ctor-http-nio-4] r.n.http.client.Http2ConnectionProvider : [f0195865/2-1, L:/127.0.0.1:63494 ! R:localhost/127.0.0.1:9995] Stream opened, now: 0 active streams and 2147483647 max active streams.
2023-10-06T20:26:41.590-04:00 WARN 24876 --- [ctor-http-nio-4] r.netty.http.client.HttpClientConnect : [f0195865/2-1, L:/127.0.0.1:63494 ! R:localhost/127.0.0.1:9995] The connection observed an error
reactor.netty.http.client.PrematureCloseException: Connection prematurely closed BEFORE response
`
Steps to Reproduce
Use client-side protocols of H1 & H2 forcing upgrade of connection from H1 to H2.
Send a second request (after initial connect) that exceeds header limit, and PrematureCloseException will be seen by client.
Possible Solution
@pderop Not sure why your comment was deleted, but many thanks! The change makes perfect sense given the request is never sent to the server.
Thanks again for looking into this. Our application logic will be able to make good use of the cause.