こんにちは。KOUKIです。
前回はGreetAPIを実装したので、今回はその続きからです。
<目次>
前回
Go言語記事まとめ
CalcAPI
gRPCの実装は少し特殊なので、復習がてらCalcAPIを作ってみましょう。
まずは、protoファイルを作成してください。
1 2 3 |
# golang-grpc内で実行 mkdir -p calculator/calculatorpb/ touch calculator/calculatorpb/calculator.proto |
CalcAPIに、2つの引数を与えると何らかの計算結果を返してくれるAPIです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// calculator/calculatorpb/calculator.proto syntax = "proto3"; package calculator; option go_package="calculatorpb"; service CalculatorService { // Unary rpc Calc(CalcRequest) returns (CalcResponse) {}; } message Number { int32 num1 = 1; int32 num2 = 2; } message CalcRequest { Number number = 1; } message CalcResponse { int32 sum = 1; } |
続いて、generage.shファイルに次のコマンドを追加してください。
1 2 3 4 5 6 7 8 9 |
#!/bin/bash # exportは環境によっては不要 export PATH=$PATH:/Users/<user>/go/bin/ protoc greet/greetpb/greet.proto --go_out=plugins=grpc:. # new protoc calculator/calculatorpb/calculator.proto --go_out=plugins=grpc:. |
次のコマンドを実行します。
1 |
./generate.sh |
「calculator.pb.go」ファイルが、calculatorpbディレクトリ内に生成されたことを確認してください。
続いて、Serverコードを実装します。
1 2 |
mkdir -p calculator/calculator_server touch calculator/calculator_server/server.go |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
// calculator/calculator_server/server.go package main import ( "context" "fmt" "log" "net" "github.com/selfnote/golang-grpc/calculator/calculatorpb" "google.golang.org/grpc" ) type server struct{} func (*server) Calc(ctx context.Context, req *calculatorpb.CalcRequest) (*calculatorpb.CalcResponse, error) { fmt.Printf("Calc function was invoked with %v\n", req) num1 := req.GetNumber().GetNum1() num2 := req.GetNumber().GetNum2() sum := num1 + num2 res := &calculatorpb.CalcResponse{ Sum: sum, } return res, nil } func main() { fmt.Println("Server Start...") lis, err := net.Listen("tcp", "0.0.0.0:50051") if err != nil { log.Fatalf("Failed to listen: %v", err) } s := grpc.NewServer() calculatorpb.RegisterCalculatorServiceServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("Failed to server %v", err) } } |
実装内容は、GreetAPIとほぼ同じです。
続いて、Clientコードを実装しましょう。
1 2 |
mkdir -p calculator/calculator_client touch calculator/calculator_client/client.go |
ClientコードもGreetAPIとほぼ同じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
// calculator/calculator_client/client.go package main import ( "context" "fmt" "log" "github.com/selfnote/golang-grpc/calculator/calculatorpb" "google.golang.org/grpc" ) func doUnary(c calculatorpb.CalculatorServiceClient) { fmt.Println("Starting to do a Unary RPC...") req := &calculatorpb.CalcRequest { Number: &calculatorpb.Number{ Num1: 3, Num2: 10, }, } res, err := c.Calc(context.Background(), req) if err != nil { log.Fatalf("error while calling Calc RPC: %v", err) } log.Printf("Response from Calc: %v", res.Sum) } func main() { fmt.Println("Client Request...") cc, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) if err != nil { log.Fatalf("Could not connect: %v", err) } defer cc.Close() c := calculatorpb.NewCalculatorServiceClient(cc) fmt.Println("Created client: %f\n", c) doUnary(c) } |
実行コマンドをMakefileに実装しておきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
.PHONY: server client c_srv c_cnt server: go run greet/greet_server/server.go client: go run greet/greet_client/client.go c_srv: go run calculator/calculator_server/server.go c_cnt: go run calculator/calculator_client/client.go |
Serverコード->Clientコードの順に実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
make c_srv go run calculator/calculator_server/server.go Server Start... Calc function was invoked with number:<num1:3 num2:10 > make c_cnt go run calculator/calculator_client/client.go Client Request... Created client: %f &{0xc0000f6700} Starting to do a Unary RPC... 2019/10/25 09:14:03 Response from Calc: 13 |
gRPCを使えば、Go言語で簡単にAPIを作れますね。
GreetManyTimes API – protoファイルの定義 –
復習はここまでにして、GreetAPIの続きを実装していきます。
今回は、gRPCの通信規格の一つ、Server Streaming APIを使って、GreetManyTimes APIを作成します。
Server Streaming APIの特徴は、次の通りです。
・ Serverから大量のデータを送るときに適している
・ チャットなどClientからのリクエストを必要としない実装に適している
Server Streamingを設定するには、protoファイルのServiceのResponseに、kewordとして”stream“を付与するだけでOKです。
では、さっそくprotoファイルに定義してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
// greet/greetpb/greet.proto // version syntax = "proto3"; // go pakcage package greet; // Include subdirectory into package too option go_package="greetpb"; service GreetService{ // Unary rpc Greet(GreetRequest) returns (GreetResponse){}; // Server Streaming rpc GreetManyTimes(GreetManyTimesRequest) returns (stream GreetManyTimesResponse){}; }; message Greeting { string first_name = 1; string last_name = 2; } message GreetRequest { Greeting greeting = 1; } message GreetResponse { string result = 1; } message GreetManyTimesRequest { Greeting greeting = 1; } message GreetManyTimesResponse { string result = 1; } |
protoファイルの定義を変更したら下記のコマンドを実行してください。
1 |
./generate.sh |
GreetManyTimes API – Serverコードの定義 –
次は、Serverコードを実装します。
Server Streaming APIは、単一のリクエストに対して複数のレスポンスを返す仕様のため、動作確認用に10回レスポンスを返す処理をGreetManyTimes関数に実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
// greet/greet_server/server.go package main import ( "context" "fmt" "log" "net" "strconv" "time" "github.com/selfnote/golang-grpc/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 } # new func (*server) GreetManyTimes(req *greetpb.GreetManyTimesRequest, stream greetpb.GreetService_GreetManyTimesServer) error { fmt.Printf("GreetManyTimes function was invoked with %v\n", req) firstName := req.GetGreeting().GetFirstName() // Do print 10 times. for i := 0; i < 10; i++ { // Itoa -> Change int type to string type. result := "Hello " + firstName + " number" + strconv.Itoa(i) res := &greetpb.GreetManyTimesResponse { Result: result, } stream.Send(res) time.Sleep(1000 * time.Millisecond) } return 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) } } |
GreetManyTimes API – Clientコードの定義 –
続いて、Clientコードの実装を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
// greet/greet_client/client.go package main import ( "context" "fmt" "io" "log" "github.com/selfnote/golang-grpc/greet/greetpb" "google.golang.org/grpc" ) 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) doServerStreaming(c) } func doUnary(c greetpb.GreetServiceClient) { fmt.Println("Starting to do a Unary RPC...") req := &greetpb.GreetRequest{ Greeting: &greetpb.Greeting{ FirstName: "Harry", LastName: "Potter", }, } res, err := c.Greet(context.Background(), req) if err != nil { log.Fatalf("error whiole caling Greet RPC: %v\n", err) } log.Printf("Response from Greet: %v\n", res.Result) } func doServerStreaming(c greetpb.GreetServiceClient) { fmt.Println("Starting to do a Server Streaming PRC...") req := &greetpb.GreetManyTimesRequest { Greeting: &greetpb.Greeting { FirstName: "Hary", LastName: "Potter", }, } 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 { fmt.Println(io.EOF) break } if err != nil { log.Fatalf("error while reading stream: %v", err) } log.Printf("Response from greetManyTimes: %v", msg.GetResult()) } } |
GreetManyTimes API – 動作確認 –
次のコマンドをServer->Clientの順に実行してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
make server go run greet/greet_server/server.go Hello World GreetManyTimes function was invoked with greeting:<first_name:"Hary" last_name:"Potter" > make client go run greet/greet_client/client.go Hello I'm a client Starting to do a Server Streaming PRC... 2019/10/25 10:46:52 Response from greetManyTimes: Hello Hary number0 2019/10/25 10:46:53 Response from greetManyTimes: Hello Hary number1 2019/10/25 10:46:54 Response from greetManyTimes: Hello Hary number2 2019/10/25 10:46:55 Response from greetManyTimes: Hello Hary number3 2019/10/25 10:46:56 Response from greetManyTimes: Hello Hary number4 2019/10/25 10:46:57 Response from greetManyTimes: Hello Hary number5 2019/10/25 10:46:58 Response from greetManyTimes: Hello Hary number6 2019/10/25 10:46:59 Response from greetManyTimes: Hello Hary number7 2019/10/25 10:47:00 Response from greetManyTimes: Hello Hary number8 2019/10/25 10:47:01 Response from greetManyTimes: Hello Hary number9 EOF |
問題なく実行できましたね。
次回
次回は、Client Streaming APIについて学んでいきます。
コメントを残す
コメントを投稿するにはログインしてください。