RESTful API/gRPC API提供
gRPC-GatewayはgRPCで提供されているAPIをRESTful APIに変換して、提供するためのコード生成ができます。
なので、HTTP/2をサポートしていない環境からは、RESTful API経由で、リクエストを受け付けるといった使い方ができます。
*.protoファイルで一括管理できて、サーバーの管理がとても楽になります。
備忘録です。
環境
% go version 
go version go1.22.5 darwin/arm64
% protoc --version
libprotoc 3.20.3
実行ファイルをインストール
go install \
    github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest \
    github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest
version
% protoc-gen-grpc-gateway --version
Version v2.22.0, commit unknown, built at unknown
% protoc-gen-openapiv2 --version
Version v2.22.0, commit unknown, built at unknown
※echo ${GOPATH}/binなどにパスは通しておく。
概要
 
引用:
https://grpc-ecosystem.github.io/grpc-gateway/#getting-started
Write and download Proto files
図書館の本を借りるサービスをGolangで実装します。このサービスにRestful APIとgRPC APIを実装します。
mkdir sample-book-lending
go mod init sample-book-lending
protofileを格納するディレクトリです。
mkdir proto
cd proto
Copy google/api/annotations.proto and google/api/http.proto
Copy google/api/annotations.proto and google/api/http.proto
Proxy Serverの実装のためのProtofileをコピーします。
mkdir -p google/api
cd google/api
curl -O https://raw.githubusercontent.com/googleapis/googleapis/refs/heads/master/google/api/annotations.proto
curl -O https://raw.githubusercontent.com/googleapis/googleapis/refs/heads/master/google/api/http.proto
mv *.proto google/api/
*.proto作成
book.proto
syntax = "proto3";
option go_package = "./pkg/grpc";
message Book {
  string title = 1; //
}
account.proto
syntax = "proto3";
// for proxy
import "google/api/annotations.proto";
import "book.proto";
option go_package = "./pkg/grpc";
package myapp;
// 本を借りるためのAccount
message Account {
  string name = 1;
}
message AccountInfo {
  string name = 1;
}
message BorrowRequest {
  Account account = 1;
  Book book = 2;
}
message BorrrowResponse {
  Account account = 1;
  Book book = 2;
}
// サービスの定義
service LendingBooksService {
  
  rpc SendBorrow (BorrowRequest) returns (BorrrowResponse);
  
  rpc AccountInfo (Account) returns (Account) {
    option (google.api.http) = {
      post: "/v1/accountinfo/books"
      body: "*"
    };
  }
}
pwd
sample-book-lending/proto
proto % tree
.
├── account.proto
├── book.proto
└── google
    └── api
        ├── annotations.proto
        └── http.proto
*.protoをビルド
protocを実行します。※bufを使用する方法もありますが今回はprotocを使用します。
protoc -I ./proto \
--go_out=./pkg/grpc --go_opt paths=source_relative \
--go-grpc_out ./pkg/grpc --go-grpc_opt paths=source_relative \
--grpc-gateway_out ./pkg/grpc --grpc-gateway_opt paths=source_relative \
./proto/*.proto
Server実装
ビルドした*.protoを使用してServerにメソッドを実装します。
下記のようにSever Processを実装するためにmain.goを作成します。
sample-book-lending % tree ./cmd
./cmd
├── client
└── server
    └── main.go
server/main.go
package main
import (
	"context"
	"fmt"
	"google.golang.org/grpc/reflection"
	hellopb "sample-book-lending/pkg/grpc"
	"log"
	"os"
	"os/signal"
	// (一部抜粋)
	"google.golang.org/grpc"
	"net"
	// for proxy
	"google.golang.org/grpc/credentials/insecure"
	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"net/http"
)
type myServer struct {
	hellopb.UnimplementedLendingBooksServiceServer
}
// account.protoの`service`に定義したメソッドの実装
// 本を借りるためのメソッド
func (s *myServer) SendBorrow(ctx context.Context, req *hellopb.BorrowRequest) (*hellopb.BorrrowResponse, error) {
	// リクエストからnameフィールドを取り出して
	// "Hello, [名前]!"というレスポンスを返す
	return &hellopb.BorrrowResponse{
		Account: &hellopb.Account{Name: req.Account.Name},
		Book: &hellopb.Book{Title: req.Book.Title},
	}, nil
}
// account.protoの`service`に定義したメソッドの実装
// アカウントの貸与状態を知るためのRestful API
func (s *myServer) AccountInfo(ctx context.Context, req *hellopb.Account) (*hellopb.Account, error) {
	// リクエストからnameフィールドを取り出して
	// "Hello, [名前]!"というレスポンスを返す
	return &hellopb.Account{
		Name: req.GetName(),
	}, nil
}
// 自作サービス構造体のコンストラクタを定義
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を登録
	// 第二引数はinterfaceであるGreetingServiceServerのため、これのメソッドリストを実装した構造体がはいる。
	hellopb.RegisterLendingBooksServiceServer(s, NewMyServer())
	// x_numberはproxy serverのインスタンス作成と起動です。
	// x_1. for proxy
	// Create a client connection to the gRPC server we just started
	// This is where the gRPC-Gateway proxies the requests
	conn, err := grpc.NewClient(
		"0.0.0.0:8080",
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	gwmux := runtime.NewServeMux()
	err = hellopb.RegisterLendingBooksServiceHandler(context.Background(), gwmux, conn)
	if err != nil {
		log.Fatalln("Failed to register gateway:", err)
	}
	gwServer := &http.Server{
		Addr:    ":8090",
		Handler: gwmux,
	}
	// 4. サーバーリフレクションの設定
	reflection.Register(s)
	// 5. 作成したgRPCサーバーを、8080番ポートで稼働させる
	go func() {
		log.Printf("start gRPC server port: %v", port)
		s.Serve(listener)
	}()
	// x_2. for proxy
	log.Println("Serving gRPC-Gateway on http://0.0.0.0:8090")
	log.Fatalln(gwServer.ListenAndServe())
	// 6.Ctrl+Cが入力されたらGraceful shutdownされるようにする
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt)
	<-quit
	log.Println("stopping gRPC server...")
	s.GracefulStop()
}
サーバーを起動
goを実行します。
cd cmd/server && go run main.go
2024/09/27 16:37:41 Serving gRPC-Gateway on http://0.0.0.0:8090
2024/09/27 16:37:41 start gRPC server port: 8080
Client
Restful APIとgRPC APIを実装しています。
リクエストを送信してみます。
call gRPC API
grpcurl -plaintext -d '{"account":{ "name": "Tanaka"},"book":{ "title": "赤毛のあん"}}' localhost:8080 myapp.LendingBooksService.SendBorrow
{
  "account": {
    "name": "Tanaka"
  },
  "book": {
    "title": "赤毛のあん"
  }
}
call Restful API
curl -X POST -k http://localhost:8090/v1/accountinfo/books -d '{"name": " Proxy Anpanman"}'
{"name":" Proxy Anpanman"}
Doc生成
Install protoc-gen-doc
protoc-gen-docをインストールしてください。
go install github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc@latest
ドキュメント生成
protofileからドキュメントを生成します。コマンドでprofofileから仕様書を生成できるので、ドキュメント管理が楽です。
protoc -I ./proto --doc_out=html,index.html:./doc proto/*.proto
生成されたProtocol Documentationです。
今回の全コードはこちら。
