Golangで始めるgRPC

# 概要

gRPCはあらゆる環境で実行できるモダンで高性能なオープンソースRPC(Remoto Protocol Call)フレームワークです。Protocol Buffersを使ってデータをシリアライズし、高速な通信を実現できるという利点がある。様々な言語やプラットフォームに対応しており、http/2を活かした双方向ストリーミングなどが可能である。Protocol Buffersを使用してシンプルにサービス(通信するデータや関数)を定義でき、APIの仕様を明文化できる。

※この章は[gRPC](https://grpc.io/)を参考にしています。

git: [k-washi/example-golang-gRPC](https://github.com/k-washi/example-golang-gRPC)


# スタートプロジェクト

## VSCodeの設定

+ vscode-proto3 & Clang-Format extensions をインストール

もし、自動でファイルをフォーマットしたいならば、以下も設定する。

+ Clang-Formatをインストール

```bash
brew install clang-format #MacOSX

#other OS  
#http://www.codepool.biz/vscode-format-c-code-windows-linux.html
```

## Protocol buffersの設定

Protocol buffersのインストール


```bash
# MacOSX
brew install protobuf #MacOSX
```

```bash
# Linux
#https://github.com/google/protobuf/releases

#example
# Make sure you grab the latest version
curl -OL https://github.com/google/protobuf/releases/download/v3.5.1/protoc-3.5.1-linux-x86_64.zip
# Unzip
unzip protoc-3.5.1-linux-x86_64.zip -d protoc3
# Move protoc to /usr/local/bin/
sudo mv protoc3/bin/* /usr/local/bin/
# Move protoc3/include to /usr/local/include/
sudo mv protoc3/include/* /usr/local/include/
# Optional: change owner
sudo chown [user] /usr/local/bin/protoc
sudo chown -R [user] /usr/local/include/google
```

## Golangの設定

パッケージののインストール

```bash
go get -u google.golang.org/grpc
go get -d -u github.com/golang/protobuf/protoc-gen-go
```

## [実行の確認](https://github.com/k-washi/example-golang-gRPC/tree/master/greet)

まず、サービスを定義する。

```proto:greet.proto
syntax = "proto3";

package greet;
option go_package="greetpb";

service GreetService{}
```

この定義したファイルに対して、以下のコマンドでgolang用のprotocol buffers(greet.pb.go)に変換する。この変換されたライブラリファイル(greet.pb.go)内の関数を、golangによるクライアントとサーバーで使用することで、gRPCによる通信を実現できる。

```bash
protoc greet/greetpb/greet.proto  --go_out=plugins=grpc:.
```

クライアントは、以下のように接続の確立を行い、クライアントAPI(NewGreetServiceClient)で通信している。

```golang:client.go
package main

import (
	"fmt"
	"log"
  //protocol buffersライブラリー
	"github.com/k-washi/example-golang-gRPC/greet/greetpb"
	"google.golang.org/grpc"
)

func main() {
	fmt.Println("Hello. I'm a client")
	conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("Could not connect: %v", err)
	}
	defer conn.Close()
	c := greetpb.NewGreetServiceClient(conn)
	fmt.Printf("Created client %f", c)
}

```

サーバーは、構造体serverを定義し、サービスをサーバーに登録する。そして、サーバーを立ち上げる。

```golang:server.go
package main

import (
	"fmt"
	"log"
	"net"
	"github.com/k-washi/example-golang-gRPC/greet/greetpb"
	"google.golang.org/grpc"
)

type server struct{}

func main() {
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
	}

	s := grpc.NewServer()
	greetpb.RegisterGreetServiceServer(s, &server{})

	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}
```

以下のコマンドで、サーバーを立ち上げ、クライアントを実行することで、実行の確認を行う。

```bash

#server setup
go run greet/greet_server/server.go 

#client setup
go run greet/greet_client/client.go
```

# 通信手法

gRPCは、以下のような通信手法があり、データの特性に応じて適宜選択する必要がある。

+ Unary - もっとも一般的なgRPCのAPIで、クライアントからのメッセージ1つに対して、サーバーは1回のメッセージを返す
+ Server streaming - HTTP/2の恩恵を受けたAPIで、クライアントからのメッセージ1つに対して、サーバーは複数回のメッセージを返す
+ Client streaming - HTTP/2の恩恵を受けたAPIで、クライアントからの複数のメッセージに対して、サーバーは1回のメッセージを返す

## サービスの定義

1. 送信するメッセージの型を定義する
2. Unary, Server streaming, Client streamingのリクエスト・レスポンスにおけるメッセージの型を定義する
3. サービスを rpc サービス名(stream クライアントの型) returns (stream レスポンスの型) {}; として定義する。 複数回送るサービスのメッセージにはstreamをつける。
4. 以下のコマンドでprotocol buffersに変換.

```bash
protoc streaming/greetpb/greet.proto  --go_out=plugins=grpc:.
```

```proto
syntax = "proto3";

package greet;
option go_package="greetpb";

//リクエストメッセージの型を定義
message Greeting {
  string first_name = 1;
  string last_name = 2;
}

// Unary リクエスト
message GreetRequest {
  Greeting greeting = 1;
}

// Unary レスポンス
message GreetResponse {
  string result = 1;
}

// Server streaming リクエスト
message GreetManyTimesRequest {
  Greeting greeting = 1;
}

// Server streaming レスポンス
message GreetManyTimesResponse {
  string result = 1;
}

// Client streaming リクエスト
message LongGreetRequest {
  Greeting greeting = 1;
}

// Client streaming レスポンス
message LongGreetResponse {
  string result = 1;
}

// サービスの定義
service GreetService{
  //Unary
  rpc Greet(GreetRequest) returns (GreetResponse) {};

  //Server Streaming
  rpc GreetManyTimes(GreetManyTimesRequest) returns (stream GreetManyTimesResponse) {};

  //Client Streaming
  rpc LongGreet(stream LongGreetRequest) returns (LongGreetResponse) {};
}
```

## クライアント

Golangにより、Unary, Server streaming, Client streamingのクライアントを実装する。基本的には、サービス定義に合わせた型でデータを作成、リクエストを送信、そして、レスポンスを待つ形式である。これらの処理には、protocol buffersライブラリに記載されたパッケージを使用する。サーバーからの複数回のメッセージを待つ場合、繰り返し構文(for)を用いて受信することで、適用できる。複数回サーバーにメッセージを送信する場合も、繰り返し構文を用いているが、基本的には、メッセージごとに送信しているだけである。gRPCに関するエラーは、"google.golang.org/grpc/status"ライブラリを用いて抽出・定義できる。

```golang:client.go

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"time"

	"google.golang.org/grpc/codes"

	"github.com/k-washi/example-golang-gRPC/streaming/greetpb"
	"google.golang.org/grpc"
	"google.golang.org/grpc/status"
)

func main() {
	fmt.Println("Hello. I'm a client")
	conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("Could not connect: %v", err)
	}
	defer conn.Close()
	c := greetpb.NewGreetServiceClient(conn)

	doUnary(c)
	doServerStreaming(c)
	doClientStreaming(c)

}

func doUnary(c greetpb.GreetServiceClient) {
	fmt.Println("Starting to do a Unary RPC ...")
	req := &greetpb.GreetRequest{
		Greeting: &greetpb.Greeting{
			FirstName: "tanaka",
			LastName:  "tarou",
		},
	}

	res, err := c.Greet(context.Background(), req)

	if err != nil {
		respErr, ok := status.FromError(err)
		if ok {
			fmt.Println(respErr.Message())
			fmt.Println(respErr.Code())
			if respErr.Code() == codes.InvalidArgument {
				fmt.Println("We probably sent a empty string")

			} else if respErr.Code() == codes.DeadlineExceeded {
				fmt.Println("Timeout was hit! Deadline was exceeded")
			}
		} else {
			log.Printf("error while calling greet rpc: %v", err)
		}
	}
	fmt.Printf("Response from greet server %v", res)
}

func doServerStreaming(c greetpb.GreetServiceClient) {
	fmt.Println("Starting to do a Server Streaming RPC ...")
	req := &greetpb.GreetManyTimesRequest{
		Greeting: &greetpb.Greeting{
			FirstName: "tanaka",
			LastName:  "tarou",
		},
	}
	resStream, err := c.GreetManyTimes(context.Background(), req)
	if err != nil {
		log.Fatalf("error while calling GreetManyTImes RPC: %v", err)
	}
	for {
		msg, err := resStream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatalf("error while reading stream: %v", err)
		}
		log.Printf("Response from GreetManyTImes: %v", msg.GetResult())
	}

}

func doClientStreaming(c greetpb.GreetServiceClient) {
	fmt.Println("Starting to do a Client Streaming RPC")

	requests := []*greetpb.LongGreetRequest{
		&greetpb.LongGreetRequest{
			Greeting: &greetpb.Greeting{
				FirstName: "one",
			},
		},
		&greetpb.LongGreetRequest{
			Greeting: &greetpb.Greeting{
				FirstName: "two",
			},
		},
		&greetpb.LongGreetRequest{
			Greeting: &greetpb.Greeting{
				FirstName: "three",
			},
		},
		&greetpb.LongGreetRequest{
			Greeting: &greetpb.Greeting{
				FirstName: "four",
			},
		},
	}

	stream, err := c.LongGreet(context.Background())
	if err != nil {
		log.Fatalf("error while calling LongGreet %v", err)
	}
	for _, req := range requests {
		fmt.Printf("Sending req: %v\n", req)
		stream.Send(req)
		time.Sleep(1000 * time.Millisecond)
	}

	res, err := stream.CloseAndRecv()
	if err != nil {
		log.Fatalf("error while receiving response from LongGreet: %v", err)
	}
	fmt.Printf("LongGreet Response: %v", res)

}

```

## サーバー

server構造体に、protocol buffersに定義されたGreetServiceServerと同じ形式で関数を埋め込む。
メッセージの送受信もprotocol buffersに定義されたものを用いている。また、リクエストの取得にも、メッセージに対するGet関数がprotocol bufferに用意されている。

```golang:server.go
package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"net"
	"strconv"
	"time"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/k-washi/example-golang-gRPC/streaming/greetpb"

	"google.golang.org/grpc"
)

//server greet.pb.go GreetServiceClient
type server struct{}

func (*server) Greet(ctx context.Context, req *greetpb.GreetRequest) (*greetpb.GreetResponse, error) {
	fmt.Printf("Greet func was invoked with %v", req)
	firstName := req.GetGreeting().GetFirstName()
	if firstName == "" {
		return nil, status.Errorf(
			codes.InvalidArgument,
			"Recived a empty string",
		)
	}

	if ctx.Err() == context.Canceled {
		return nil, status.Error(codes.Canceled, "the client canceld the request")
	}

	result := "Hello " + firstName
	//client config deadline
	/*
		res := &greetpb.GreetWithDeadlineResponse{
			Result: result,
		}
	*/
	res := &greetpb.GreetResponse{
		Result: result,
	}
	return res, nil
}

func (*server) GreetManyTimes(req *greetpb.GreetManyTimesRequest, stream greetpb.GreetService_GreetManyTimesServer) error {
	fmt.Printf("GreetManyTimes func was invoked with %v", req)
	firstName := req.GetGreeting().GetFirstName()
	for i := 0; i < 10; i++ {
		result := "Hello" + firstName + " number " + strconv.Itoa(i)
		res := &greetpb.GreetManyTimesResponse{
			Result: result,
		}
		stream.Send(res)
		time.Sleep(1000 * time.Millisecond)
	}
	return nil
}

func (*server) LongGreet(stream greetpb.GreetService_LongGreetServer) error {
	fmt.Println("LongGreet function was invoked with a streaming request")
	result := ""
	for {
		req, err := stream.Recv()
		if err == io.EOF {
			//we have finished reading the client stream
			return stream.SendAndClose(&greetpb.LongGreetResponse{
				Result: result,
			})
		}
		if err != nil {
			log.Fatalf("Error while reading client stream: %v", err)
		}
		firstName := req.GetGreeting().GetFirstName()
		result += "Hello " + firstName + "! "

	}
}

func main() {
	fmt.Println("Hello World!")

	lis, err := net.Listen("tcp", "0.0.0.0:50051")
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
	}

	s := grpc.NewServer()
	greetpb.RegisterGreetServiceServer(s, &server{})

	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}
```

# 参考文献

+ [gRPCって何？](https://qiita.com/oohira/items/63b5ccb2bf1a913659d6)
+ [Goで始めるgRPC入門](https://qiita.com/marnie_ms4/items/4582a1a0db363fe246f3)
+ [今から学ぶgRPCの基礎](https://qiita.com/yuuyamad/items/bbbd1782150d7958ff11)