4
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?

grpc-gatewayってなに?実装しながら理解してみよう

Last updated at Posted at 2024-10-20

株式会社Schoo 新卒1年目の @hiroto_0411です!
最近、gRPCサーバーの実装を行っていく中で、grpc-gatewayというのがあると知ったので実装しながら理解を深めてみました!

この記事でわかること

  • grpc-gatewayってなに?
  • grpc-gatewayを使った、gRPCサーバーを作る流れ

gRPCについて理解が曖昧な方は以下の記事にまとめたのでこちらを先に見ていただくと、この記事が理解しやすくなると思います。

grpc-gatewayとは

.protoファイルを元に、REST APIに送られてきたリクエストを変換して、gRPCサーバーに転送するためのリバースプロキシーサーバーを作ることができるプラグイン。

リバースプロキシーサーバー
クライアントからのリクエストを別のサーバーに転送するサーバー

イメージ
スクリーンショット 2024-10-17 20.52.22.png
grpc-gatewayでリバースプロキシーサーバーを作ることで、REST APIとgRPC APIどちらも実装できる。

後方互換性の維持、gRPCで十分にサポートされていない言語やクライアントのサポート、RESTの思想の方が合っているなど、gRPCだけでなくREST APIも提供したいとケースに対応するために作られた。

参考

grpc-gatewayを使ってみる

上記のREADMEを参考にしながらgrpc-gatewayを使ってみた。

作成したコードは以下で見られる。

ディレクトリ構成はこんな感じになる予定。

.
├── gateway
│   └── main.go
├── gen
│   ├── helloworld_grpc.pb.go
│   ├── helloworld.pb.go
│   └── helloworld.pb.gw.go
├── proto
│   └── helloworld.proto
├── server
│   └── helloworld.go
├── buf.gen.yaml
├── buf.lock
├── buf.yaml
├── go.mod
├── go.sum
└── main.go

1. 準備

モジュールの作成

go mod init github.com/user-name/project-name   

コード生成に必要なツールのインストール

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go install github.com/bufbuild/buf/cmd/buf@latest
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest

PATHを更新

export PATH="$PATH:$(go env GOPATH)/bin"

2. .protoファイルの作成

Protocol Buffersというフォーマットを使用してgRPCのサービス名やメソッド名、リクエストやレスポンスを定義する。

./proto/helloworld.proto
syntax = "proto3";

option go_package = "github.com/user-name/project-name/gen";

import "google/api/annotations.proto";

package hello;

service Greeter {
    rpc SayHello(HelloRequest) returns (HelloResponse){
        option (google.api.http) = {
          post: "/hello"
          body: "*"
        };
  }
}

message HelloRequest {
    string name = 1;
}

message HelloResponse {
    string message = 1;
}

この後、.protoファイルを元にコードを自動生成する。その時に出力したいpackage名をgo_packageで指定する。package hello;はGoのpackage名とは関係ない。このpackageは名前が衝突するのを防ぐために使われる。

package user;

message Person {
    string name = 1;
    int32 age = 2;
}
package employee;

message Person {
    string name = 1;
    string employee_id = 2;
}

このように同じ名前のserviceやmessageを定義したときに問題が発生しないように、package名を指定する。

3. コードの自動生成

buf.yamlファイルをルートディレクトリに作成
buf.yamlにはどのディレクトリの.protoファイルをコンパイルするかや、linterなどの設定、リモートのプロジェクトやサードパーティのProtobufファイルを参照するための依存関係の設定などを記述する。

buf config init
./buf.yaml
# For details on buf.yaml configuration, visit https://buf.build/docs/configuration/v2/buf-yaml
version: v2
lint:
  use:
    - STANDARD
breaking:
  use:
    - FILE
deps:
  - buf.build/googleapis/googleapis

./proto/helloworld.protoimport "google/api/annotations.proto";している依存関係を記述する。

依存関係のロックファイルを更新
初めてこのコマンドを実行するときはbuf.lockが作成される。

buf mod update

buf.gen.yamlをルートディレクトリに作成

./buf.gen.yaml
version: v2
plugins:
  - local: protoc-gen-go
    out: gen
    opt:
      - module=github.com/user-name/project-name/gen
  - local: protoc-gen-go-grpc
    out: gen
    opt: 
      - module=github.com/user-name/project-name/gen
  - local: protoc-gen-grpc-gateway
    out: gen
    opt:
      - module=github.com/user-name/project-name/gen
      - generate_unbound_methods=true

コードを自動生成
以下のコマンドを実行することで、.protoファイルを元に、XXX_grpc.pg.goとXXX.pb.go、XXX.pb.gw.goの3種類のファイルが自動生成される。

buf generate

モジュールの依存関係を整理

go mod tidy

4. 自動生成されたinterfaceを使い、gRPCサーバーを実装する

./gen/helloworld_grpc.pb.go
type GreeterServer interface {
	SayHello(context.Context, *HelloRequest) (*HelloResponse, error)
	mustEmbedUnimplementedGreeterServer()
}

type UnimplementedGreeterServer struct{}

func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloResponse, error) {
	return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {}
./server/helloworld.go
package server

import (
	"context"
	"log"

	pb "github.com/user-name/project-name/gen"
)

type GreeterServer struct {
	pb.UnimplementedGreeterServer
}

// ./gen/helloworld_grpc.pb.goに定義されているGreeterServerインターフェースを実装する
func (s *GreeterServer) SayHello(_ context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
	log.Printf("Received: %v", in.GetName())

	// 本来ならここから、別の層のメソッドを呼び出すなどする

	return &pb.HelloResponse{Message: "Hello " + in.GetName()}, nil
}

./gen/helloworld_grpc.pb.goに定義されているGreeterServerインターフェースを実装するメソッドを作成し、それをmain.goで使用する。

./main.go
package main

import (
	"flag"
	"fmt"
	"log"
	"net"

	pb "github.com/user-name/project-name/gen"
	"github.com/user-name/project-name/server"

	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
)

var (
	port = flag.Int("port", 50051, "The server port")
)

func main() {
	flag.Parse()
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server.GreeterServer{})
	log.Printf("server listening at %v", lis.Addr())

	reflection.Register(s)

	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

5. gatewayサーバーを実装する

./gateway/main.go
package main

import (
	"context"
	"flag"
	"net/http"

	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/grpclog"

	gw "github.com/user-name/project-name/gen"
)

var (
	// command-line options:
	// gRPC server endpoint
	grpcServerEndpoint = flag.String("grpc-server-endpoint", "localhost:50051", "gRPC server endpoint")
)

func run() error {
	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	// Register gRPC server endpoint
	// Note: Make sure the gRPC server is running properly and accessible
	mux := runtime.NewServeMux()
	opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
	err := gw.RegisterGreeterHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts)
	if err != nil {
		return err
	}

	// Start HTTP server (and proxy calls to gRPC server endpoint)
	return http.ListenAndServe(":8081", mux)
}

func main() {
	flag.Parse()

	if err := run(); err != nil {
		grpclog.Fatal(err)
	}
}

6. それぞれのサーバーにリクエストを送ってみる

サーバーの起動
それぞれのmain.goがあるディレクトリに移動して起動する。

go run main.go

grpcurlをインストール

brew install grpcurl

gRPCサーバーにリクエストを送信

 grpcurl -plaintext -d '{"name": "パブりん"}' localhost:50051 hello.Greeter/SayHello
{
  "message": "Hello パブりん"
}

gatewayサーバーにリクエストを送信

curl \
    --header "Content-Type: application/json" \
    --data '{"name": "パブりん"}' \
    http://localhost:8081/hello   
{
  "message": "Hello パブりん"
}

参考


Schooでは一緒に働く仲間を募集しています!

4
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
4
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?