Skip to main content

gRPC HTTP Transcoding

This extension provides a Java implementation of gRPC HTTP transcoding, similar to grpc-gateway in Go.

Refer to the google.api.http specification to understand how to define HTTP mappings.

Why Not Use a Sidecar?

Why not use a sidecar like Envoy gRPC-JSON transcoder for transcoding?

  • A sidecar acts as a black box, making debugging and testing inconvenient.
  • It lacks sufficient scalability, and customizing transcoding is challenging.

Dependencies

Select the dependencies that match your technology stack.

implementation("io.github.danielliu1123:grpc-server-boot-starter")
implementation("io.github.danielliu1123:grpc-starter-transcoding")
implementation("org.springframework.boot:spring-boot-starter-web")
tip

Since both WebFlux and gRPC utilize Netty, using the grpc-netty-shaded dependency will increase the size of the final JAR package by approximately 9MB. To avoid this, exclude the grpc-netty-shaded dependency and use the grpc-netty dependency instead.

Example

Transcoding is only support unary and server streaming RPCs.

Unary

syntax = "proto3";

import "google/api/annotations.proto";

package transcoding.mvc;

message SimpleRequest {
string requestMessage = 1;
}
message SimpleResponse {
string responseMessage = 1;
}
service SimpleService {
rpc UnaryRpc (SimpleRequest) returns (SimpleResponse) {
option (google.api.http) = {
post: "/unary",
body: "*"
};
}
}

HTTP:

curl -X POST -d '{"requestMessage": "World"}' http://localhost:8080/unary

gRPC:

grpcurl -plaintext -d '{"requestMessage": "World"}' localhost:9090 transcoding.mvc.SimpleService/UnaryRpc
info

When grpc.transcoding.auto-mapping is true(default value) and google.api.http option is not specified, the default mapping path is POST /<package>.<Service>/<MethodName> (case-sensitive).

Refer to webmvc example.

Server Streaming

It is implemented using SSE (Server-Sent Events), data will be returned in JSON format.

syntax = "proto3";

import "google/api/annotations.proto";

package transcoding.mvc;

message SimpleRequest {
string requestMessage = 1;
}
message SimpleResponse {
string responseMessage = 1;
}
service SimpleService {
rpc ServerStreamingRpc (SimpleRequest) returns (stream SimpleResponse) {
option (google.api.http) = {
get: "/serverstreaming"
};
}
}

HTTP:

curl http://localhost:8080/serverstreaming?requestMessage=World

gRPC:

grpcurl -plaintext -d '{"requestMessage": "World"}' localhost:9090 transcoding.mvc.SimpleService/ServerStreamingRpc

Refer to webmvc example.

info

The values of request message fields can be filled by path variables, request body and query parameters. If the same field exists, the priority is: path variables > request body > query parameters.

Extension Points

HeaderConverter

HeaderConverter is used to

  • Convert HTTP headers to gRPC metadata when transcode from HTTP to gRPC.
  • Convert gRPC metadata to HTTP headers when transcode from gRPC to HTTP.

The default implementation is DefaultHeaderConverter.

  • When converting HTTP headers to gRPC metadata (request), it removes all http canonical headers (except Authorization) and leaves only custom headers.
  • When converting gRPC metadata to HTTP headers (response), it removes all http canonical headers, headers with the prefix grpc-, headers with the suffix -bin and leaves only custom headers.

Use custom HeaderConverter to replace the default implementation:

@Component
public class CustomHeaderConverter implements HeaderConverter {

@Override
public Metadata toMetadata(HttpHeaders headers) {
// convert HttpHeaders to Metadata
}

@Override
public HttpHeaders toHttpHeaders(Metadata headers) {
// convert Metadata to HttpHeaders
}
}

Custom Transcoding Exception Handling

Provides two interfaces TranscodingExceptionResolver (Spring MVC) and ReactiveTranscodingExceptionHandler (Spring WebFlux) for custom transcoding exception handling.

The default implementations are DefaultTranscodingExceptionResolver and DefaultReactiveTranscodingExceptionHandler.

The logic of the default implementation is: when an exception occurs, gRPC status code will be converted to http status code, and then throw TranscodingRuntimeException, which will then be captured and handled by Spring MVC/Flux.

tip

If you want Spring to handle ResponseStatusException, you need to set spring.{mvc,webflux}.problemdetails.enabled to true.

TranscodingExceptionResolver

Customize a TranscodingExceptionResolver to replace the default implementation:

@Component
public class MyTranscodingExceptionResolver implements TranscodingExceptionResolver {

@Override
public ServerResponse resolve(StatusRuntimeException exception) {
// do your custom logic here ...
}
}
warning

Spring MVC does not handle ResponseStatusException thrown by HandlerFunction before Spring 6.2.0. Refer to spring-projects/spring-framework#32689.

ReactiveTranscodingExceptionHandler

Customize a ReactiveTranscodingExceptionHandler to replace the default implementation:

public class MyReactiveTranscodingExceptionResolver implements ReactiveTranscodingExceptionResolver {

@Override
public void resolve(MonoSink<ServerResponse> sink, StatusRuntimeException exception) {
// do your custom logic here ...

// return a exception response
// sink.error(new MyException());

// return a normal response
// ServerResponse.ok().bodyValue("Ops!").subscribe(sink::success);
}
}

Configurations

Disable transcoding:

grpc:
transcoding:
enabled: false

Disable auto-mapping:

grpc:
transcoding:
auto-mapping: false

Custom gRPC server endpoint:

grpc:
transcoding:
endpoint: localhost:9090
note

In most cases, you do not need to manually configure this property, it will automatically find the gRPC server endpoint of the current application.

How It Works

  1. During application startup, all protobuf configurations for HTTP mapping (google.api.http) are retrieved and registered into the internally implemented RouterFunction.

  2. When an HTTP request is received, the RouterFunction identifies the corresponding gRPC service based on the request’s URL and method. It then converts the HTTP request into a gRPC request, sends it to the gRPC server endpoint, and finally converts the gRPC response back into an HTTP response to return to the client.

For more details, refer to grpc-gateway.