Class GrpcToHttpResponseHandler

java.lang.Object
io.netty.channel.ChannelHandlerAdapter
io.netty.channel.ChannelOutboundHandlerAdapter
io.netty.handler.codec.MessageToMessageEncoder<HttpResponse>
org.mockserver.netty.grpc.GrpcToHttpResponseHandler
All Implemented Interfaces:
io.netty.channel.ChannelHandler, io.netty.channel.ChannelOutboundHandler

@Sharable public class GrpcToHttpResponseHandler extends io.netty.handler.codec.MessageToMessageEncoder<HttpResponse>
Converts a matched (JSON-bodied) HttpResponse back into a gRPC wire response: a length-prefixed protobuf frame plus a grpc-status trailer.

Service/method resolution. The gRPC service and method names are taken from x-grpc-service/x-grpc-method response headers when present (the forward-proxy path stamps them via GrpcForwardTranslator.decodeResponseFromUpstream(org.mockserver.model.HttpResponse, java.lang.String, java.lang.String, org.mockserver.grpc.GrpcProtoDescriptorStore), and users may set them explicitly). Otherwise they fall back to the per-connection GrpcPendingRequests registry populated by GrpcToHttpRequestHandler when it decoded the request, looked up by the response's HTTP/2 stream id. The matching pipeline does not propagate the internal x-grpc-* request headers onto the matched response, so without this fallback a normal mock expectation would return raw JSON on a stream the client expects to be framed protobuf (issue #2419). The same trap is documented on the HTTP/3 path in Http3GrpcResponseWriter.

Only successful responses are converted. A non-2xx response that carries no explicit gRPC status did not come from a matched gRPC expectation -- most commonly the 404 notFoundResponse produced when nothing matched. Converting it would invent a schema-valid example body and report OK, so a typo'd path would return a plausible green response with no client-side indicator. Such responses are instead mapped to a gRPC error status per the gRPC-over-HTTP/2 HTTP-status mapping (404 becomes UNIMPLEMENTED, which is what a real gRPC server returns for an unknown method).

Trailers, not headers. Per gRPC-over-HTTP/2 (and HTTP/3), a unary response must deliver grpc-status/grpc-message in a terminal trailing HEADERS frame, never in the initial response headers. Only content-type: application/grpc is a real header. gRPC-Web is the exception: it carries the status in an in-body trailer frame, so convertToGrpcWebResponse(org.mockserver.model.HttpResponse, java.lang.String) consumes the trailers and strips them from the response.

  • 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
    encode(io.netty.channel.ChannelHandlerContext ctx, HttpResponse response, List<Object> out)
     
    void
    write(io.netty.channel.ChannelHandlerContext ctx, Object msg, io.netty.channel.ChannelPromise promise)
    Releases the pending record when a response is written that this encoder will never see.

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

    acceptOutboundMessage

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

  • Method Details

    • write

      public void write(io.netty.channel.ChannelHandlerContext ctx, Object msg, io.netty.channel.ChannelPromise promise) throws Exception
      Releases the pending record when a response is written that this encoder will never see.

      MessageToMessageEncoder<HttpResponse> only matches the MockServer model type. The gRPC streaming path (GrpcStreamResponseActionHandler) writes raw Netty DefaultHttpResponse/HttpContent objects, so encode(io.netty.channel.ChannelHandlerContext, org.mockserver.model.HttpResponse, java.util.List<java.lang.Object>) -- and therefore consume() -- never runs for a streaming exchange, leaving its record orphaned. On HTTP/1.1 that record then poisons the single-shot slot: the next unary call on the same keep-alive connection is marked ambiguous, consume returns null, and its response goes out as unframed JSON with no grpc-status -- the exact issue #2419 shape. It also left the unary deadline timer armed alongside the streaming one, so both could terminate the stream.

      gRPC-Web runs over HTTP/1.1, so this is reachable in the primary browser scenario.

      Specified by:
      write in interface io.netty.channel.ChannelOutboundHandler
      Overrides:
      write in class io.netty.handler.codec.MessageToMessageEncoder<HttpResponse>
      Throws:
      Exception
    • encode

      protected void encode(io.netty.channel.ChannelHandlerContext ctx, HttpResponse response, List<Object> out)
      Specified by:
      encode in class io.netty.handler.codec.MessageToMessageEncoder<HttpResponse>