Class ConnectionScopeHandler
- All Implemented Interfaces:
io.netty.channel.ChannelHandler,io.netty.channel.ChannelInboundHandler
Why this exists. Netty's Http2MultiplexHandler
gives every HTTP/2 stream its own Http2StreamChannel child
channel. AbstractHttp2StreamChannel delegates localAddress()/remoteAddress()
to the parent but does not inherit channel attributes. Every MockServer subsystem
that reads connection-scoped state from ctx.channel() therefore misbehaves on a child stream
channel, because the connection-level decisions (TLS negotiation, negotiated protocol, proxying,
local-host set, client certificates, ...) were all recorded on the parent channel during
port unification / TLS handshake, before the multiplex handler split the connection into streams.
Concretely, without this propagation the following break on a multiplexed HTTP/2 stream:
SniHandler.getALPNProtocolreturnsnull→ the request is not recognised asHTTP_2, so the stream id is never captured andwithProtocol(HTTP_2)matching and HAR/log protocol reporting are wrong;PortUnificationHandler.isHttp2Enabledreturnsfalse→ the callback and dashboard WebSocket handlers attempt a WebSocket handshake over an HTTP/2 stream instead of returning501;SniHandler.retrieveClientCertificatesreturnsnull→ mTLS control-plane authentication fails over HTTP/2;HttpRequestHandler.isProxyingRequestreturnsfalse(andHttpActionHandler.getRemoteAddressreturnsnull) → a proxied (SOCKS/CONNECT/original-destination/port-forward) request arriving over HTTP/2 is mis-routed: either treated as a local request, or forwarded to the wrong upstream (falling back to theHostheader instead of the recorded CONNECT/original-destination target).
Design: copy values once, do not read through the parent lazily. The attribute
values are copied from the parent onto the child once, when the child pipeline is being
built (handlerAdded(ChannelHandlerContext)). We deliberately do not keep a
reference to the parent channel and read through it on demand: a child stream can outlive interest
in (or mutation of) the parent's state, and lazy reads would re-introduce exactly the shared,
connection-wide mutable coupling that giving each stream its own channel is meant to remove. The
connection-scoped attributes copied here are all established during connection setup and are
stable for the life of the connection, so a one-shot copy is correct.
This handler is @Sharable and stateless, so a single instance can
be installed as the first handler of every child pipeline. After copying, it removes itself from
the pipeline — its job is done and it must not sit in the data path.
-
Nested Class Summary
Nested classes/interfaces inherited from interface io.netty.channel.ChannelHandler
io.netty.channel.ChannelHandler.Sharable -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final List<io.netty.util.AttributeKey<?>>The connection-scoped attribute keys copied from parent to child.static final ConnectionScopeHandler -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoidhandlerAdded(io.netty.channel.ChannelHandlerContext ctx) static voidpropagate(io.netty.channel.Channel parent, io.netty.channel.Channel child) Methods inherited from class io.netty.channel.ChannelInboundHandlerAdapter
channelActive, channelInactive, channelRead, channelReadComplete, channelRegistered, channelUnregistered, channelWritabilityChanged, exceptionCaught, userEventTriggeredMethods inherited from class io.netty.channel.ChannelHandlerAdapter
ensureNotSharable, handlerRemoved, isSharableMethods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, waitMethods inherited from interface io.netty.channel.ChannelHandler
handlerRemoved
-
Field Details
-
CONNECTION_SCOPED_ATTRIBUTES
The connection-scoped attribute keys copied from parent to child.Each entry references the
publicAttributeKeyconstant declared by the class that owns and reads it, so there is a single shared source of truth: a rename of the key value on the owning class updates both the reader and this propagation list at once. (The keys are all process-wide singletons viaAttributeKey.valueOf(String)regardless, but referencing the constant — not a re-typed string literal — is what makes the coupling compile-time.)The list was derived by enumerating every channel attribute that is set on the connection (parent) channel during port unification / TLS handshake / proxy detection and later read from
ctx.channel()by a handler that runs in the child stream pipeline:LOCAL_HOST_HEADERS—HttpRequestHandler.getLocalAddresses;PROXYING—HttpRequestHandler.isProxyingRequest(set on the parent by the SOCKS/CONNECT/transparent proxy handlers);REMOTE_SOCKET—HttpActionHandler.getRemoteAddresson the forward path (the CONNECT / original-destination / port-forward target); the connection-scoped sibling ofPROXYING— set on the parent by the same proxy handlers and the server bootstrap;HTTP2_ENABLED—PortUnificationHandler.isHttp2Enabled, read by the callback and dashboard WebSocket handlers;TLS_ENABLED_UPSTREAM/TLS_ENABLED_DOWNSTREAM—PortUnificationHandler.isSslEnabledUpstream/Downstream;NETTY_SSL_CONTEXT_FACTORY—PortUnificationHandler.getNettySslContextFactory;NEGOTIATED_APPLICATION_PROTOCOL/UPSTREAM_SSL_HANDLER—SniHandler.getALPNProtocol;UPSTREAM_CLIENT_CERTIFICATES/UPSTREAM_SSL_ENGINE—SniHandler.retrieveClientCertificates;SNI_HOSTNAME—SniHandler.getSniHostname.
HTTP_ENABLEDandTRANSPARENT_ORIGINAL_DST_RESOLVEDare intentionally excluded: both are read only by connection-level handlers that never run on a child stream channel.TRACE_CONTEXT,WS_REGISTRY_KEYand the CORS attributes are excluded because they are (re-)initialised on the child itself, not inherited from the connection. -
INSTANCE
-
-
Constructor Details
-
ConnectionScopeHandler
public ConnectionScopeHandler()
-
-
Method Details
-
handlerAdded
public void handlerAdded(io.netty.channel.ChannelHandlerContext ctx) - Specified by:
handlerAddedin interfaceio.netty.channel.ChannelHandler- Overrides:
handlerAddedin classio.netty.channel.ChannelHandlerAdapter
-
propagate
public static void propagate(io.netty.channel.Channel parent, io.netty.channel.Channel child) Copies everyconnection-scoped attributewhose value is non-null fromparentontochild. Attributes that were never set on the parent are left unset on the child (rather than written asnull), preserving the "attribute absent" semantics that the reader helpers rely on.- Parameters:
parent- the connection (parent) channel that recorded the connection-scoped statechild- the per-stream (child) channel to copy the state onto
-