Class ConnectionScopeHandler

java.lang.Object
io.netty.channel.ChannelHandlerAdapter
io.netty.channel.ChannelInboundHandlerAdapter
org.mockserver.netty.unification.ConnectionScopeHandler
All Implemented Interfaces:
io.netty.channel.ChannelHandler, io.netty.channel.ChannelInboundHandler

@Sharable public class ConnectionScopeHandler extends io.netty.channel.ChannelInboundHandlerAdapter
Copies the connection-scoped channel attributes from a parent channel onto a child channel.

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.getALPNProtocol returns null → the request is not recognised as HTTP_2, so the stream id is never captured and withProtocol(HTTP_2) matching and HAR/log protocol reporting are wrong;
  • PortUnificationHandler.isHttp2Enabled returns false → the callback and dashboard WebSocket handlers attempt a WebSocket handshake over an HTTP/2 stream instead of returning 501;
  • SniHandler.retrieveClientCertificates returns null → mTLS control-plane authentication fails over HTTP/2;
  • HttpRequestHandler.isProxyingRequest returns false (and HttpActionHandler.getRemoteAddress returns null) → 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 the Host header 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

    Fields
    Modifier and Type
    Field
    Description
    static final List<io.netty.util.AttributeKey<?>>
    The connection-scoped attribute keys copied from parent to child.
     
  • Constructor Summary

    Constructors
    Constructor
    Description
     
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    handlerAdded(io.netty.channel.ChannelHandlerContext ctx)
     
    static void
    propagate(io.netty.channel.Channel parent, io.netty.channel.Channel child)
    Copies every connection-scoped attribute whose value is non-null from parent onto child.

    Methods inherited from class io.netty.channel.ChannelInboundHandlerAdapter

    channelActive, channelInactive, channelRead, channelReadComplete, channelRegistered, channelUnregistered, channelWritabilityChanged, exceptionCaught, userEventTriggered

    Methods inherited from class io.netty.channel.ChannelHandlerAdapter

    ensureNotSharable, handlerRemoved, isSharable

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

    Methods inherited from interface io.netty.channel.ChannelHandler

    handlerRemoved
  • Field Details

    • CONNECTION_SCOPED_ATTRIBUTES

      public static final List<io.netty.util.AttributeKey<?>> CONNECTION_SCOPED_ATTRIBUTES
      The connection-scoped attribute keys copied from parent to child.

      Each entry references the public AttributeKey constant 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 via AttributeKey.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_HEADERSHttpRequestHandler.getLocalAddresses;
      • PROXYINGHttpRequestHandler.isProxyingRequest (set on the parent by the SOCKS/CONNECT/transparent proxy handlers);
      • REMOTE_SOCKETHttpActionHandler.getRemoteAddress on the forward path (the CONNECT / original-destination / port-forward target); the connection-scoped sibling of PROXYING — set on the parent by the same proxy handlers and the server bootstrap;
      • HTTP2_ENABLEDPortUnificationHandler.isHttp2Enabled, read by the callback and dashboard WebSocket handlers;
      • TLS_ENABLED_UPSTREAM / TLS_ENABLED_DOWNSTREAMPortUnificationHandler.isSslEnabledUpstream/Downstream;
      • NETTY_SSL_CONTEXT_FACTORYPortUnificationHandler.getNettySslContextFactory;
      • NEGOTIATED_APPLICATION_PROTOCOL / UPSTREAM_SSL_HANDLERSniHandler.getALPNProtocol;
      • UPSTREAM_CLIENT_CERTIFICATES / UPSTREAM_SSL_ENGINESniHandler.retrieveClientCertificates;
      • SNI_HOSTNAMESniHandler.getSniHostname.
      HTTP_ENABLED and TRANSPARENT_ORIGINAL_DST_RESOLVED are intentionally excluded: both are read only by connection-level handlers that never run on a child stream channel. TRACE_CONTEXT, WS_REGISTRY_KEY and the CORS attributes are excluded because they are (re-)initialised on the child itself, not inherited from the connection.
    • INSTANCE

      public static final ConnectionScopeHandler INSTANCE
  • Constructor Details

    • ConnectionScopeHandler

      public ConnectionScopeHandler()
  • Method Details

    • handlerAdded

      public void handlerAdded(io.netty.channel.ChannelHandlerContext ctx)
      Specified by:
      handlerAdded in interface io.netty.channel.ChannelHandler
      Overrides:
      handlerAdded in class io.netty.channel.ChannelHandlerAdapter
    • propagate

      public static void propagate(io.netty.channel.Channel parent, io.netty.channel.Channel child)
      Copies every connection-scoped attribute whose value is non-null from parent onto child. Attributes that were never set on the parent are left unset on the child (rather than written as null), preserving the "attribute absent" semantics that the reader helpers rely on.
      Parameters:
      parent - the connection (parent) channel that recorded the connection-scoped state
      child - the per-stream (child) channel to copy the state onto