はじめに
この記事ではgo × gRPCで簡単なリクエスト送信・レスポンスの表示を説明していきます。
大きく分けて、下記の3つを順番に実行していきます。
- protoファイルからコードを自動生成
- goでgRPCサーバーを起動
- goでクライアントを実行してリクエスト送信・レスポンス表示
最初はローカルで実行して、次に同じことをDocker環境でも実行します。
この記事を書くにあたり、下記を参考にさせていただきました。
今回実装したリポジトリです。
全体のコードはこちらをご覧くだしさい。
ローカル環境で実行する場合
パッケージインストール
まずは必要なパッケージをインストールします。
$ brew install protobuf
$ go get -u google.golang.org/grpc
$ go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc
パッケージの依存関係を管理するためのgo.modファイルを生成します。
$ cd study_grpc # 作業ディレクトリ
$ go mod init mygrpc
ディレクトリ構成
今回は下記のようなディレクトリ構成にしました。
./study_grpc # 作業ルートディレクトリ
├─ api
│ └─ hello.proto # protoファイル
├─ cmd
│ ├─ server
│ │ └─ main.go
│ └─ client
│ └─ main.go
├─ pkg
│ └─ grpc # ここにコードを自動生成させる
│ ├─ hello.pb.go
│ └─ hello_grpc.pb.go
├─ go.mod
└─ go.sum
protoファイル作成
api/hello.proto
// protoのバージョンの宣言
syntax= "proto3";
// protoファイルから自動生成させるGoのコードの置き先
// (詳細は4章にて)
option go_package = "pkg/grpc";
// packageの宣言
package myapp;
// サービスの定義
service GreetingService {
// サービスが持つメソッドの定義
rpc Hello (HelloRequest) returns (HelloResponse);
}
// 型の定義
message HelloRequest {
stringname = 1;
}
message HelloResponse {
stringmessage = 1;
}
protocコマンドでコードを自動生成
下記のコマンドを実行するとpkg/grpc
ディレクトリ直下に、2つのファイルが生成されます。
$ cd api
$ protoc --go_out=../pkg/grpc --go_opt=paths=source_relative \
--go-grpc_out=../pkg/grpc --go-grpc_opt=paths=source_relative \
hello.proto
--go_opt=paths=source_relative
は相対パスを指定するオプションです。
./study_grpc/api
でコマンドを実行したため、相対パスでファイルの保存先を指定しています。
ちなみに--go_opt=paths=source_relative
を使わず、./study_grpc/
から実行する場合は、
下記のコマンドになります。
$ protoc --go_out=./ --go-grpc_out=./ api/hello.proto
gRPCサーバーを作成
cmd/server/main.go
package main
import (
"fmt"
"net"
"log"
"os"
"context"
"os/signal"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
hellopb "mygrpc/pkg/grpc"
)
func NewMyServer() *myServer {
return &myServer{}
}
func main() {
// 1. 8080番portのLisnterを作成
port := 8080
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
panic(err)
}
// 2. gRPCサーバーを作成
s := grpc.NewServer()
// 3. gRPCサーバーにGreetingServiceを登録
hellopb.RegisterGreetingServiceServer(s, NewMyServer())
// 4. サーバーリフレクションの設定
reflection.Register(s)
// 5. 作成したgRPCサーバーを、8080番ポートで稼働させる
go func() {
log.Printf("start gRPC server port: %v", port)
s.Serve(listener)
}()
// 6.Ctrl+Cが入力されたらGraceful shutdownされるようにする
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
log.Println("stopping gRPC server...")
s.GracefulStop()
}
type myServer struct {
hellopb.UnimplementedGreetingServiceServer
}
func (s *myServer) Hello(ctx context.Context, req *hellopb.HelloRequest) (*hellopb.HelloResponse, error) {
// リクエストからnameフィールドを取り出して
// "Hello, [名前]!"というレスポンスを返す
return &hellopb.HelloResponse{
Message: fmt.Sprintf("Hello, %s!", req.GetName()),
}, nil
}
gRPCurlをインストール
gPRCはcurlで動作確認できませんが、gPRCurlを使うことで,
ローカル環境でも動作確認することができます。
$ brew install grpcurl
gRPCサーバーを起動
これでgRPCサーバーを起動する準備ができました!
下記コマンドを実行してgRPCが起動すれば成功です!
$ go cmd/server/run main.go
2023/02/13 10:01:38 start gRPC server port: 8080
gRPCクライアントを作成
cmd/server/main.go
package main
import (
"bufio"
"fmt"
"os"
"log"
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
hellopb "mygrpc/pkg/grpc"
)
var (
scanner *bufio.Scanner
client hellopb.GreetingServiceClient
)
func main() {
fmt.Println("start gRPC Client.")
// 1. 標準入力から文字列を受け取るスキャナを用意
scanner = bufio.NewScanner(os.Stdin)
// 2. gRPCサーバーとのコネクションを確立
address := "localhost:8080"
conn, err := grpc.Dial(
address,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithBlock(),
)
if err != nil {
log.Fatal("Connection failed.")
return
}
defer conn.Close()
// 3. gRPCクライアントを生成
client = hellopb.NewGreetingServiceClient(conn)
for {
fmt.Println("1: send Request")
fmt.Println("2: exit")
fmt.Print("please enter >")
scanner.Scan()
in := scanner.Text()
switch in {
case "1":
Hello()
case "2":
fmt.Println("bye.")
goto M
}
}
M:
}
func Hello() {
fmt.Println("Please enter your name.")
scanner.Scan()
name := scanner.Text()
req := &hellopb.HelloRequest{
Name: name,
}
res, err := client.Hello(context.Background(), req)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(res.GetMessage())
}
}
クライアントを実行して動作確認
gRPCサーバーとは別のターミナルを開いて、下記を実行することで、
gPRCサーバーに対して、リクエスト送信・レスポンス表示をすることができます。
$ go run cmd/client/main.go
start gRPC Client.
1: Hello
2: exit please enter >1
Please enter your name.
shungo
Hello, shungo!
1: Hello
2: exit please enter >2
bye.
このように、リクエスト送信・レスポンスの表示ができれば成功です!
Docker環境で実行する場合
これまではローカル環境にパッケージをインストールして、ローカル環境でgRPC用のコードを生成していました。
gRPC入門の記事を見るとローカルで実行するものが多いですが、バージョン管理などを考慮したらやはりDocker環境で実行したいですよね。
そこで、これまでやってきたことを今後はDocker環境で実行してみようと思います。
Dockerfile
FROM golang:1.19.5-buster
ENV PROTOBUF_VERSION3.17.3
RUN apt-getupdate \
&& apt-getinstall -y protobuf-compiler unzip --no-install-recommends \
&& apt-getclean \
&& rm-rf /var/lib/apt/lists/*
WORKDIR /grpc
COPY. /grpc
## build時のみ使いたいので、runで実行。
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
RUN go get google.golang.org/grpc/internal/channelz@v1.53.0
docker-compose.yml
version:'3'
services:
grpc:
build: .
volumes:
- .:/grpc
ports:
- "8080:8080"
tty: true
Docker環境で実行する
1つ目のターミナルでdocker-composeを起動します。
$ docker-compose up
2つ目のターミナルでコンテナに入り、gRPCサーバーを起動します。
$ docker-compose exec grpc bash
> go run cmd/server/main.go
2023/02/13 10:01:38 start gRPC server port: 8080
3つ目のターミナルでコンテナに入り、クライアントを動かします。
$ docker-compose exec grpc bash
> go run cmd/client/main.go
1: Hello
2:exit
please enter>1
Please enter your name.
shungo
Hello, shungo!
1: Hello
2:exit
please enter>2
bye.
これでDocker環境でもgRPCを動かせることを確認できました!
上記では実行していませんが、コンテナ内で同じようにprotocコマンドでコードを生成することも可能です。
コメント