golangでgRPCを使ったプロトタイプを作る手順です。
を記事を多いに参考にしてます。
この記事のコードは上記の記事と同一です。どこにコードを書くかと、protoc
コマンドについて多少明確になっています。
Goのバージョン管理にはgvm
を使っています。
Goのバージョンは go version go1.16.5 darwin/amd64
です。
protoc
コマンドを使うので以下でinstallしてください。
gRPCの何がいいかについては
プロジェクトの用意
適当なプロジェクトをこしらえます。
go mod init example.com/cat
必要なパッケージを入れます。
go get -u google.golang.org/grpc
go get -u github.com/golang/protobuf/protoc-gen-go
こんな感じのgo.mod
ができます。
module example.com/cat
go 1.16
require (
github.com/golang/protobuf v1.5.2 // indirect
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
golang.org/x/sys v0.0.0-20210816032535-30e4713e60e3 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c // indirect
google.golang.org/grpc v1.40.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
)
ディレクトリを作る
以下のようにフォルダとファイルを作成します。
client/main.go
proto/cat.proto
server/main.go
service/main.go
このうちまずproto/cat.proto
を以下のように書きます。
syntax = "proto3";
option go_package = "./;pb";
package cat;
service Cat {
rpc GetMyCat (GetMyCatMessage) returns (MyCatResponse) {}
}
message GetMyCatMessage {
string target_cat = 1;
}
message MyCatResponse {
string name = 1;
string kind = 2;
}
option go_package
ですが、packageのimport pathをxx.proto
ファイルに与える必要があって、protoc
コマンド実行字か、.proto
に記述するのがするかできます。推奨としてはxx.proto
ファイルに書くのがいいようです。
https://developers.google.com/protocol-buffers/docs/reference/go-generated#package
関連
- https://github.com/techschool/pcbook-go/issues/3#issuecomment-824040393
- https://blog.ebiiim.com/posts/grpc-with-go-mod/
コードの自動生成
このproto/cat.proto
に基づいて、protoc
コマンドで、gRPCのコードを自動生成します。
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/cat.prot
参考 - https://grpc.io/docs/languages/go/basics/#generating-client-and-server-code
上記のコマンドで
proto/cat_grpc.pb.go
proto/cat.pb.go
が生成されますが、proto/cat_grpc.pb.go
の中は以下のようにmustEmbedUnimplementedCatServer()
というメソッドも生成されます。
type CatServer interface {
GetMyCat(context.Context, *GetMyCatMessage) (*MyCatResponse, error)
mustEmbedUnimplementedCatServer()
}
このメソッドがあると今回参考にした実装が動かなかったので、上記のメソッドが生成されないように自動生成コマンドを少し変更します。
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=require_unimplemented_servers=false:. --go-grpc_opt=paths=source_relative proto/cat.proto
参考
- https://github.com/grpc/grpc-go/issues/3794#issuecomment-725860916
- https://sat8bit.github.io/logs/daily-2021-01-28/
Service
package service
import (
"context"
"errors"
pb "example.com/cat/proto"
)
type MyCatService struct {
}
func (s *MyCatService) GetMyCat(ctx context.Context, message *pb.GetMyCatMessage) (*pb.MyCatResponse, error) {
switch message.TargetCat {
case "tama":
//たまはメインクーン
return &pb.MyCatResponse{
Name: "tama",
Kind: "mainecoon",
}, nil
case "mike":
//ミケはノルウェージャンフォレストキャット
return &pb.MyCatResponse{
Name: "mike",
Kind: "Norwegian Forest Cat",
}, nil
}
return nil, errors.New("Not Found YourCat")
}
func (s *MyCatService) UnimplementedGreeterServer() {}
Client
package main
import (
"context"
"fmt"
"log"
pb "example.com/cat/proto"
"google.golang.org/grpc"
)
func main() {
//sampleなのでwithInsecure
conn, err := grpc.Dial("127.0.0.1:19003", grpc.WithInsecure())
if err != nil {
log.Fatal("client connection error:", err)
}
defer conn.Close()
client := pb.NewCatClient(conn)
message := &pb.GetMyCatMessage{TargetCat: "tama"}
res, err := client.GetMyCat(context.TODO(), message)
fmt.Printf("result:%#v \n", res)
fmt.Println("---------")
fmt.Printf("error::%#v \n", err)
}
Server
package main
import (
"log"
"net"
"example.com/cat/service"
pb "example.com/cat/proto"
"google.golang.org/grpc"
)
func main() {
listenPort, err := net.Listen("tcp", ":19003")
if err != nil {
log.Fatalln(err)
}
server := grpc.NewServer()
catService := &service.MyCatService{}
// 実行したい実処理をseverに登録する
pb.RegisterCatServer(server, catService)
server.Serve(listenPort)
}
実行してみる
ターミナルで二つのプロセスを使います。
まず以下をうちこみます。
go run server/main.go
次に別のプロセスでサーバにアクセスします
go run client/main.go
client/main.go
で以下のoutputが見れます。
result:&pb.MyCatResponse{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(0xc0000e5a08)}, sizeCache:0, unknownFields:[]uint8(nil), Name:"tama", Kind:"mainecoon"}
---------
error::<nil>
tama
だけ取得したければ、以下のように変更します。
fmt.Printf("result:%#v \n", res.Name)