Class StreamAddressedContentHandler

java.lang.Object
io.netty.channel.ChannelHandlerAdapter
io.netty.channel.ChannelOutboundHandlerAdapter
org.mockserver.netty.unification.StreamAddressedContentHandler
All Implemented Interfaces:
io.netty.channel.ChannelHandler, io.netty.channel.ChannelOutboundHandler

@Sharable public class StreamAddressedContentHandler extends io.netty.channel.ChannelOutboundHandlerAdapter
Translates a StreamAddressedHttpContent into the child-channel equivalent when a streaming response is written on an Http2MultiplexHandler child channel.

The StreamAddressedHttpContent wrapper carries two pieces of out-of-band state — a stream id and an endStream() flag. On the multiplex path every stream has its own Http2StreamChannel — the channel is the stream — so the streamId() field is redundant here. Only the endStream() flag still carries information the child pipeline cannot otherwise recover.

Why this handler is required: without it the wrapper reaches Http2StreamFrameToHttpObjectCodec.encode(io.netty.channel.ChannelHandlerContext, io.netty.handler.codec.http.HttpObject, java.util.List<java.lang.Object>), whose only bare-HttpContent branch emits new DefaultHttp2DataFrame(content.retain(), false) — it hard-codes endStream=false. The terminal frame's endStream=true is therefore silently discarded, the stream is never closed, and an SSE/NDJSON/AWS-event-stream client receives every event and then hangs until it times out. Nothing is logged (GitHub issue #2669 follow-up).

The translation preserves the flag by picking the HttpObject subtype the codec maps correctly:

  • endStream() == trueDefaultLastHttpContent. The codec's encodeLastContent sets needFiller = true for a non-FullHttpMessage with empty trailers, so it emits DefaultHttp2DataFrame(content, endStream=true) even for an empty terminal buffer.
  • endStream() == false → plain DefaultHttpContent, which the codec maps to a non-terminal DATA frame exactly as the non-terminal chunks already work today.

Reference counting: the replacement content wraps the same ByteBuf, so buffer ownership transfers with it. The wrapper holds no resource of its own beyond that buffer, so this handler neither retains nor releases anything — it just swaps the object that carries the buffer downstream.

The handler is @Sharable and stateless, so a single INSTANCE can be installed on every child pipeline, matching how ConnectionScopeHandler.INSTANCE is used.

  • 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
     
  • Constructor Summary

    Constructors
    Constructor
    Description
     
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    write(io.netty.channel.ChannelHandlerContext ctx, Object msg, io.netty.channel.ChannelPromise promise)
     

    Methods inherited from class io.netty.channel.ChannelOutboundHandlerAdapter

    bind, close, connect, deregister, disconnect, flush, read

    Methods inherited from class io.netty.channel.ChannelHandlerAdapter

    ensureNotSharable, exceptionCaught, handlerAdded, 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

    exceptionCaught, handlerAdded, handlerRemoved
  • Field Details

  • Constructor Details

    • StreamAddressedContentHandler

      public StreamAddressedContentHandler()
  • Method Details

    • write

      public void write(io.netty.channel.ChannelHandlerContext ctx, Object msg, io.netty.channel.ChannelPromise promise) throws Exception
      Specified by:
      write in interface io.netty.channel.ChannelOutboundHandler
      Overrides:
      write in class io.netty.channel.ChannelOutboundHandlerAdapter
      Throws:
      Exception