Skip to main content

gRPC HTTP transcoding

This extension is Java implementation of gRPC HTTP transcoding, like grpc-gateway in Go.

Read google.api.http specification to learn how to define the HTTP mapping.

Why Not Sidecar

Why not just use a sidecar like Envoy gRPC-JSON transcoder to do the transcoding?

  • Sidecar is like a black box and is inconvenient for debugging and testing
  • Scalability is not enough, and transcoding customization is challenging.

Dependencies

Choose the appropriate dependencies based on your 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

Because both webflux and gRPC use netty, if you use the grpc-netty-shaded dependency, the final jar package will be larger (~9MB). Therefore, 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. When the application starts up, it will get all the protobuf configurations for HTTP mapping (google.api.http), and then register them into the internally implemented RouterFunction.

  2. When an HTTP request comes in, the RouterFunction will find the corresponding gRPC service based on the request’s URL and method, then convert the HTTP request into a gRPC request, send it to the gRPC server endpoint, and finally convert the gRPC response back into an HTTP response to return.

Refer to grpc-gateway for more details.