logo

Lucas Katayama/Golang gRPC

Created Sun, 26 Jul 2020 12:51:27 -0300 Modified Sun, 26 Jul 2020 12:51:27 -0300
685 Words

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.

References