gRPC
gRPC is a modern open source high performance RPC framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.
Pre-requisites
Protobuf
To install protobuf
and its compiler, on macOS:
$ brew install protobuf
Golang plugin
Now install golang
plugin:
$ go get github.com/golang/protobuf/protoc-gen-go
$ cd $(go env GOPATH)/src/github.com/golang/protobuf/protoc-gen-go
$ go install .
gRPC plugin
$ go get google.golang.org/grpc
$ cd $(go env GOPATH)/src/google.golang.org/grpc/cmd/protoc-gen-go-grpc
$ go install .
Example Project
Let’s create an example project called hello-world
:
At your user workspace $(go env GOPATH)/src/github/<username>
$ mkdir hello-world && cd hello-world
$ go mod init
Create a folder structure like this:
├── client
│ └── main.go
├── go.mod
├── hello
│ └── hello.proto
└── server
└── main.go
Protobuf Definitions
Create a hello.proto
inside hello
package with this content:
syntax = "proto3";
package hello;
option go_package = "github.com/lucaskatayama/hello-world/hello";
message Name {
string name = 1;
}
message Message {
string body = 1;
}
service HelloService {
rpc SayHello(Name) returns (Message);
}
Generate client code
Now, generate the code for client:
$ protoc --proto_path=./ \
--go_opt=paths=source_relative --go_out=./ \
./hello/hello.proto
This will generate a hello.pb.go
inside <project>/hello
folder.
Generate gRPC server code
Now, generate the code for client:
$ protoc --proto_path=./ \
--go-grpc_opt=paths=source_relative --go-grpc_out=./ \
./hello/hello.proto
This will generate a hello_grpc.pb.go
inside <project>/hello
folder.
Server
Modules
Install modules:
$ go get google.golang.org/grpc
Server code
Let’s implement the server.
Edit the <project>/server/main.go
file:
package main
import (
"log"
"net"
"github.com/<username>/hello-world/hello"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
func main() {
srv, err := net.Listen("tcp", "localhost:9000")
if err != nil {
log.Fatal(err)
}
grpcSrv := grpc.NewServer()
reflection.Register(grpcSrv)
s := hello.UnimplementedHelloServiceServer{}
hello.RegisterHelloServiceServer(grpcSrv, &s)
if err := grpcSrv.Serve(srv); err != nil {
log.Fatal(err)
}
}
The reflection.Register(grpcSrv)
line, enables reflection to be used with a tool (evans
) later.
This code will create a gRPC server with a stub for HelloService
, see this line:
s := hello.UnimplementedHelloServiceServer{}
To implement an actual service, create a package hello inside server folder, with its implementation:
// <project>/server/hello/hello.go
package hello
import (
"context"
"fmt"
"github.com/lucaskatayama/hello-world/hello"
)
type Server struct {
hello.UnimplementedHelloServiceServer
}
func (s Server) SayHello(ctx context.Context, name *hello.Name) (*hello.Message, error) {
return &hello.Message{
Body: fmt.Sprintf("Hello %s!", name.Name),
}, nil
}
See, you have implemented the SayHello
method for the SayHelloService
, instead of using a stub.
Now modify the server.go
to use your implementation:
package main
import (
"log"
"net"
"github.com/lucaskatayama/hello-world/hello"
my_hello "github.com/lucaskatayama/hello-world/server/hello"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
func main() {
srv, err := net.Listen("tcp", "localhost:9000")
if err != nil {
log.Fatal(err)
}
grpcSrv := grpc.NewServer()
reflection.Register(grpcSrv)
s := my_hello.Server{}
hello.RegisterHelloServiceServer(grpcSrv, &s)
if err := grpcSrv.Serve(srv); err != nil {
log.Fatal(err)
}
}
Run the server:
$ go run ./server
Evans
To test the server, let’s install evans
, which is a cli tool to communicate with gRPC servers:
$ go get github.com/ktr0731/evans
Run it on localhost:9000
gRPC server:
$ evans -r repl --host localhost --port 9000
Call service
First see if the service is there with show service
hello.HelloService@localhost:9000> show service
+--------------+----------+--------------+---------------+
| SERVICE | RPC | REQUEST TYPE | RESPONSE TYPE |
+--------------+----------+--------------+---------------+
| HelloService | SayHello | Name | Message |
+--------------+----------+--------------+---------------+
Now you can call it:
hello.HelloService@localhost:9000> call SayHello
name (TYPE_STRING) => John Doe
{
"body": "Hello John Doe!"
}
Client
Let’s implement a client with golang
.
Edit the file <project>/client/main.go
:
package main
import (
"context"
"log"
"github.com/lucaskatayama/hello-world/hello"
"google.golang.org/grpc"
)
func main() {
var conn *grpc.ClientConn
conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %s", err)
}
defer conn.Close()
c := hello.NewHelloServiceClient(conn)
name := hello.Name{
Name: "John Doe",
}
response, err := c.SayHello(context.Background(), &name)
if err != nil {
log.Fatalf("Error when calling SayHello: %s", err)
}
log.Printf("Response from server: %s", response.Body)
}
Now run it:
$ go run ./client
You will get:
$ go run ./client
2020/07/26 14:01:36 Response from server: Hello John Doe!
That’s it!
With this post, you have a gRPC server and two clients, one CLI and one coded in golang
.