1. Home
  2. Golang
  3. GuideToBecomingGoDeveloper
  4. ToolForMicroservices
  5. gRPC-Gateway - Golang learning step 10-1

gRPC-Gateway - Golang learning step 10-1

  • 公開日
  • カテゴリ:ToolForMicroservices
  • タグ:Golang,roadmap.sh,学習メモ
gRPC-Gateway - Golang learning step 10-1

roadmap.sh > Go > Tool for Microservices > gRPC-Gateway の学習を進めていきます。

※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。

contents

  1. 開発環境
  2. 参考 URL
  3. gRPC-Gateway
  4. gRPC-Gateway のインストールと準備
    1. 1. 必要なツールをインストール
    2. 2. annotations.proto
    3. 2. .proto ファイルの作成
    4. 3. protoc でコード生成
  5. gRPC サーバーの実装
  6. gRPC-Gateway の実装
  7. gRPC & REST API の動作確認
    1. 1. サーバーを起動
    2. 2. 動作確認

開発環境

  • チップ: Apple M2 Pro
  • OS: macOS Sonoma
  • go version: go1.23.2 darwin/arm64

参考 URL

gRPC-Gateway

gRPC-Gateway は、gRPC サーバーの API を RESTful API として公開するためのツールです。 Go 言語で実装された gRPC サーバーを HTTP/JSON クライアントでも利用できるようにします。 これにより、gRPC クライアントが使えない環境でも gRPC サーバーと通信できる というメリットがあります。

gRPC-Gateway は以下の仕組みで動作します:

  1. .proto ファイルに gRPC の定義を記述
  2. gRPC サーバーを実装(grpc-go を使用)
  3. protoc(Protocol Buffers コンパイラ)を使って gRPC-Gateway のコードを自動生成
  4. 生成したコードを用いて、REST API を提供する HTTP サーバーを起動

gRPC-Gateway のインストールと準備

# プロジェクトを初期化
go mod init sample

1. 必要なツールをインストール

まず、必要なツールやライブラリをインストールします。

tools.go を作成し、go mod tidy を実行します。

//go:build tools
// +build tools

package tools

import (
	_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway"
	_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2"
	_ "google.golang.org/grpc/cmd/protoc-gen-go-grpc"
	_ "google.golang.org/protobuf/cmd/protoc-gen-go"
)

2. annotations.proto

gRPC-Gateway では google/api/annotations.proto などの Google API 定義を利用するため、googleapis リポジトリをプロジェクトルートにダウンロードしておきます。

git clone https://github.com/googleapis/googleapis.git

2. .proto ファイルの作成

次に、.proto ファイルを作成します。proto/hello.proto を作成します。

syntax = "proto3";

package hello;

option go_package = "grpc/hello";

import "google/api/annotations.proto";

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

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string message = 1;
}

この .proto ファイルには SayHello という API を定義しています。

name を送信すると message が返る、シンプルな API です。

3. protoc でコード生成

以下のコマンドで gRPC と gRPC-Gateway のコードを自動生成します。

protoc -I . -I googleapis --go_out=. --go-grpc_out=. --grpc-gateway_out=. \
    --go_opt=paths=source_relative \
    --go-grpc_opt=paths=source_relative \
    --grpc-gateway_opt=paths=source_relative \
    proto/hello.proto

.proto ファイルを protoc でコンパイルするときに、import された .proto ファイル(googleapis)を適切に解決するために -I オプション(--proto_path)を指定しています。

このコマンドを実行すると、以下の Go ファイルが生成されます:

  • proto/hello.pb.go(gRPC メッセージ定義)
  • proto/hello_grpc.pb.go(gRPC サーバーのインターフェース)
  • proto/hello.pb.gw.go(gRPC-Gateway の変換コード)

gRPC サーバーの実装

次に、gRPC サーバーを作成します。

server/main.go を作成し、以下を実装します。

package main

import (
	"context"
	"fmt"
	"log"
	"net"

	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
	pb "sample/proto"
)

// HelloService の実装
type helloServer struct {
	pb.UnimplementedHelloServiceServer
}

func (s *helloServer) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
	return &pb.HelloResponse{
		Message: fmt.Sprintf("Hello, %s!", req.Name),
	}, nil
}

func main() {
	// gRPC サーバーを起動
	listener, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
	}

	grpcServer := grpc.NewServer()
	reflection.Register(grpcServer)
	pb.RegisterHelloServiceServer(grpcServer, &helloServer{})

	log.Println("gRPC server listening on port 50051...")
	if err := grpcServer.Serve(listener); err != nil {
		log.Fatalf("Failed to serve: %v", err)
	}
}

このコードでは SayHello メソッドを実装し、リクエストに対して "Hello, <name>!" を返すシンプルな gRPC サーバーを作成しています。

gRPC-Gateway の実装

次に、gRPC サーバーを REST API として公開する gRPC-Gateway を実装します。

gateway/main.go を作成し、以下を実装します。

package main

import (
	"context"
	"log"
	"net/http"

	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"google.golang.org/grpc"
	pb "sample/proto"
)

func main() {
	mux := runtime.NewServeMux()
	opts := []grpc.DialOption{grpc.WithInsecure()}

	// gRPC-Gateway を gRPC サーバーに接続
	err := pb.RegisterHelloServiceHandlerFromEndpoint(context.Background(), mux, "localhost:50051", opts)
	if err != nil {
		log.Fatalf("Failed to start gRPC-Gateway: %v", err)
	}

	log.Println("gRPC-Gateway listening on port 8080...")
	http.ListenAndServe(":8080", mux)
}

このコードでは RegisterHelloServiceHandlerFromEndpoint を使い、gRPC サーバーの localhost:50051 に接続し、REST API として公開しています。

gRPC & REST API の動作確認

1. サーバーを起動

gRPC サーバーを起動します。

go run server/main.go

gRPC-Gateway を起動します。

go run gateway/main.go

2. 動作確認

01. gRPC プロトコルでのリクエスト

grpcurl(要インストール)を使って、サーバーに直接 gRPC プロトコルでリクエストを送信します。

grpcurl -plaintext -d '{"name": "Alice"}' localhost:50051 hello.HelloService/SayHello

結果は以下になります。gRPC のバイナリ通信が適切に処理されていることが確認できました。

{
  "message": "Hello, Alice!"
}

02. HTTP (RESTful API) でのリクエスト

gRPC-Gateway 経由の REST API の動作確認です。curl コマンドを使ってリクエストを送信します。

 curl -X POST http://localhost:8080/v1/hello \
     -H "Content-Type: application/json" \
     -d '{"name": "Alice"}'

以下、HTTP/JSON で gRPC-Gateway 経由のリクエストが成功し、REST API として正しく動作していることを確認できました。

{
  "message": "Hello, Alice!"
}

まとめ

gRPC-Gateway は、Go で実装した gRPC サーバーを、RESTful API(HTTP/JSON)として公開するためのツール。gRPC サーバーにアクセスできない環境(ブラウザや REST API クライアント)からでも、gRPC サーバーを利用できるようにする。

どんな時に役立つか

  • Web フロントエンドやモバイルアプリから gRPC サーバーを利用したい時
    • gRPC はバイナリ通信のため、通常のブラウザやモバイルから直接利用できないが、gRPC-Gateway を使えば REST API として利用可能になる。
  • gRPC をサポートしていない外部サービスと連携する時
    • REST API しか使えない外部システムとも gRPC-Gateway を介して連携できる。
  • gRPC サーバーを構築しつつ、従来の REST API も提供したい時
    • クライアントの移行をスムーズにしながら、新旧の API を共存させられる。

gRPC-Gateway のメリット

  • gRPC の高性能な通信(バイナリ)と REST API の互換性を両立できる
  • gRPC サーバーのコードをそのまま活用しつつ、追加で REST API を提供できる
  • API の定義を .proto に統一することで、gRPC と REST API の一貫性を保てる
  • フロントエンドとバックエンドの技術スタックを柔軟に選べる

gRPC-Gateway のデメリット

  • gRPC-Gateway を経由すると、直接 gRPC を使うよりレイテンシが増加する
  • REST API のエラーハンドリングを適切に設定する必要がある


[Prev] Step 10-2: Rpcx

[Prev] Step 9-1: Testing

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級