Class LenientInboundHttp2StreamFrameCodec

java.lang.Object
io.netty.channel.ChannelHandlerAdapter
io.netty.channel.ChannelInboundHandlerAdapter
io.netty.channel.ChannelDuplexHandler
io.netty.handler.codec.MessageToMessageCodec<io.netty.handler.codec.http2.Http2StreamFrame,io.netty.handler.codec.http.HttpObject>
io.netty.handler.codec.http2.Http2StreamFrameToHttpObjectCodec
org.mockserver.netty.unification.LenientInboundHttp2StreamFrameCodec
All Implemented Interfaces:
io.netty.channel.ChannelHandler, io.netty.channel.ChannelInboundHandler, io.netty.channel.ChannelOutboundHandler

public class LenientInboundHttp2StreamFrameCodec extends io.netty.handler.codec.http2.Http2StreamFrameToHttpObjectCodec
A Http2StreamFrameToHttpObjectCodec that is lenient on the INBOUND (request) side but strict on the OUTBOUND (response) side.

Netty's Http2StreamFrameToHttpObjectCodec carries a single validateHeaders flag that governs both directions of the HTTP/1-object ↔ HTTP/2-frame conversion. On the gRPC multiplex re-aggregating chain MockServer needs the two directions to differ:

  • Inbound (lenient, validateHeaders=false): a request header value with a leading space, an embedded 0x7F (DEL), or a control character must be accepted and reach the matchers rather than being rejected with RST_STREAM(PROTOCOL_ERROR) before matching. This mirrors the shared-connection path, which sets InboundHttp2ToHttpAdapterBuilder.validateHttpHeaders(false). The base class applies this flag in decode/newMessage/newFullMessage via HttpConversionUtil.toHttpRequest(int, io.netty.handler.codec.http2.Http2Headers, boolean)/toFullHttpRequest.
  • Outbound (strict): response header NAMES and trailer NAMES must still be validated exactly as Netty validates them by default, matching the shared-connection path whose AbstractHttp2ConnectionHandlerBuilder.isValidateHeaders() defaults to true. Passing false to the base class also silently relaxed this outbound name validation, which this subclass restores.

Only header/trailer NAMES are validated outbound — never values. The base class builds outbound headers with HttpConversionUtil.toHttp2Headers(..., validateHeaders), which uses the 2-arg DefaultHttp2Headers(validate, arraySizeHint) constructor: that installs a name validator (HTTP2_NAME_VALIDATOR) when validate is true and no value validator either way. (The separate 3-arg DefaultHttp2Headers(validate, validateValues, arraySizeHint) constructor can install a value validator, but the codec does not use it.) Header names are also lower-cased before validation, so an uppercase name never trips it — only an illegal token character (space, control char, DEL, separator, non-ASCII) does.

How the strict outbound check is re-asserted: rather than re-implementing the RFC token rule by hand (which would drift from Netty across upgrades), this override performs the exact conversion the base class would have performed with validation on — toHttp2Headers(msg, true) for a response head and toHttp2Headers(trailingHeaders, true) for non-empty trailers — and discards the result, letting it throw exactly the Http2Exception the strict path would throw. The subsequent super.encode then re-does the conversion with validateHeaders=false; this is one extra header conversion per response head (and per trailer block), a deliberate and acceptable cost to keep the check faithful to Netty. Per-chunk HttpContent carries no headers and is not validated.

A response to HEAD is sent as its header block alone, which ends the stream: the base class would send its body as well, which RFC 9110 section 9.3.2 does not allow. The header block keeps the content-length a GET would be sent, as Netty's HttpServerCodec does on HTTP/1.1.

  • Nested Class Summary

    Nested classes/interfaces inherited from interface io.netty.channel.ChannelHandler

    io.netty.channel.ChannelHandler.Sharable
  • Constructor Summary

    Constructors
    Constructor
    Description
     
  • Method Summary

    Modifier and Type
    Method
    Description
    protected void
    decode(io.netty.channel.ChannelHandlerContext ctx, io.netty.handler.codec.http2.Http2StreamFrame frame, List<Object> out)
     
    protected void
    encode(io.netty.channel.ChannelHandlerContext ctx, io.netty.handler.codec.http.HttpObject obj, List<Object> out)
     
    void
    exceptionCaught(io.netty.channel.ChannelHandlerContext ctx, Throwable cause)
    A stream's own error, trailers over maxHeaderSize among them, reaches the stream as an exception, already logged, and Netty then resets the stream with the error's code.
    void
    userEventTriggered(io.netty.channel.ChannelHandlerContext ctx, Object evt)
     
    void
    write(io.netty.channel.ChannelHandlerContext ctx, Object msg, io.netty.channel.ChannelPromise promise)
     

    Methods inherited from class io.netty.handler.codec.http2.Http2StreamFrameToHttpObjectCodec

    acceptInboundMessage, handlerAdded, isSsl

    Methods inherited from class io.netty.handler.codec.MessageToMessageCodec

    acceptOutboundMessage, channelRead, channelReadComplete

    Methods inherited from class io.netty.channel.ChannelDuplexHandler

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

    Methods inherited from class io.netty.channel.ChannelInboundHandlerAdapter

    channelActive, channelInactive, channelRegistered, channelUnregistered, channelWritabilityChanged

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

    • LenientInboundHttp2StreamFrameCodec

      public LenientInboundHttp2StreamFrameCodec()
  • Method Details

    • decode

      protected void decode(io.netty.channel.ChannelHandlerContext ctx, io.netty.handler.codec.http2.Http2StreamFrame frame, List<Object> out) throws Exception
      Overrides:
      decode in class io.netty.handler.codec.http2.Http2StreamFrameToHttpObjectCodec
      Throws:
      Exception
    • 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.handler.codec.MessageToMessageCodec<io.netty.handler.codec.http2.Http2StreamFrame,io.netty.handler.codec.http.HttpObject>
      Throws:
      Exception
    • encode

      protected void encode(io.netty.channel.ChannelHandlerContext ctx, io.netty.handler.codec.http.HttpObject obj, List<Object> out) throws Exception
      Overrides:
      encode in class io.netty.handler.codec.http2.Http2StreamFrameToHttpObjectCodec
      Throws:
      Exception
    • exceptionCaught

      public void exceptionCaught(io.netty.channel.ChannelHandlerContext ctx, Throwable cause) throws Exception
      A stream's own error, trailers over maxHeaderSize among them, reaches the stream as an exception, already logged, and Netty then resets the stream with the error's code. Do not pass it on: a handler further on logs it again and closes the stream itself, which resets it with CANCEL; for a header block sent after the request ended, on a stream whose whole response has been written, that close sends no reset at all.
      Specified by:
      exceptionCaught in interface io.netty.channel.ChannelHandler
      Specified by:
      exceptionCaught in interface io.netty.channel.ChannelInboundHandler
      Overrides:
      exceptionCaught in class io.netty.channel.ChannelInboundHandlerAdapter
      Throws:
      Exception
    • userEventTriggered

      public void userEventTriggered(io.netty.channel.ChannelHandlerContext ctx, Object evt) throws Exception
      Specified by:
      userEventTriggered in interface io.netty.channel.ChannelInboundHandler
      Overrides:
      userEventTriggered in class io.netty.channel.ChannelInboundHandlerAdapter
      Throws:
      Exception