Class Http3GrpcBidiStreamHandler

java.lang.Object
org.mockserver.netty.http3.Http3GrpcBidiStreamHandler

public class Http3GrpcBidiStreamHandler extends Object
Drives true bidirectional gRPC streaming over a single QUIC bidirectional stream (HTTP/3). It is the HTTP/3 analogue of GrpcBidiStreamHandler but, instead of being a Netty pipeline handler, it is a plain helper driven by Http3MockServerHandler (which already extends Netty's Http3RequestStreamInboundHandler and receives frames incrementally via channelRead / channelInputClosed).

A QUIC stream is full-duplex, so the server can write response frames while the client is still sending request frames. The lifecycle is:

  • start() -- write the initial response HEADERS (:status=200, content-type=application/grpc, plus any configured headers) and any EAGER messages from the GrpcBidiResponse (honouring per-message delays);
  • onData(byte[]) -- feed inbound bytes to the incremental gRPC frame decoder; for each complete inbound message, convert protobuf to JSON, evaluate rules in order, and emit the first matching rule's responses as DATA frames;
  • onInputClosed() -- the client half-closed (FIN); once all scheduled response writes have drained, write the trailing HEADERS carrying grpc-status and shut the QUIC stream output;
  • onChannelInactive() -- the stream/connection was torn down; clears responseInProgress on the matched expectation via the completion callback.

All methods run on the QUIC stream's single event-loop thread, so the activeChains counter that orders the trailing HEADERS after all (possibly delayed) response writes needs no synchronization. The completion callback is guarded by an AtomicBoolean so it runs exactly once across every terminal path.

Inbound (client-to-server) breakpoints. When an INBOUND_STREAM breakpoint matcher matches this bidi stream at stream onset, the driver passes a non-null inboundStreamId (plus the matched breakpoint's clientId/id and the WS registry). Each inbound gRPC DATA frame is then parked in the StreamFrameBreakpointRegistry / StreamFrameCallbackDispatcher before being decoded, and resumed (CONTINUE / MODIFY / DROP / INJECT / CLOSE) by a dashboard or callback client. This is the HTTP/3 analogue of the inbound interception in GrpcBidiStreamHandler.

Ordering & flow control. The QUIC driver (Http3MockServerHandler) copies each DATA frame's bytes to a byte[] and releases the Http3DataFrame before calling onData(byte[]), so no ByteBuf is retained across a hold and the QUIC stream/connection flow-control window is never pinned by a parked frame. To preserve per-frame ordering while a frame is parked, at most one inbound frame is dispatched at a time; any frames the client sends while a frame is held are buffered in heldInboundFrames and drained in order when the held frame resolves. That buffer is bounded by the driver's existing maxRequestBodySize enforcement (the driver caps total accumulated inbound bytes before calling onData). When inbound breakpoints are inactive the frame takes a byte-for-byte-identical default path with zero added work.

  • Constructor Details

    • Http3GrpcBidiStreamHandler

      public Http3GrpcBidiStreamHandler(io.netty.channel.ChannelHandlerContext ctx, com.google.protobuf.Descriptors.MethodDescriptor methodDescriptor, GrpcJsonMessageConverter converter, GrpcBidiResponse config, Runnable completionCallback, MockServerLogger mockServerLogger)
      Constructs a bidi handler without inbound breakpoint support (default path).
    • Http3GrpcBidiStreamHandler

      public Http3GrpcBidiStreamHandler(io.netty.channel.ChannelHandlerContext ctx, com.google.protobuf.Descriptors.MethodDescriptor methodDescriptor, GrpcJsonMessageConverter converter, GrpcBidiResponse config, Runnable completionCallback, MockServerLogger mockServerLogger, Configuration configuration, String inboundStreamId, String inboundBreakpointClientId, String inboundBreakpointId, WebSocketClientRegistry webSocketClientRegistry)
      Constructs a bidi handler with optional inbound (client-to-server) breakpoint support. Inbound breakpoints are active only when inboundStreamId, inboundBreakpointClientId and webSocketClientRegistry are all non-null (i.e. an INBOUND_STREAM breakpoint matcher matched this stream at onset).
      Parameters:
      configuration - active server configuration (timeout / max-held rails); nullable
      inboundStreamId - unique stream id for inbound frame parking; null disables inbound breakpoints
      inboundBreakpointClientId - the matched inbound breakpoint's owning callback clientId; nullable
      inboundBreakpointId - the matched inbound breakpoint's id; nullable
      webSocketClientRegistry - per-server WS registry for callback dispatch; null disables inbound breakpoints
  • Method Details

    • start

      public void start()
      Write the initial response HEADERS and any eager messages. The top-level action delay (Action.getDelay()), if configured, delays the eager message stream (the HEADERS are sent promptly so inbound DATA frames never race ahead of them).
    • onData

      public void onData(byte[] bytes)
      Feed inbound request bytes. When inbound breakpoints are inactive this decodes complete gRPC frames and emits matching rule responses immediately (byte-for-byte default path). When active, the frame is parked at a breakpoint first (see class javadoc); frames that arrive while one is held are buffered in order.
    • onInputClosed

      public void onInputClosed()
      The client half-closed (END_STREAM). Finish once all scheduled responses have drained.
    • onChannelInactive

      public void onChannelInactive()
      The QUIC stream / connection was torn down. Evict any held inbound breakpoint frames (releasing their futures and the per-stream registry entry) and clear responseInProgress so a times-limited expectation is not left stuck when a bidi stream is abandoned without a clean END_STREAM.