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 Akka gRPC plugin to your sbt build along with the Play gRPC generators:
// in project/plugins.sbt:
addSbtPlugin("com.lightbend.akka.grpc" % "sbt-akka-grpc" % "2.1.5")
resolvers += Resolver.sonatypeRepo("snapshots")
libraryDependencies += "com.typesafe.play" %% "play-grpc-generators" % "0.11.x"
Then you need to enable the Play server side code generator in build.sbt
:
-
Scala
-
Java
enablePlugins(AkkaGrpcPlugin)
import play.grpc.gen.scaladsl.PlayScalaServerCodeGenerator
akkaGrpcExtraGenerators += PlayScalaServerCodeGenerator
libraryDependencies += "com.typesafe.play" %% "play-grpc-runtime" % "0.11.x"
enablePlugins(AkkaGrpcPlugin)
import play.grpc.gen.javadsl.PlayJavaServerCodeGenerator
akkaGrpcExtraGenerators += PlayJavaServerCodeGenerator
libraryDependencies += "com.typesafe.play" %% "play-grpc-runtime" % "0.11.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 akka.actor.ActorSystem
import example.myapp.helloworld.grpc.helloworld.AbstractGreeterServiceRouter
import example.myapp.helloworld.grpc.helloworld.HelloReply
import example.myapp.helloworld.grpc.helloworld.HelloRequest
/** 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 akka.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.