Fukuoka.go #13 by Yuichi Watanabe @nulab
gRPCは複数の言語をサポートしているため、言語によってはリポジトリ自体を分けて作られている。
多くの言語はCで実装されたコアライブラリをラップした形で提供されている。(github.com/grpc/grpc)
Go版やJava版はコアライブラリ自体は使用言語そのもので実装されている。(github.com/grpc/grpc-go, grpc-java)
Go版と他の言語のフレームワークのスタックの違いを比較してみる。
Cコアライブラリをラップした言語
参考: Visualizing gRPC Language Stacks
https://grpc.io/blog/grpc-stacks
gRPCのGo実装
参考: Visualizing gRPC Language Stacks
https://grpc.io/blog/grpc-stacks
// file.proto
syntax = "proto3";
package file;
message FileRequest {
string name = 1;
}
message FileResponse {
bytes data = 1;
}
service FileService {
rpc Download (FileRequest) returns (stream FileResponse);
}
$ protoc -I ./proto --go_out=plugins=grpc:./proto/ ./proto/file.proto
type FileServiceServer interface {
Download(*FileRequest, FileService_DownloadServer) error
}
略
type FileService_DownloadServer interface {
Send(*FileResponse) error
grpc.ServerStream
}
type FileService struct{}
func (s FileService) Download(req *pb.FileRequest,
stream pb.FileService_DownloadServer) error {
fs, _ := os.Open(filepath.Join("./resource", req.Name))
defer fs.Close()
buf := make([]byte, 1000*1024)
for {
n, err := fs.Read(buf)
if err != nil {
if err != io.EOF {
return err
}
break
}
err = stream.Send(&pb.FileResponse{
Data: buf[:n],
})
if err != nil {
return err
}
}
return nil
}
package main
import (
"log"
"net"
"google.golang.org/grpc"
pb "example.com/server_stream/proto"
)
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterFileServiceServer(s, &FileService)
log.Println("start server on port :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
略
"google.golang.org/grpc"
pb "example.com/server_stream/proto"
)
func main() {
conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
defer conn.Close()
c := pb.NewFileServiceClient(conn)
name := os.Args[1]
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
stream, _ := c.Download(ctx, &pb.FileRequest{Name: name})
var blob []byte
for {
c, err := stream.Recv()
if err != nil {
if err == io.EOF {
break
}
panic(err)
}
blob = append(blob, c.Data...)
}
ioutil.WriteFile(name, blob, 0644)
}
できればGoのプログラムからGitのローレベルなAPIを使って少ないオーバーヘッドでGitリポジトリを操作したい。
リモートリポジトリはワークツリーを持たないBaraリポジトリとして展開されている。
そのため、Gitコマンドが提供する一部の機能が使えない。例えば`git merge`等。
正直GitのローレベルなAPIをフルスクラッチで実装するのは、それだけで大きなプロジェクトになってしまう。
_人人人_
> cgo <
 ̄Y^Y^Y^ ̄
modulesライブラリのダウンロード先は`$GOPAPTH/pkg/mod`以下。
それに無理矢理手を加えるの現実的ではない。
そもそもダウンロードされたときに.gitディレクトリは存在しないので、Gitの機能であるサブモジュールを使ってlibgit2をダウンロードできない。
modulesにはreplaceディレクティブなるものがあった。
これは指定したパッケージのみローカルやフォーク先に向けることができる。
`replace example.com/project/foo => ./foo`
これで参照先を変えることができるはず
git-rpc-serverのリポジトリ直下にビルドしたlibgi2を同梱したgit2goを用意する。
そして、replaceディレクティブでgit2goの向き先を変えることで成功。
module example.com/vvatanabe/git-rpc-server
require (
github.com/golang/protobuf v1.2.0
google.golang.org/grpc v1.17.0
gopkg.in/libgit2/git2go.v27 v27.0.0-20190104134018-ecaeb7a21d47
)
replace gopkg.in/libgit2/git2go.v27 v27.0.0-20190104134018-ecaeb7a21d47 => ./local/git2go