Publishing gRPC services over REST/HTTP with gRPC Gateway

Tweet about this on TwitterShare on LinkedInShare on FacebookShare on Google+Share on Reddit

In the previous post we’ve seen how to implement a gRPC service in Scala. While gRPC is a great way to implement remote services, many client/server interactions are still implemented using REST/HTTP nowadays.

So the question is: Is it possible to use gRPC to define and implement services and make them available over REST at the same time?

Well, it turns out it’s possible. The translation from protobuf to JSON format is quite straightforward. The only thing left is to define associated endpoints for each of the gRPC calls. Here again it can be easily done along with the gRPC definition using the HTTP annotations from Google.

This is exactly what GRPC-Gateway provides. While GRPC-Gateway is implemented in Go it can run along with any gRPC implementation as it only relies on the protobuf/gRPC definitions (.proto files). It then spawns up an HTTP proxy that translates and forwards REST (HTTP/JSON) calls to a gRPC server.

Fortunately ScalaPB‘s code generation makes it possible to implement the same functionalities in Scala.

Architecture

The Scala’s GRPC-Gateway is made of 3 components:

  • The code generator: Generates source files from the .proto files definition
  • The runtime library: Provides the runtime infrastructure (e.g. Netty server)
  • The user code: Implements business logic and runs infrastructure (e.g. spawns up server on specific port)

Code generators

Code generators run at compile time to generate application source files. Therefore their code is similar to compiler plugin code and is executed directly by SBT. It means the generator source code must be compatible with the Scala version used by SBT. Currently Scala 2.10 is used by SBT 0.13.

As the GRPC-Gateway code generators relies on annotations define in protobuf files, these files need to be compile against Scala 2.10.

Note that this code won’t be embedded into the application’s package, it is only used to generate sources that will in turn be compiles and packaged with the application.

ScalaPB code generation

ScalaPB provides an easy way to define your own code generators. All you have to do is to extend protocbridge.ProtocCodeGenerator and implement the def run(request: CodeGeneratorRequest): CodeGeneratorResponse method.

Then the code generation is performed by means of a FunctionalPrinter to which you pass the strings of generated code:

val fp = FunctionalPrinter()
  .add(s"package io.grpc.gateway.MyGatewayHandler")
  .newline
  .add("object MyGatewayHandler { /* More code comes here */ }")
  .newline

All you have to do is then create a File.Builder and add the content using the FunctionalPrinter

val b = CodeGeneratorResponse.File.newBuilder()
b.setName(s"io/grpc/gateway/MyGatewayHandler.scala")
b.setContent(fp.result)
val file = b.build

Finally we need to turn the File into a CodeGeneratorResponse:

val b = CodeGeneratorResponse.newBuilder
b.addFile(file)
b.build

As the FunctionalPrinter accepts any String we can use this very same mechanism to generate any type of file. In fact we use it to generate the Swagger YAML specification corresponding to the GRPC definitions.

The HTTP annotations

In order to define the HTTP endpoint associated to a gRPC call, the gRPC method definitions must be annotated with an HTTP option extension:

import "google/api/annotations.proto";

rpc GetFeature(Point) returns (Feature) {
  // define associated REST endpoint on GRPC Gateway
  option (google.api.http) = {
    get: "/api/routeguide/feature"
  };
}

This example specifies that the GetFeature method is available on the path /api/routeguide/feature using the HTTP GET method.

This also introduces a dependency to the annotations.proto file. This file is provided by Google and must be available in the user project when scalaPB parses the .proto files.

In order to reduce the user burden this file is embedded in the runtime’s jar file so the user only has to add the following dependency to his build.sbt:

libraryDependencies += "beyondthelines" %% "grpcgatewayruntime" % "0.0.1" % "compile,protobuf"

Note that the protobuf is important as it tells ScalaPB to look into this jar for .proto files.

One pitfall worth noting is that in order to be able to use extensions the corresponding .proto files must be compiled in Java. In our case it means that the Google annotations .proto files must be compiled as Java sources. Fortunately it’s just a matter of configuring ScalaPB accordingly:

PB.targets in Compile += PB.gens.java -> (sourceManaged in Compile).value

JSON translation

The JSON translation is provided by the scalapb-json4s library which is able to translate any GeneratedMessage (case classes generated from protobuf definitions) into JSON.

unaryCall(req.method(), req.uri(), body)   // calls the gRPC service
  .map(JsonFormat.toJsonString)            // transform into JSON format
  .map(_.getBytes(StandardCharsets.UTF_8)) // transform into bytes

Similarly transforming an HTTP request body is straightforward:

printer.add(
  s"val input = Try(JsonFormat.fromJsonString[${method.getInputType.getName}](body))"
)

This snippet above is from the GRPC-Gateway generator. It generates the following code:

  val input = Try(JsonFormat.fromJsonString[T](body))

which turns a JSON string into an instance of type T.

The Swagger generator

The swagger generator is quite simple. It iterates over the services and methods definitions and generates corresponding Swagger specification for unary methods calls (no streaming supported) having an HTTP endpoint defined using the HTTP annotation.

It doesn’t generate a Scala file but a YAML file (.yml).

The GRPC-Gateway generator

The structure of the gateway generator follows a similar structure except that this time it does generate a Scala source file.

As the generated code is going to be compiled and included with the application code it doesn’t have to be limited to Scala 2.10 but can use the latest Scala version.

The code generated for the GRPC-Gateway is a simple Netty handler in charge of:

  • Building gRPC input parameters from requests body or query string
  • Performing corresponding gRPC call using the instances extracted above
  • Translating back the responses into JSON
  • Forwarding the translated responses to the HTTP Client

In fact only the extraction of the input parameters and the call to the appropriate gRPC method is generated.
The last 2 actions (translating the response to Json and sending the response back) are generic enough so they can be placed into a super class that is extended by the generated code.

printer
  .add(s"class ${service.getName}Handler(channel: ManagedChannel)(implicit ec: ExecutionContext)")
  .indent
  .add(
    "extends GrpcGatewayHandler(channel)(ec) {",
    s"""override val name: String = "${service.getName}"""",
    s"private val stub = ${service.getName}Grpc.stub(channel)"
  )
  .newline
  .call(generateUnaryCall(service))
  .outdent
  .add("}")
  .newline

The runtime library

The GRPC Gateway Handler

The runtime provides the Netty’s handler common functionality that is extended by the generated code. This is the GrpcGatewayHandler. Its role is to extract the HTTP request body and parameters, invoke the subclass method to trigger the call to the GRPC server, then translate the response into JSON and send it back to the HTTP client.

The Swagger Handler

The swagger handler is a simple Netty handler that relies Swagger-UI to serve the generated swagger specification.
It gets the requested files from the classpath (as both the generated swagger specs and the swagger-ui files are packaged inside jars) and serves them back to the client.

The swagger .yml files is available on the /specs path while the swagger files are available on the /docsfiles.

Therefore to open the swagger docs one must point its browser to the following url: http://localhost:8981/docs/index.html?url=/specs/RouteGuide.yml. (Assuming the GRPC gateway runs on localhost:8981 and that the spec file is named RouteGuide.yml).

The server utilities

Finally the runtime provides a utility class GrpcGatewayServerBuilder in order to start a gateway server easily.

This is what a user must write in order to start its own gateway:

// channel pointing to the GRPC server
val channel = ManagedChannelBuilder
  .forAddress("localhost", 8980)
  .usePlaintext(true)
  .build()

val gateway = GrpcGatewayServerBuilder
  .forPort(port)
  // add the generated handler
  .addService(new RouteGuideHandler(channel))
  .build()

gateway.start()

Conclusion

Here ends our tour of the GRPC gateway implementation. It is merely a Proof-of-concept but it should get you through the main pitfalls of working with protobuf code generation.

It still suffers a few limitations: no streaming is supported (might be worth looking at websocket) or try a different backend. In fact I’ll be glad to see gRPC running on top of Akka-Http now that it officially supports HTTP/2 it surely sounds possible.

And as always the source code is available on github for those who want to have a closer look or contribute.

If you only want to give it a try you may have a look at this example. Enjoy!

  • Jonas Chapuis

    Hey there! I just ran onto your article and your github projects. Cool to see you’re going through the same process as I do, I was also putting together some helpers to use Monix with grpc and looking into exposing services in REST. I was considering a somewhat different approach, introspecting the service at runtime – but that’s because I didn’t dig into scalapb code generation yet, your code looks great! May I ask in what context you are using this stack, is it still experimental or you have some production experience with it? Cheers!😀

    • Hi,
      These are all experimental projects although I’ve already used both ScalaPB (with code generation) and Monix in production but not combined together as exposed here.
      The only concern I have with grpcmonix is the use of unbounded buffers for the observables.
      For the gRPC gateway you may want to use the original implementation in Go as it supports more functionalities than this port to ScalaPB.
      Anyway if you go the ScalaPB way I’d be interested in any feedback you may have but so far I have found code generation an efficient way to add functionalities to protobuf code.

      • Jonas Chapuis

        Cool. I’m interested in running the REST gateway in scala since it makes for a more streamlined build and control – we are not a go shop.

        Regarding monix, for now I have just bridged the observers with adapters (https://gist.github.com/jchapuis/201889106ca214018ab1516f589aa7ad) and I had reported an issue precisely concerning unbounded buffers: https://github.com/monix/monix/issues/352, which has been fixed. Is that what you are referring to?

        • I actually used an observable with an unbounded overflow strategy but since reactive streams are now fixed I think I’ll replace this observable with one created from a reactive publisher.