Class GrpcHttp3Adapter
Reuses the existing GrpcFrameCodec, GrpcProtoDescriptorStore,
and GrpcJsonMessageConverter from mockserver-core -- no logic is
duplicated. The adapter handles:
- Inbound: detecting gRPC content-type, decoding the
5-byte length-prefixed gRPC frame(s) from the request body, converting
protobuf to JSON via the descriptor store, and tagging the request
with
x-grpc-service/x-grpc-methodso the response path can re-encode. - Outbound: converting the matched response's JSON body back to gRPC-framed protobuf, building the initial HTTP/3 HEADERS frame (without grpc-status), the DATA frame (gRPC framed body), and a separate trailing HEADERS frame with grpc-status/grpc-message and any user-authored trailing metadata -- the correct wire framing that gRPC clients expect over HTTP/3.
This class is stateless and thread-safe; all state is passed via method parameters.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final classHolds the parts of a gRPC response for HTTP/3 framing: the gRPC-framed body bytes, the grpc-status value, and an optional grpc-message. -
Method Summary
Modifier and TypeMethodDescriptionstatic io.netty.handler.codec.http3.DefaultHttp3DataFramebuildDataFrame(byte[] grpcFrameBytes) Build an HTTP/3 DATA frame from gRPC-framed bytes.static io.netty.handler.codec.http3.DefaultHttp3HeadersFrameBuild the initial HTTP/3 HEADERS frame for a gRPC response.static io.netty.handler.codec.http3.DefaultHttp3HeadersFramebuildInitialHeadersFrame(HttpResponse response) AsbuildInitialHeadersFrame(), additionally copying the matched response's own headers through to the client.static io.netty.handler.codec.http3.DefaultHttp3HeadersFramebuildTrailersOnlyFrame(String grpcStatus, String grpcMessage) Build a "trailers-only" HTTP/3 HEADERS frame that combines :status, content-type, and grpc-status/grpc-message in a single HEADERS frame (no DATA frame follows).static io.netty.handler.codec.http3.DefaultHttp3HeadersFramebuildTrailersOnlyFrame(String grpcStatus, String grpcMessage, HttpResponse response) AsbuildTrailersOnlyFrame(String, String), additionally copying the matched response's own headers and trailers through -- a trailers-only response is the client's only frame, so dropping either loses it entirely.static io.netty.handler.codec.http3.DefaultHttp3HeadersFramebuildTrailingHeadersFrame(String grpcStatus, String grpcMessage) Build the trailing HTTP/3 HEADERS frame for a gRPC response.static io.netty.handler.codec.http3.DefaultHttp3HeadersFramebuildTrailingHeadersFrame(String grpcStatus, String grpcMessage, HttpResponse response) AsbuildTrailingHeadersFrame(String, String), additionally emitting the matched response's user-authored trailers as gRPC trailing metadata.errorResponse(GrpcStatusMapper.GrpcStatusCode statusCode, String message) Build an error response (trailers-only pattern) for gRPC over HTTP/3.static booleanisGrpcRequest(String contentType) Detect whether the given content-type indicates a gRPC request.static HttpRequesttransformGrpcRequest(HttpRequest request, GrpcProtoDescriptorStore descriptorStore) Transform a gRPC request for the MockServer matching pipeline: decode the gRPC length-prefixed message(s), convert protobuf to JSON via the descriptor store, and tag the request with service/method markers.transformGrpcResponse(HttpResponse response, String serviceName, String methodName, GrpcProtoDescriptorStore descriptorStore) Convert the response from the matching pipeline back to a gRPC-framed response.
-
Method Details
-
isGrpcRequest
Detect whether the given content-type indicates a gRPC request. Delegates toGrpcStatusMapper.isGrpcContentType(String). -
transformGrpcRequest
public static HttpRequest transformGrpcRequest(HttpRequest request, GrpcProtoDescriptorStore descriptorStore) Transform a gRPC request for the MockServer matching pipeline: decode the gRPC length-prefixed message(s), convert protobuf to JSON via the descriptor store, and tag the request with service/method markers.- Parameters:
request- the raw HttpRequest with gRPC-framed binary bodydescriptorStore- the gRPC proto descriptor store- Returns:
- a transformed HttpRequest with a JSON body suitable for matching
- Throws:
GrpcException- if the gRPC path or framing is invalid
-
transformGrpcResponse
public static GrpcHttp3Adapter.GrpcResponseParts transformGrpcResponse(HttpResponse response, String serviceName, String methodName, GrpcProtoDescriptorStore descriptorStore) Convert the response from the matching pipeline back to a gRPC-framed response. Encodes the JSON body to protobuf, wraps it in a gRPC length-prefixed frame, and determines the grpc-status.- Parameters:
response- the matched HttpResponse (JSON body, grpc-status-name header)serviceName- the gRPC service name from the original requestmethodName- the gRPC method name from the original requestdescriptorStore- the gRPC proto descriptor store- Returns:
- a
GrpcHttp3Adapter.GrpcResponsePartswith the initial headers, body bytes, and trailing headers
-
errorResponse
public static GrpcHttp3Adapter.GrpcResponseParts errorResponse(GrpcStatusMapper.GrpcStatusCode statusCode, String message) Build an error response (trailers-only pattern) for gRPC over HTTP/3. Used when gRPC request processing fails before matching.- Parameters:
statusCode- the gRPC status codemessage- the error message- Returns:
- a
GrpcHttp3Adapter.GrpcResponsePartswith no body and the error status
-
buildInitialHeadersFrame
public static io.netty.handler.codec.http3.DefaultHttp3HeadersFrame buildInitialHeadersFrame()Build the initial HTTP/3 HEADERS frame for a gRPC response. Contains :status=200 and content-type=application/grpc but NOT grpc-status (which belongs in the trailing HEADERS frame). -
buildInitialHeadersFrame
public static io.netty.handler.codec.http3.DefaultHttp3HeadersFrame buildInitialHeadersFrame(HttpResponse response) AsbuildInitialHeadersFrame(), additionally copying the matched response's own headers through to the client.Without this, a unary HTTP/3 gRPC response silently dropped every header the expectation set --
withHeader("x-tenant-id", "acme")simply never arrived. HTTP/2 preserves them by cloning the response, and the HTTP/3 server-streaming path already copies them viaaddConfiguredHeaders, so unary HTTP/3 was the only path that lost them. gRPC protocol metadata is excluded (GrpcResponseStatusResolver.isGrpcProtocolMetadata(java.lang.String)) because this frame emits:statusandcontent-typeitself, andgrpc-status/grpc-messagebelong in the trailing HEADERS frame.- Parameters:
response- the matched response, may benull
-
buildTrailingHeadersFrame
public static io.netty.handler.codec.http3.DefaultHttp3HeadersFrame buildTrailingHeadersFrame(String grpcStatus, String grpcMessage) Build the trailing HTTP/3 HEADERS frame for a gRPC response. Contains grpc-status and optionally grpc-message. No pseudo-headers (no :status) because this is a trailing HEADERS frame. -
buildTrailingHeadersFrame
public static io.netty.handler.codec.http3.DefaultHttp3HeadersFrame buildTrailingHeadersFrame(String grpcStatus, String grpcMessage, HttpResponse response) AsbuildTrailingHeadersFrame(String, String), additionally emitting the matched response's user-authored trailers as gRPC trailing metadata.Without this, HTTP/3 silently dropped every trailer an expectation set:
response().withTrailer("x-request-cost", "42")and the gRPC chaos profile'scustomTrailerssimply never reached the client, on both the body and the body-less branch. HTTP/1.1 and HTTP/2 emit them from the response model viaMockServerHttpResponseToFullHttpResponse.mapResponseWithTrailers; the HTTP/3 gRPC writer builds its frames by hand and carried onlygrpc-status/grpc-message.The custom metadata rides the same terminal frame as the status, so the framing is unchanged -- there is still exactly one trailing HEADERS frame, written with
SHUTDOWN_OUTPUT. This is why HTTP/3 never had the HTTP/2 Trailers-Only defect thatGrpcToHttpResponseHandler.asTrailersOnlyIfHttp2guards against.- Parameters:
response- the matched response, may benullfor a transport-synthesized status (agrpc-timeoutdeadline, an encoding failure) that has no user-authored trailers
-
buildDataFrame
public static io.netty.handler.codec.http3.DefaultHttp3DataFrame buildDataFrame(byte[] grpcFrameBytes) Build an HTTP/3 DATA frame from gRPC-framed bytes. Returns null if the frame bytes are null or empty. -
buildTrailersOnlyFrame
public static io.netty.handler.codec.http3.DefaultHttp3HeadersFrame buildTrailersOnlyFrame(String grpcStatus, String grpcMessage) Build a "trailers-only" HTTP/3 HEADERS frame that combines :status, content-type, and grpc-status/grpc-message in a single HEADERS frame (no DATA frame follows). This is used for error responses where there is no message body. -
buildTrailersOnlyFrame
public static io.netty.handler.codec.http3.DefaultHttp3HeadersFrame buildTrailersOnlyFrame(String grpcStatus, String grpcMessage, HttpResponse response) AsbuildTrailersOnlyFrame(String, String), additionally copying the matched response's own headers and trailers through -- a trailers-only response is the client's only frame, so dropping either loses it entirely.Folding the user-authored trailers into this frame is the correct gRPC shape rather than a compromise: the Trailers-Only form is defined as
HTTP-Status Content-Type Trailers, andTrailersincludes custom metadata. A gRPC client reads this single end-of-stream frame as the call's trailing metadata. Emitting a second frame after it instead would be wrong twice over -- the frame is already written withSHUTDOWN_OUTPUT, and a non-terminal initial frame carryinggrpc-statusis exactly the HTTP/2 defect fixed inGrpcToHttpResponseHandler.asTrailersOnlyIfHttp2.
-