gRPCは、googleが開発したRPCライブラリ+フレームワークです。
通信はHTTP/2、IDL(interface description language)はProtocol Buffers、というあたりが特徴になります。
とはいえ、お試しレベルで使う分には「RPCである」という以上の特徴はないかもしれません。
もっと詳しく知りたい人は、以下を見ていただくのがよいと思います。
Goal
Go 1.9.2 + gRPCで動作するサンプルを紹介する。
環境構築
Windows 10 Pro 64bit を使った例として記載します。
何か不明点がある場合は、オフィシャルの Quick Start - Go を見るとよいです。
- gRPCをインストールする
$ go get -u google.golang.org/grpc
- Protocol Buffers v3 の protoc compiler をインストールする
- 以下のURLから、プラットフォームに合わせたコンパイル済みバイナリをダウンロードする
protoc-<version>-<platform>.zip
のようなファイル名になっています
必要なのはprotoc.exe
のみなので、適当にPATHの通った場所に置くなどしてください
- protoc plugin for Go をインストールする
$ go get -u github.com/golang/protobuf/protoc-gen-go
基本的な開発の流れ;
以下の流れで開発を行います。
変更などがある場合は、1~3を繰り返す形となります。
- gRPCのサービスを
*.proto
により定義する - protocにより
*.proto
から*.pb.go
を生成する
*.pb.go
は、自動生成されるソースコードなので編集不要 - サービスを実装する
なお、*.proto
はprotocによりJavaやC#等の各種言語用ソースにコンパイルできるため、serverとclientは異なる言語での記述が可能となります。
作るもの
題材は、gRPCでzipファイルを作成するサービスgrpczip
の作成とします。
- client
以下のように指定することで、aaa.txtとbbb.txtを圧縮してoutput.zipを作成する
$ client.exe output.zip aaa.txt bbb.txt
- server
gRPCによって受け取った引数をzip圧縮し、clientに返す
サービスを定義する
grpczip.proto
を作成します。
Goの作法に従い、今回は%GOPATH%\src\github.com\sago35\grpczip-sample
以下で作業を行います。
以下が作成するサービスの全体像になります。
syntax = "proto3";
package grpczip;
message File {
string Filename = 1;
bytes Data = 2;
}
message Request {
string ZipFilename = 1;
repeated File Files = 2;
}
message Response {
File ZipFile = 1;
}
service Grpczip {
rpc Grpczip (Request) returns (Response) {}
}
上記を作成したら以下のコマンドでgrpczip.pb.zip
を作成します。
この時点では、2ファイルが存在します。
$ protoc --go_out=plugins=grpc:. grpczip.proto
$ tree /f
フォルダー パスの一覧: ボリューム Windows
ボリューム シリアル番号は 0000008E AEC0:D95D です
C:.
grpczip.pb.go
grpczip.proto
サブフォルダーは存在しません
サービスの詳細
以下の部分が、RPCのための関数定義となります。
clientはGrpczip(Reuest)
をコールして、Response
を受け取る事ができます。
service Grpczip {
rpc Grpczip (Request) returns (Response) {}
}
Request
は以下で定義しています。
Request
はZipFilename
とrepeated File
(Fileの配列のようなイメージ)のFiles
を持ちます。
File
は、Filename
とData
(実際のバイナリデータ)を持ちます。
Goの構造体とほぼ同じイメージで定義できるため特に迷うことはないと思います。
構造体の入れ子を作れるので、分かりやすい定義ができていいですね。
string Filename = 1;
の 1
の部分は unique numbered tag
と呼ばれ、バイナリにエンコードした時の識別に使われます。
1~15は1byteにエンコードされるため頻繁に使うフィールドに、16以降は2byte以上にエンコードされるためそれ以外のフィールドに使うべきです。
また、フィールドは追加はOKだが変更や削除はしないほうが良い、というのが基本となっています。
message File {
string Filename = 1;
bytes Data = 2;
}
message Request {
string ZipFileName = 1;
repeated File Files = 2;
}
最後にResponse
を定義します。
message Response {
File ZipFile = 1;
}
*.proto
の記述について、より詳細に知りたい場合は以下を見てください。
Server側を作成する
./cmd/server/server.go
を作成します。
ある程度長くなってしまうのですが、実体は関数Grpczip()
となります。
r.GetFiles()
により、gRPCの引数のFiles
を取り出すことができます。
同様に、r.GetZipFilename()
によりZipFilename
を取り出すことができます。
ここでは、archize/zip
使ってFiles
を圧縮しpb.Response
にセットしています。
package main
import (
"archive/zip"
"bytes"
"log"
"net"
"path/filepath"
pb "github.com/sago35/grpczip-sample"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
type grpcServer struct {
}
func (s *grpcServer) Grpczip(ctx context.Context, r *pb.Request) (*pb.Response, error) {
buf := new(bytes.Buffer)
w := zip.NewWriter(buf)
for _, file := range r.GetFiles() {
f, err := w.Create(filepath.Base(file.GetFilename()))
if err != nil {
return nil, err
}
_, err = f.Write(file.GetData())
if err != nil {
return nil, err
}
}
err := w.Close()
if err != nil {
return nil, err
}
res := &pb.Response{
ZipFile: &pb.File{
Filename: r.GetZipFilename(),
Data: buf.Bytes(),
},
}
return res, nil
}
func main() {
lis, err := net.Listen("tcp", ":11111")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
srv := grpc.NewServer(grpc.MaxMsgSize(0xFFFFFFFF))
pb.RegisterGrpczipServer(srv, &grpcServer{})
srv.Serve(lis)
}
grpc.NewServer()
で作成されるサーバーはデフォルトでは最大4096KBのMessageしか受け付けてくれません。
今回はzipを作成するサービスなので、以下のようにして大きい値で設定しています。
srv := grpc.NewServer(grpc.MaxMsgSize(0xFFFFFFFF))
Client側を作成する
./cmd/client/client.go
を作成します。
またもや結構な長さになってしまうのですが、基本的にはpb.Request
を作成してclient.Grpczip()
をコールしているだけです。
package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"golang.org/x/net/context"
pb "github.com/sago35/grpczip-sample"
"google.golang.org/grpc"
)
func main() {
if len(os.Args) < 2 {
log.Fatalf("usage : %s [zipFilename] [input...]", os.Args[0])
}
zipFilename := os.Args[1]
files := os.Args[2:]
conn, err := grpc.Dial("127.0.0.1:11111", grpc.WithInsecure(), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(0xFFFFFFFF)))
if err != nil {
log.Fatal(err)
}
defer conn.Close()
client := pb.NewGrpczipClient(conn)
req, err := makeRequest(zipFilename, files)
if err != nil {
log.Fatal(err)
}
resp, err := client.Grpczip(context.Background(), req)
if err != nil {
log.Fatal(err)
}
z := resp.GetZipFile()
w, err := os.Create(z.GetFilename())
if err != nil {
log.Fatal(err)
}
defer w.Close()
r := bytes.NewReader(z.GetData())
size, err := io.Copy(w, r)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s (%d bytes)\n", z.GetFilename(), size)
}
func makeRequest(zipFilename string, files []string) (*pb.Request, error) {
ret := &pb.Request{
ZipFilename: zipFilename,
Files: nil,
}
for _, file := range files {
content, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
ret.Files = append(ret.Files, &pb.File{
Filename: file,
Data: content,
})
}
return ret, nil
}
grpc.WithInsecure()
は、grpc: no transport security set
というエラーへの対策です。
grpc.MaxCallRecvMsgSize(0xFFFFFFFF)
は、Responseサイズの上限を4096KBから増やすための設定になります。
conn, err := grpc.Dial("127.0.0.1:11111", grpc.WithInsecure(), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(0xFFFFFFFF)))
実行してみる
Buildは以下の通りです。
$ go build ./cmd/server
$ go build ./cmd/client
この時点で、以下のようなフォルダ構成となります。
$ tree /f
フォルダー パスの一覧: ボリューム Windows
ボリューム シリアル番号は 0000000B AEC0:D95D です
C:.
│ client.exe
│ grpczip.pb.go
│ grpczip.proto
│ server.exe
│
└─cmd
├─client
│ client.go
│
└─server
server.go
先に、Serverを立ち上げておき、
$ server
Clientを実行すると、zipファイルが作成されます。
正しくzipファイルが作成されました。
$ client grpczip.proto.zip grpczip.proto
grpczip.proto.zip (307 bytes)
$ client output.zip grpczip.pb.go grpczip.proto server.exe client.exe
output.zip (7071632 bytes)
まとめ
Go + gRPCの簡単なサンプルを作成しました。
それなりのコード量を書かないといけないのですが、それほど難しい内容ではないので写経等のトライをするとすぐに理解できると思います。
まだgRPCを触っていない人は、是非試してみてください。
今回のコードは以下にあります。