In a previous blog series I’ve talked about how to work with a gRPC backend from the GopherJS world. It relies on the gRPC-gateway which is a great piece of tech, but unfortunately carries a couple of downsides:
- Clients don’t know what types are used - the interface is HTTP JSON. This can be somewhat mitigated with the use of swagger generated interfaces, but it’s still not perfect.
- The interface being JSON means marshalling and unmarshalling can become a significant part of the latency between the client and the server.
- The gRPC-gateway requires specific changes to the proto definitions - it’s not as straightforward as just defining your RPC methods.
Fortunately, with the release of a spec compliant gRPC-Web implementation from Improbable, we can finally start enjoying the benefits of a protobuf typed interface in the frontend. This deals with all the mentioned downsides of the gRPC-gateway;
- Interfaces are typed via protobuffers.
- Messages are serialized to binary.
- There’s no difference between exposing the gRPC server to the web client than any other client.
The Improbable gRPC-Web README also has a long list of benefits.
gRPC-Web in GopherJS
In the last couple of weeks I’ve been working on a GopherJS wrapper for gRPC-Web,
and I’m pleased to say that it’s ready for others to play around with. The wrapper is comprised of
the grpcweb
GopherJS library and the
protoc-gen-gopherjs
protoc plugin.
Together, they make it possible to generate a GopherJS client
interface from your proto file definitions. The
protoc-gen-gopherjs
README contains a thorough guide
into how to generate the client interfaces.
To give an idea of the usage, I’ve put together an example using the GopherJS React bindings created by Paul Jolly (@_myitcv). If you want to skip ahead, the source is available on my github.
I’m going to assume that if you’re reading this post you’re already familiar with how to implement the Go backend part of this, so we’ll jump right into the client. The only difference in the backend from a normal Go gRPC server is the use of the Improbable gRPC-Web proxy wrapper. This is necessary as a translation layer from the gRPC-Web requests to fully compliant gRPC requests. There also exists a general-purpose proxy server, which can be used with gRPC servers in other languages.
The Client
The interface is generated using protoc-gen-gopherjs
.
The source protofile can be found
in the repo.
With the generated file we get access to the gRPC-Web
methods GetBook
and QueryBooks
.
First off we need to create a new client:
client := library.NewBookServiceClient(baseURI)
The parameter is the address of the gRPC server, in this case the same address as we’re hosting the JS from, but it could be located on some external address. Note that gRPC-Web over HTTP2 requires TLS.
A simple request
Once we have a client, we can make calls on it just like on
a normal Go gRPC client. The generated interfaces are
designed to be as similar as possible to protoc-gen-go
client interfaces.
All RPC methods are blocking by default, though there are
plans to expose an asynchronous API later on, if there
is demand for it.
Lets get the book with an ISBN
of 140008381
:
req := &library.GetBookRequest{
Isbn: 140008381,
}
book, err := client.GetBook(context.Background(), req)
if err != nil {
panic(status.FromErr(err))
}
println(book)
The context parameter can be used to control timeout, deadline and cancellation of requests. The second parameter is the request to the method. Looks just like the normal Go client API.
Server side streaming
It wouldn’t be gRPC without streaming. Unfortunately, gRPC-Web does not currently support client-side streaming. We do have access to server side streaming though. This is a simple example of how to consume message from a streaming server side method:
req := &library.QueryBooksRequest{
AuthorPrefix: "George",
}
srv, err := client.QueryBooks(context.Background(), req)
if err != nil {
panic(status.FromErr(err))
}
for {
// Blocks until new book is received
bk, err := srv.Recv()
if err != nil {
if err == io.EOF {
// Success! End of stream.
return
}
panic(status.FromErr(err))
}
println(bk)
}
Much like the Go client API, we get a streaming server
which we call Recv
on until we see an error. If the
error is io.EOF
, it means the server has closed the stream
successfully.
Wrapping up
With the release of an unofficial gRPC-Web client by Improbable, the frontend can finally start getting some of the benefits the backend has enjoyed for a couple of years now, courtesy of gRPC and Protobuffers. I’m personally extremely excited by the opportunities it affords frontend developers working with a simple frontend layer talking to a backend service. Take a look at the github repo for a complete example.
If you enjoyed this blog post, have any questions or input,
don’t hesitate to contact me on
@johanbrandhorst or
under jbrandhorst
on the Gophers Slack. I’d love to hear
your thoughts!