3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ではgRPCサーバーを実装していきましょう。

server.go

server.goファイルを作成し、server構造体を定義します。この構造体にサービスのインターフェイスを実装していきます。

server.go
package main

type server struct{}

エンドポイントの実装

定義すべきインターフェイスは昨日生成したservice.pb.goに定義されている以下の3つです。

service.pb.go
type TodoAPIServer interface {
	GetTodo(context.Context, *GetTodoRequest) (*GetTodoResponse, error)
	CreateTodo(context.Context, *CreateTodoRequest) (*CreateTodoResponse, error)
	ListTodos(context.Context, *ListTodosRequest) (*ListTodosResponse, error)
}

このパッケージをインポートし、メソッドを定義しましょう。

server.go
package main

import (
	"context"

	"github.com/KentaKudo/qiita-advent-calendar-2019/internal/pb/service"
)

var _ service.TodoAPIServer = (*server)(nil)

type server struct{}

func (*server) GetTodo(context.Context, *service.GetTodoRequest) (*service.GetTodoResponse, error) {
	return &service.GetTodoResponse{}, nil
}

func (*server) CreateTodo(context.Context, *service.CreateTodoRequest) (*service.CreateTodoResponse, error) {
	return &service.CreateTodoResponse{}, nil
}

func (*server) ListTodos(context.Context, *service.ListTodosRequest) (*service.ListTodosResponse, error) {
	return &service.ListTodosResponse{}, nil
}

ためしに、GetTodoではダミーのデータを返すようにしてみます。

func (*server) GetTodo(context.Context, *service.GetTodoRequest) (*service.GetTodoResponse, error) {
	return &service.GetTodoResponse{
		Todo: &service.Todo{
			Title:       "clean your desk!",
			Description: "clean up the piles of documents from the desk to make more space.",
		},
	}, nil
}

サーバープロセスの立ち上げ

main.goファイルに戻り、gRPCサーバープロセスを新たなgoroutineとして起動します。
デフォルトで8090ポートを使います。

main.go
func main() {
	app := cli.App(appName, appDesc)

	...

	grpcPort := app.Int(cli.IntOpt{
		Name:   "grpc-port",
		Desc:   "gRPC server port",
		EnvVar: "GRPC_PORT",
		Value:  8090,
	})

	app.Action = func() {
		...

		lis, err := net.Listen("tcp", net.JoinHostPort("", strconv.Itoa(*grpcPort)))
		if err != nil {
			log.Fatalln("init gRPC server:", err)
		}
		defer lis.Close()

		gSrv := initialiseGRPCServer(&server{})

		errCh := make(chan error, 2) // <- 1 incr.

		...

		go func() {
			if err := gSrv.Serve(lis); err != nil {
				errCh <- errors.Wrap(err, "gRPC server")
			}
		}()

		...
	}

	if err := app.Run(os.Args); err != nil {
		log.WithError(err).Fatal("app run")
	}
}

ここで、initialiseGRPCServer()関数は以下のようになっています。

func initialiseGRPCServer(srv service.TodoAPIServer) *grpc.Server {
	gSrv := grpc.NewServer()

	service.RegisterTodoAPIServer(gSrv, srv)
	return gSrv
}

Gracefulなサーバーのシャットダウン

一応サーバープロセスを開始することができましたが、このままでは異常発生時にリクエストを捌く途中のままシャットダウンしてしまうことになるので、丁寧にシャットダウンするように改善してみます。

やることは2つ、

  1. 異常発生時にGracefulStop()メソッドを呼び出す
  2. sync.WaitGroupを使って、サーバーの終了を待ち受ける
    です。
func main() {
	...

	app.Action = func() {
		...

		var wg sync.WaitGroup
		wg.Add(1)
		go func() {
			defer wg.Done()

			if err := gSrv.Serve(lis); err != nil {
				errCh <- errors.Wrap(err, "gRPC server")
			}
		}()

		sigCh := make(chan os.Signal, 1)
		signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

		select {
		case err := <-errCh:
			log.Println(err)
		case <-sigCh:
			log.Println("termination signal received. attempt graceful shutdown")
		}
		gSrv.GracefulStop()
		wg.Wait()

		log.Println("bye")
	}

	...
}

さて、せっかくサーバーも立ち上がったのでデバッグしてみましょう。
みなさんgRPCの動作確認には何を使っていますか?httpではないのでcurlは使えませんね。
おそらくはgRPC版のcurlである fullstorydev/grpcurl の人気が高いのではないかと思います。
このツールの使い方に関してはこちらの記事が詳しくてわかりやすいためオススメです。

明日はgRPCのGUIクライアントツールを紹介したいと思います。お楽しみに。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?