1
Help us understand the problem. What are the problem?

More than 1 year has 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クライアントツールを紹介したいと思います。お楽しみに。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
1
Help us understand the problem. What are the problem?