本記事 Cocone Advent Calendar 2023
9日目の記事となります。
はじめに
最近では様々な事情もあり、connect-goの利用を始めています。
今回は簡単な利用方法と、protocol bufferのコンパイルもDockerを利用し、すぐに開発開始できるようにしたいよね、ということで、その為に必要な作業をまとめました。
※golang及びDockerの利用が可能、という前提で進めて行きます
connect-goについて
gRPC互換で、よりスリムに、よりスマートに利用できるHTTP APIライブラリです。今回はgolangで利用しますが、web/kotlin/swift/nodeなど、様々な環境への対応も進んでいます。
Unary RPC
Client Streaming RPC
Server Streaming RPC
Bidirectional Streaming RPC
へも対応しており、今回は触れませんが、各種interceptorも利用できます。
既にgRPCで構築されているシステムでも比較的容易に切り替えできますし、もし「アプリでリリース予定だが、webでのリリースも考えて行きたい」という状況でも、一つのコードでそのまま対応可能なため、クロスプラットフォームがもはやデファクトになりつつある昨今ではとても魅力的かと思います。
また後述しますが、最近ではクライアント側(Unity)のHTTP/2対応の話も出ているので、その点においても切り替え検討の余地はあると思います。
ディレクトリ構成
.
├─ protos
| └─ echo.proto
├─ server
│ └─ main.go
├─ buf.gen.yaml
├─ docker-compose.yaml
├─ Dockerfile.proto
└─ generate-proto.sh
proto
syntax = "proto3";
package pb;
option go_package = ".;pb";
service EchoService {
rpc Echo(EchoRequest) returns (EchoResponse) {}
}
message EchoRequest { string msg = 1; }
message EchoResponse { string msg = 1; }
protoのビルドコンテナを準備
次にprotoがビルドできる環境を構築していきます。
docker-compose.yaml
今回composeを利用して手軽に管理したかったため、docker-composeを利用しています。やっていることは単純で、必要なファイルをマウントし、ビルド用のスクリプトを立ち上げているだけです。
version: "3.7"
services:
proto_builder_connect_go:
build:
context: ./
dockerfile: ./Dockerfile.proto
volumes:
- ./protos:/builder/protos
- ./server/gen/pb:/builder/gen
command: bash generate-proto.sh
Dockerfile
次にDockerfileです。connect-goの利用に必要な各種モジュールをインストールしています。
FROM golang:1.20.10-bullseye
WORKDIR /builder
RUN apt update && apt install -y unzip rsync clang-format vim
COPY ./generate-proto.sh ./
COPY ./buf.gen.yaml ./
RUN go install github.com/bufbuild/buf/cmd/buf@latest
RUN go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
RUN go install github.com/bufbuild/connect-go/cmd/protoc-gen-connect-go@latest
ENV PATH $PATH:/root/.local/bin/:/go/bin/
buf.gen.yaml
ビルドの設定です。詳細は参考リンクを見ていただければともいますが、サンプルは server/gen/pb
へ成果物を出力するものとなっています。
version: v1
managed:
enabled: true
go_package_prefix:
default: server/gen/pb
plugins:
- name: go
out: gen
opt: paths=source_relative
- name: connect-go
out: gen
opt: paths=source_relative
generate-proto.sh
最後にビルドスクリプトです。buf generate
を呼び出すことで、作成した protoファイル からgolangで利用できるコードを生成しています。
#!/bin/bash
set -e -o pipefail
PROTO_DIR='/builder/protos'
OUT_TMP='/builder/gen'
buf generate --template buf.gen.yaml protos
exit 0
実行
docker-compose -f docker-compose.yaml up
実行が完了すると、 server/gen/pb
ディレクトリに下記ファイルが生成されます。
これで準備が完了したので、実際にgolangで利用してみます。
- echo.pb.go
- pbconnect/echo.pbconnect.go
golang側の準備
# モジュール初期化
cd server
go mod init server
# 必要なパッケージを取得
go get google.golang.org/protobuf
go get github.com/bufbuild/connect-go
go get github.com/bufbuild/connect-grpcreflect-go
go get golang.org/x/net
# メインファイル作成
touch main.go
サーバーの実装です。
package main
import (
"context"
"fmt"
"log"
"net/http"
"server/gen/pb"
"server/gen/pb/pbconnect"
"github.com/bufbuild/connect-go"
grpcreflect "github.com/bufbuild/connect-grpcreflect-go"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
func main() {
log.Println("setup server")
// EchoServiceの作成
echoService := EchoService{}
// サービス登録
mux := http.NewServeMux()
handle := mux.Handle
handle(pbconnect.NewEchoServiceHandler(echoService))
// テスト用にリフレクションサービスを利用
reflector := grpcreflect.NewStaticReflector(
pbconnect.EchoServiceName,
)
handle(grpcreflect.NewHandlerV1(reflector))
// サーバー起動
log.Println("start server")
if err := http.ListenAndServe(
":8090",
h2c.NewHandler(mux, &http2.Server{}),
); err != nil {
log.Fatal(err)
}
}
// EchoService: EchoServiceの実装
type EchoService struct{}
// Echo: 送られてきたメッセージをそのまま返す
func (s EchoService) Echo(ctx context.Context, req *connect.Request[pb.EchoRequest]) (*connect.Response[pb.EchoResponse], error) {
log.Println("EchoService.Echo:", req.Msg.Message)
return connect.NewResponse(&pb.EchoResponse{
Message: fmt.Sprintf("%s from server", req.Msg.Message),
}), nil
}
実際に呼び出してみる
サーバーを起動し、 grpcurl
または curl
でアクセスしてみます。
cd server
go run main.go
>2023/12/06 09:42:44 setup server
>2023/12/06 09:42:44 start server
# curl
curl --header "Content-Type: application/json" --data '{"message": "hello"}' http://localhost:8080/pb.EchoService/Echo
>{"message":"hello from server"}
# grpccurl
grpcurl -plaintext -d '{"message": "hello"}' localhost:8080 pb.EchoService/Echo
>{
> "message": "hello from server"
>}
最後に
Unity側のgRPCサポート問題が浮上する中、Cysharp様がUnityでHTTP/2を利用できる YetAnotherHttpHandler
をリリースし、クライアント側もgRPCからHTTP/2へ切り替える場面が出てきています。
そのような場合に、より円滑に進行できる状況を先んじて作っておく事はとても重要で、その積み重ねがより良い開発環境・良いサービスに繋がっていくと思うので、現状gRPCを利用している方も是非この機会に切り替えを検討してみてはいかがでしょうか。