はじめに
前の記事で紹介したgRPCのAPIタイプの内、1リクエストに対して1レスポンスを返すUnaryを実装してみました。
実装
細かい手順に分けて実装していきます。
準備
GoでgRPCやProtocol Bufferを実装するにあたり、以下のライブラリを導入します。
$ go get -u google.golang.org/grpc
$ go get -u github.com/golang/protobuf/protoc-gen-go
また、シェルにパスを通します(今回は.bash_profile
)。
export GO_PATH=~/go
export PATH=$PATH:/$GO_PATH/bin
スキーマ作成(.protoファイルの作成)
greet.proto
というファイルを作成し、スキーマを定義します。
送信するメッセージの型をGreeting
で定義し、Unaryによるリクエストとレスポンスの型をGreetRequest
とGreetResponse
で定義します。
また、APIのタイプ(どのようなリクエストとレスポンスの形式か)をサービスGreetService
で定義します。
syntax = "proto3";
package greet;
option go_package="./greet/greetpb";
message Greeting {
string first_name = 1; // タグ番号
string last_name = 2;
}
message GreetRequest {
Greeting greeting = 1;
}
message GreetResponse {
string result = 1;
}
service GreetService{
// Unary
rpc Greet(GreetRequest) returns (GreetResponse) {};
}
コード生成(.pb.goファイルの作成)
以下のコマンドにより、作成したスキーマからコード(Protocol Buffer)を生成します。
protoc greet/greetpb/greet.proto --go_out=plugins=grpc:.
するとスキーマで定義したGreeting
の構造体の型やそのレシーバ(メソッド)などがGoのコードとして生成されます(以下が生成コードの一部)。
これらのメソッドをクライアントとサーバで使用することで、gRPCによる通信を実現することができます。
type Greeting struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
FirstName string `protobuf:"bytes,1,opt,name=first_name,json=firstName,proto3" json:"first_name,omitempty"`
LastName string `protobuf:"bytes,2,opt,name=last_name,json=lastName,proto3" json:"last_name,omitempty"`
}
func (x *Greeting) GetFirstName() string {
if x != nil {
return x.FirstName
}
return ""
}
func (x *Greeting) GetLastName() string {
if x != nil {
return x.LastName
}
return ""
}
サーバ実装
生成したAPIをサーバに実装(フック)します。
server構造体を用意して、Protocol Buffer(greet.pb.go
)で定義されたGreetServiceServer
と同じ型でメソッド(レシーバ)を埋め込みます。
レシーバGreet()
では、リクエストで取得したfirstName
をHello [firstName]
というレスポンスで返すような処理を行います。
Greet()
内のGetGreeting()
などもProtocol Bufferで定義されたものを用います。
package main
import (
"context"
"fmt"
"log"
"net"
"github.com/suzuki0430/grpc-project/greet/greetpb"
"google.golang.org/grpc"
)
type server struct{}
func (*server) Greet(ctx context.Context, req *greetpb.GreetRequest) (*greetpb.GreetResponse, error) {
fmt.Printf("Greet function was invoked with %v\n", req)
firstName := req.GetGreeting().GetFirstName()
result := "Hello " + firstName
res := &greetpb.GreetResponse{
Result: result,
}
return res, nil
}
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)
}
}
サーバをlistenする処理はmain()
内に記述します。
サーバの立ち上げには以下のコマンドを利用します。
go run greet/greet_server/server.go
クライアント実装
Protocol Buffer(greet.pb.go
)に定義されたメソッドを用いてリクエストの作成・送信とレスポンスの取得を行う処理をdoUnary()
にまとめます。
server.go
に記述したメソッドGreet()
をクライアント側で呼び出しているように、この部分でRPC(クライアントでサーバのコードを実行する)を行っています。
package main
import (
"context"
"fmt"
"io"
"log"
"time"
"google.golang.org/grpc/codes"
"github.com/suzuki0430/grpc-project/greet/greetpb"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
)
func main() {
fmt.Println("Hello I'm a client")
cc, err := grpc.Dial("localhost:50051", grpc.WithInsecure)
if err != nil {
log.Fatalf("could not connect: %v", err)
}
defer cc.Close()
c := greetpb.NewGreetServiceClient(cc)
doUnary(c)
}
func doUnary(c greetpb.GreetServiceClient) {
fmt.Println("Starting to do a Unary RPC...")
req := &greetpb.GreetRequest{
Greeting: &greetpb.Greeting{
FirstName: "Katsumi",
LastName: "Yamada",
},
}
res, err := c.Greet(context.Background(), req)
if err != nil {
log.Fatalf("error while calling Greet RPC: %v", err)
}
log.Printf("Response from Greet: %v", res.Result)
}