Serving gRPC from Play

To be able to serve gRPC from a Play Framework app you must enable HTTP/2 Support with HTTPS and the ALPN agent.

To use gRPC in Play Framework you must enable HTTP/2 Support.

Generating classes from the gRPC service definition is done by adding the Pekko gRPC plugin to your sbt build along with the Play gRPC generators:

// in project/plugins.sbt:
addSbtPlugin("org.apache.pekko" % "pekko-grpc-sbt-plugin" % "1.0.3")
resolvers += Resolver.sonatypeRepo("snapshots")
libraryDependencies += "org.playframework" %% "play-grpc-generators" % "0.12.x"

Then you need to enable the Play server side code generator in build.sbt:

  • Scala

  • Java

enablePlugins(PekkoGrpcPlugin)
import play.grpc.gen.scaladsl.PlayScalaServerCodeGenerator
pekkoGrpcExtraGenerators += PlayScalaServerCodeGenerator
libraryDependencies += "org.playframework" %% "play-grpc-runtime" % "0.12.x"
enablePlugins(PekkoGrpcPlugin)
import play.grpc.gen.javadsl.PlayJavaServerCodeGenerator
pekkoGrpcExtraGenerators += PlayJavaServerCodeGenerator
libraryDependencies += "org.playframework" %% "play-grpc-runtime" % "0.12.x"

The plugin will look for .proto service descriptors in app/protobuf and output an abstract class per service that you then implement, so for example for the following protobuf descriptor:

syntax = "proto3";

option java_multiple_files = true;
option java_package = "example.myapp.helloworld.grpc";
option java_outer_classname = "HelloWorldProto";

package helloworld;

service GreeterService {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

You will get an abstract class named example.myapp.helloworld.grpc.helloworld.AbstractGreeterServiceRouter. example.myapp.helloworld.grpc.AbstractGreeterServiceRouter. Create a concrete subclass implementing this wherever you see fit in your project, let’s say controller.GreeterServiceImpl like so:

  • Scala

  • Java

package controllers

import javax.inject.Inject
import javax.inject.Singleton

import scala.concurrent.Future

import example.myapp.helloworld.grpc.helloworld.AbstractGreeterServiceRouter
import example.myapp.helloworld.grpc.helloworld.HelloReply
import example.myapp.helloworld.grpc.helloworld.HelloRequest
import org.apache.pekko.actor.ActorSystem

/** User implementation, with support for dependency injection etc */
@Singleton
class GreeterServiceImpl @Inject() (implicit actorSystem: ActorSystem)
    extends AbstractGreeterServiceRouter(actorSystem) {

  override def sayHello(in: HelloRequest): Future[HelloReply] = Future.successful(HelloReply(s"Hello, ${in.name}!"))

}
package controllers;

import org.apache.pekko.actor.ActorSystem;
import com.google.inject.Inject;
import example.myapp.helloworld.grpc.AbstractGreeterServiceRouter;
import example.myapp.helloworld.grpc.HelloReply;
import example.myapp.helloworld.grpc.HelloRequest;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import javax.inject.Singleton;

/** User implementation, with support for dependency injection etc */
@Singleton
public class GreeterServiceImpl extends AbstractGreeterServiceRouter {

  @Inject
  public GreeterServiceImpl(ActorSystem actorSystem) {
    super(actorSystem);
  }

  @Override
  public CompletionStage<HelloReply> sayHello(HelloRequest in) {
    String message = String.format("Hello, %s!", in.getName());
    HelloReply reply = HelloReply.newBuilder().setMessage(message).build();
    return CompletableFuture.completedFuture(reply);
  }
}

And then add the router to your Play conf/routes file. Note that the router already knows its own path since it is based on the package name and service name of the service and therefore the path / is enough to get it to end up in the right place (in this example the path will be /helloworld.GreeterService). It cannot be added at an arbitrary path (if you try to do so an exception will be thrown when the router is started).

->     /   controllers.GreeterServiceImpl

A gRPC client can now connect to the server and call the provided services.