Class GrpcBidiStreamHandler

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

public class GrpcBidiStreamHandler extends io.netty.channel.ChannelInboundHandlerAdapter
Per-stream handler for true bidirectional gRPC streaming. NOT @Sharable -- holds per-stream state (the incremental frame decoder, finished guard).

Phase 3b behaviour (rule-driven via GrpcBidiResponse):

  • On Http2HeadersFrame: applies the top-level action delay (if configured) before writing the initial response HEADERS (:status=200, content-type=application/grpc, plus any configured headers from the GrpcBidiResponse, endStream=false). Then writes any EAGER messages from the response, honouring per-message GrpcStreamMessage.getDelay() via event-loop scheduling (messages are chained so ordering is preserved and the trailing grpc-status is written only after all scheduled messages complete).
  • On Http2DataFrame: feeds content bytes to IncrementalGrpcFrameDecoder; for each complete inbound message, converts to JSON via the converter, evaluates rules in order (first match emits its responses as DATA frames). If no rule matches, no response is emitted. If the frame has endStream=true, calls finish(ChannelHandlerContext).
  • finish(): writes trailing HEADERS with configured grpc-status and endStream=true. Guarded to run at most once.

The handler supports two modes:

  • Phase 3b (GrpcBidiResponse-driven): Constructed with a GrpcBidiResponse config; eager messages, rules, status, and headers come from the config.
  • Phase 3a (legacy responder function): Constructed with a Function responder for backward compatibility with existing tests.

Flow control: the channel's autoRead is set to false when this handler is added; after processing each inbound frame, ctx.read() is called to request the next frame. If the decoder's buffer cap is exceeded, a RESOURCE_EXHAUSTED trailing status is written and the stream is finished.

Error handling: exceptions during channelRead are caught and result in an INTERNAL grpc-status trailer, never an uncaught exception propagating up the pipeline.

  • Constructor Details

    • GrpcBidiStreamHandler

      public GrpcBidiStreamHandler(com.google.protobuf.Descriptors.MethodDescriptor methodDescriptor, GrpcJsonMessageConverter converter, Function<String,List<String>> responder)
      Phase 3a constructor: function-based responder (backward compatible).
      Parameters:
      methodDescriptor - the resolved gRPC method descriptor
      converter - JSON/protobuf converter for the method's message types
      responder - maps an inbound message JSON string to a list of response JSON strings; for 3a the default is echo: returns [inboundJson]
    • GrpcBidiStreamHandler

      public GrpcBidiStreamHandler(com.google.protobuf.Descriptors.MethodDescriptor methodDescriptor, GrpcJsonMessageConverter converter, GrpcBidiResponse config)
      Phase 3b constructor: GrpcBidiResponse-driven (without completion callback).
      Parameters:
      methodDescriptor - the resolved gRPC method descriptor
      converter - JSON/protobuf converter for the method's message types
      config - the GrpcBidiResponse configuration from the matched expectation
    • GrpcBidiStreamHandler

      public GrpcBidiStreamHandler(com.google.protobuf.Descriptors.MethodDescriptor methodDescriptor, GrpcJsonMessageConverter converter, GrpcBidiResponse config, Runnable completionCallback)
      Phase 3b constructor: GrpcBidiResponse-driven with completion callback. The completion callback is invoked exactly once when the stream finishes (or errors), clearing responseInProgress on the matched expectation.
      Parameters:
      methodDescriptor - the resolved gRPC method descriptor
      converter - JSON/protobuf converter for the method's message types
      config - the GrpcBidiResponse configuration from the matched expectation
      completionCallback - invoked once on stream finish to clear responseInProgress
    • GrpcBidiStreamHandler

      public GrpcBidiStreamHandler(com.google.protobuf.Descriptors.MethodDescriptor methodDescriptor, GrpcJsonMessageConverter converter, GrpcBidiResponse config, Runnable completionCallback, Configuration configuration, String inboundStreamId)
      Phase 3b constructor: GrpcBidiResponse-driven with completion callback and inbound breakpoints.
      Parameters:
      methodDescriptor - the resolved gRPC method descriptor
      converter - JSON/protobuf converter for the method's message types
      config - the GrpcBidiResponse configuration from the matched expectation
      completionCallback - invoked once on stream finish to clear responseInProgress
      configuration - the active server configuration (null to disable inbound breakpoints)
      inboundStreamId - the stream ID for inbound breakpoints (null to disable)
    • GrpcBidiStreamHandler

      public GrpcBidiStreamHandler(com.google.protobuf.Descriptors.MethodDescriptor methodDescriptor, GrpcJsonMessageConverter converter, GrpcBidiResponse config, Runnable completionCallback, Configuration configuration, String inboundStreamId, WebSocketClientRegistry webSocketClientRegistry)
      Deprecated.
      use the constructor that accepts inboundBreakpointClientId and inboundBreakpointId from the outer caller to avoid mis-routing across clients
      Phase 3b constructor: GrpcBidiResponse-driven with completion callback, inbound breakpoints, and per-server WebSocket registry. Performs its own findMatch for inbound breakpoints.
    • GrpcBidiStreamHandler

      public GrpcBidiStreamHandler(com.google.protobuf.Descriptors.MethodDescriptor methodDescriptor, GrpcJsonMessageConverter converter, GrpcBidiResponse config, Runnable completionCallback, Configuration configuration, String inboundStreamId, WebSocketClientRegistry webSocketClientRegistry, String inboundBreakpointClientId, String inboundBreakpointId)
      Phase 3b constructor: GrpcBidiResponse-driven with completion callback, inbound breakpoints, per-server WebSocket registry, and pre-resolved breakpoint identity from the outer caller.
      Parameters:
      methodDescriptor - the resolved gRPC method descriptor
      converter - JSON/protobuf converter for the method's message types
      config - the GrpcBidiResponse configuration from the matched expectation
      completionCallback - invoked once on stream finish to clear responseInProgress
      configuration - the active server configuration (null to disable inbound breakpoints)
      inboundStreamId - the stream ID for inbound breakpoints (null to disable)
      webSocketClientRegistry - the per-server WS registry for callback dispatch (null to disable)
      inboundBreakpointClientId - the matched inbound breakpoint's owning clientId (from outer caller)
      inboundBreakpointId - the matched inbound breakpoint's id (from outer caller)
  • 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
    • channelRead

      public void channelRead(io.netty.channel.ChannelHandlerContext ctx, Object msg)
      Specified by:
      channelRead in interface io.netty.channel.ChannelInboundHandler
      Overrides:
      channelRead in class io.netty.channel.ChannelInboundHandlerAdapter
    • exceptionCaught

      public void exceptionCaught(io.netty.channel.ChannelHandlerContext ctx, Throwable cause)
      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
    • channelInactive

      public void channelInactive(io.netty.channel.ChannelHandlerContext ctx) throws Exception
      Handles channel deactivation (client disconnect, connection closed, abandoned stream). If the stream has not already finished via finish(io.netty.channel.ChannelHandlerContext) or writeTrailer(io.netty.channel.ChannelHandlerContext, org.mockserver.grpc.GrpcStatusMapper.GrpcStatusCode, java.lang.String), the completion callback is invoked to clear responseInProgress on the matched expectation. This prevents a times-limited expectation from being stuck forever when a bidi stream is abandoned without a clean END_STREAM.

      The callback is self-guarded by callbackInvoked (AtomicBoolean CAS), so it runs exactly once across all terminal paths: normal finish, error trailer, channel inactive, and exception caught.

      Also evicts any held inbound breakpoint frames to prevent resource leaks and hanging futures.

      Specified by:
      channelInactive in interface io.netty.channel.ChannelInboundHandler
      Overrides:
      channelInactive in class io.netty.channel.ChannelInboundHandlerAdapter
      Throws:
      Exception