gRPC Go: Beyond the basics
As a newcomer to gRPC (in Go) there are many resources setting out what gRPC is, where it originated from and how to create a basic service and client. After completing an introduction to gRPC and setting up a basic implementation I felt a bit lost as to where I need to go next.
gRPC consists of more than just sending binary blobs over HTTP/2. gRPC is also a set of libraries that will provide higher-level features consistently across platforms that other libraries typically do not. The purpose of this blog is to be a guideline for where to find the resources and leverage these libraries and features to make the most of the gRPC ecosystem after you understand the basics of gRPC.
Before we begin, let’s refresh our memory and do a quick recap of what a gRPC service and client look like and how it’s defined in a protobuf definition file (.proto).
We will create a service to query Go releases with 2 methods,
Compiling this with the
protoc tool with the grpc plugin will create generated Go code to marshal/unmarshal the messages (i.e
GetReleaseInfoRequest) between Go code and the protocol buffer binary messages. The gRPC plugin will also generate code to register and implement service interface handlers as well as code to create a gRPC client to connect to the service and send messages.
Let’s look at a basic service and client implementation.
Go gRPC API
A client connection can be configured by supplying
DialOption functional option values to the
grpc.Dial function and server configuration is done by supplying
ServerOption functional option values to the
The API also has a concept called interceptors which basically makes it possible to add middleware functionality to both Unary (single request/response) and Streaming calls.
Interceptors are very useful to wrap functionality around a RPC call. It helps to separate things like logging/auth/monitoring/tracing from the logic of the RPC service and can help to create a uniform way (for example : logging) for each call in one place.
Other functionality that the API offer are things like the handling of messages with a different codec than Protocol Buffers (i.e FlatBuffers), enabling compression of message, control maximum message sizes, add headers and trailers, enabling tracing and even creating load balancing functionality (the Load Balancing API is still experimental)
Find the full documentation of the API here.
To showcase the use of the API let’s look at some use cases and build on top of our basic example above.
Securing our service
If we look at the
grpc.NewServer function definition (
func NewServer(opt ...ServerOption) *Server) we will see that it is a variadic function that accepts a variable number of
To enable TLS for our service we need to use the
grpc.Creds function which returns a
grpc.ServerOption to send to the
Let’s look at the example.
The code to create a
tls.Config is standard Go. The real lines of code that’s of interest are the following:
credentials.NewTLS() function construct a
credentials.TransportCredentials value based on the
grpc.Creds() funtional option takes the
credentials.TransportCredentials value and sets credentials for server connections.
Now the gRPC server will accept TLS connections.
Let’s turn our focus to enabling TLS on the client side.
In the basic example we supply a
grpc.WithInsecure() value to the
grpc.Dial function. The
grpc.Insecure() function returns a
DialOption value which disables transport security for the client connection. By default, transport security is required so to disable transport security we need to set
But we want to enable TLS transport security. This is done with the
grpc.WithTransportCredentials() function. Just like the
grpc.Creds() function we used to enable transport security on the server side, the
grpc.WithTransportCredentials() function also accepts a
credentials.TransportCredentials but the difference is it returns a
DialOption value and not a
ServerOption value, and
grpc.Dial function accepts
Now our service are enabled with TLS to encrypt data sent over the wire.
There are many other options like message and buffer sizes and specifying a custom codec (something other than Protocol Buffer) available.To see what other options are available the API docs are your friend.
For more server side options, see ServerOption.
For more client side options, see DialOption.
DialOption values all have a With prefix, i.e grpc.WithTransportCredentials
For more resources on enabling TLS for gRPC in Go:
Like I said previously, the gRPC API has a concept called interceptors which enables us to write middleware functionality to our calls. To illustrate the use of interceptors we will write interceptors to add logging and basic authentication to our calls.
Interceptors can be created for both client and servers, and both support interceptors for Unary RPC calls as well as Streaming calls.
To create an interceptor you would need to create a function with a definition that matches the relevant type of interceptor you want to create.
For example, if you want to create an Unary interceptor for your server, then based on the definitions below we would need to create a function with the same definition as
UnaryServerInterceptor and supply that function to
grpc.UnaryInterceptor() to create a
ServerOption value that will be used to set the option for the server.
The API documents every parameter but I want to quickly focus on how metadata is handled by interceptors.
Metadata can be accessed by each interceptor via the
context.Context value (for Unary calls) and
ServerStream value (for Streaming calls). This is useful if we need to access the metadata (i.e authorization details) set in the call to authorize a call for example.
Enough with the theory, let’s implement our logging and authorization middleware.
This function will be called with each incoming request before the actual service method is called. We can add general logging and use the different parameter values that get passed in to make decisions of our own like using the
grpc.UnaryServerInfo value to exclude authorization checks for certain requests or use the
context.Context value to extract metadata to check authorization like this:
To enable the interceptor on the server we supply a
ServerOption that will set the server’s unary inceptor to our function called
unaryInterceptor using the
On the client side we will need to send the authorization details with the call. To do this we supply a
DialOption to the
grpc.Dial function using the
grpc.WithPerRPCCredentials() functional option which expects a
Below we have a struct type called
basicAuthCreds which satisfy the
We then create a value for
basicAuthCreds and then supply it to the
grpc.WithPerRPCCredentials() functional option :
When the call happens the gRPC client will now generate the basic auth credentials and add it to the calls’ metadata.
We have reached the end of our overview of the Go gRPC API and shown what is possible.
In summary, we made our basic server more secure and added middleware without having to change code in our basic service. What’s also nice is we can add more methods to our service which will automatically “inherit” the security and middleware functionality already created, we can just focus on the business logic required.
There are other behavior that can be changed via the API but will not go into detail now:
- Set own logger implementation for underlying logger
- Enable tracing to trace RPCs using the golang.org/x/net/trace package (
grpc.EnableTracing = true)
- Set own backoff configuration.
I encourage the reader to spend some time going over the API as well as it’s subdirectories.
Hopefully this is enough information to leverage the API and create your own specific implementation.
Go gRPC ecosystem
Let’s move towards looking at what is available in the wider gRPC ecosystem that serves as an extention to the official API.
If you recall in our example above all interceptor functionality (logging and auth) were contained in one interceptor. The API only allow one unary interceptor handler and one streaming interceptor handler for both client and servers.
This is where the go-grpc-middleware package come in very handy as it supplies functionality to chain interceptors into one interceptor:
The gRPC Middleware package also have great ready-to-use interceptors for auth, logging (logrus, zap), monitoring (Prometheus ), tracing (OpenTracing), retries, server side validation etc.
For more info:
gRPC and the web
gRPC was mainly developed for services talking RPC with each other internally in a system. It also has great support for mobile clients talking to gRPC services but how does gRPC fit into existing web technologies?
gRPC Gateway is a great project if you already have gRPC services but your API need to be exposed as a traditional RESTful API.
It includes a plugin to the
protoc tool which generates a reverse-proxy server which translates a RESTful JSON API into gRPC.
It also generates Swagger/API documentation.
For more info:
gRPC WebSocket Proxy is a proxy to transparently upgrade grpc-gateway streaming endpoints to use websockets. It enables bidirectional streaming of JSON payloads on top of grpc-gateway.
For more info:
There’s an official specification for gRPC-Web, but there’s no official implementation, however Improbable created their own implementation based on the specification and is used in production at the moment. They also open sourced their solution which includes a client side implementation in Typescipt,
protoc plugin and server side proxy in Go.
The blog on Day1 of the 2017 advent series have an excellent article on gRPC-Web and a great example to create a client in GopherJS.
For more info:
- Improbable blog about gRPC-Web
- “Official” gRPC-Web repo (Private repo)
- Caddy gRPC plugin that also supports gRPC-Web proxying.
- Vue.js example using gRPC-Web
- Starter kit for Angular 2 projects using gRPC-Web
Thank you for reading this blog. Hopefully this blog helped you into diving deeper into the wonderful world of gRPC in Go. Although gRPC is considered a framework, the API gives us a flexible API to leverage and control behavior and to make our services robust and production ready.
If you have any feedback, remarks or questions you can send me a tweet @pieterlouw
The source code for the example can be found here.