- 投稿日:2021-03-04T14:52:33+09:00
Go: Synology の WebDAV にアクセスする
こちらのライブラリーを使いました。
studio-b12/gowebdavフォルダーの一覧
webdav_list.go// --------------------------------------------------------------- // // webdav_list.go // // Mar/04/2021 // --------------------------------------------------------------- package main import ( "fmt" "os" "github.com/studio-b12/gowebdav" ) // --------------------------------------------------------------- func main() { fmt.Fprintf (os.Stderr,"*** 開始 ***\n") server := "https://example.synology.me:5006" user := "scott" password := "secret" cc := gowebdav.NewClient(server, user, password) files, _ := cc.ReadDir("/homes/scott/tmp") for _, file := range files { fmt.Println(file.Name()) } fmt.Fprintf (os.Stderr,"*** 終了 ***\n") } // ---------------------------------------------------------------ファイルのアップロード
webdav_put.go// --------------------------------------------------------------- // // webdav_put.go // // Mar/04/2021 // --------------------------------------------------------------- package main import ( "fmt" "os" "github.com/studio-b12/gowebdav" ) // --------------------------------------------------------------- func main() { fmt.Fprintf (os.Stderr,"*** 開始 ***\n") server := "https://example.synology.me:5006" user := "scott" password := "secret" cc := gowebdav.NewClient(server, user, password) webdavFilePath := "/homes/scott/tmp/tmp02.txt" localFilePath := "./tmp02.txt" file, _ := os.Open(localFilePath) defer file.Close() cc.WriteStream(webdavFilePath, file, 0644) fmt.Fprintf (os.Stderr,"*** 終了 ***\n") } // ---------------------------------------------------------------ファイルのダウンロード
webdav_get.go// --------------------------------------------------------------- // // webdav_get.go // // Mar/04/2021 // --------------------------------------------------------------- package main import ( "fmt" "os" "io" "github.com/studio-b12/gowebdav" ) // --------------------------------------------------------------- func main() { fmt.Fprintf (os.Stderr,"*** 開始 ***\n") server := "https://example.synology.me:5006" user := "scott" password := "secret" cc := gowebdav.NewClient(server, user, password) webdavFilePath := "/homes/scott/tmp/tmp01.txt" localFilePath := "./tmp01.txt" reader, _ := cc.ReadStream(webdavFilePath) file, _ := os.Create(localFilePath) defer file.Close() io.Copy(file, reader) fmt.Fprintf (os.Stderr,"*** 終了 ***\n") } // ---------------------------------------------------------------実行方法についてはこちら
https://qiita.com/ekzemplaro/items/0781f7d242cdda52eb87次のファイルが作成されます。
go.mod go.sum
- 投稿日:2021-03-04T10:38:55+09:00
go mod の使い方
こちらの記事と同様のことを行ってみました。
GOPATH モードからモジュール対応モードへ移行せよgo mod tidy を実行するのが異なります。
使用したバージョン
$ go version go version go1.16 linux/amd64テスト用のプログラム
hello.gopackage main import ( "fmt" "rsc.io/quote" ) func main() { fmt.Println(quote.Hello()) }この時点で実行すると次のようになります。
$ go run hello.go hello.go:6:5: no required module provides package rsc.io/quote: working directory is not part of a modulehello モジュールの作成
go mod init hello go mod tidy実行時の様子
$ go mod init hello go: creating new go.mod: module hello go: to add module requirements and sums: go mod tidy $ go mod tidy go: finding module for package rsc.io/quote go: found rsc.io/quote in rsc.io/quote v1.5.2次のファイルが作成されます。
go.mod go.sumプログラムの実行
$ go run hello.go Hello, world.
- 投稿日:2021-03-04T03:16:27+09:00
[gRPC-Gateway, Envoy] gRPCとRESTを繋げたい時に...
前書き
今回の記事ではgRPCについての基礎的な説明とProtocol Buffersの記述方法にはあまり触れません。以前にGo言語+gRPCをはじめから丁寧に解説してみた[ハンズオン]という記事を作成したのでそちらを踏まえた上で本記事を閲覧してもらえると助かります。
この記事の内容
今回はGolangにおいてgRPCサーバにREST形式でアクセスしたい時のアプローチについて説明します。
この記事を読むことにより、
- gRPC-Gatewayを使ってアクセスする方法
- EnvoyのgRPC-JSON-transcoderフィルタを使ってアクセスする方法
以上二つのアプローチの概観を理解することができます。
早速取り掛かりましょう!
gRPC-Gateway
アーキテクチャ図
上図を見ると、アクセスしたいgRPCサービスにリバースプロキシ的な役割を持つサーバーをgrpc-gatewayが作成することでAPIクライアントからREST形式で繋げるようにしています。
また、あくまで左側のprotoファイルからスタブを生成していることがわかります。
実装
今回は私が作成した前回の記事で使用したサンプルリポジトリをベースに変更していきます。
サンプルリポジトリ ⬇︎
また、完成系は別途リポジトリを分けて作成しており、以下のリポジトリのgrpc-gatewayブランチを参考にしていただけると幸いです。
フォルダ構成
今回のフォルダ構成は以下になっています。
. └── grpc_tutorial ├── Makefile ├── chat │ ├── chat.go │ ├── chat.pb.go │ ├── chat.pb.gw.go │ └── chat_grpc.pb.go ├── chat.proto ├── cmd │ ├── gateway │ │ └── main.go │ └── server │ └── main.go ├── go.mod └── go.sum 5 directories, 10 files
プラグインのインストール
$ go get -u -v github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway $ go get -u -v github.com/golang/protobuf/protoc-gen-go今回インストールはこれで終了です。
Makefileの作成
protocコマンドをが長くて見にくいので筆者はよくMakefileにコマンドを貼り付けています。
protoc: protoc -I/usr/local/include -I.\ -I/usr/local/opt/protobuf/include \ -I$(GOPATH)/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@v1.16.0/third_party/googleapis --plugin=protoc-gen-grpc-gateway=$(GOPATH)/bin/protoc-gen-grpc-gateway --grpc-gateway_out=logtostderr=true:./chat ./chat.proto \ && protoc -I/usr/local/include -I. \ -I$(GOPATH)/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@v1.16.0/third_party/googleapis \ -I/usr/local/opt/protobuf/include \ --go_out=./chat --go-grpc_out=./chat ./chat.protoこちら筆者が行った時はv1.16.0だったのですが、違う場合があります。その場合は自分のバージョンを調べて適宜修正ください。(恐らくmodule not found が出ると思われます。)
コマンドの実行
$ make protoc
こちらのコマンドでchatディレクトリ内にchat_grpc.pb.go, chat_pb.go, chat.pb.gw.goが生成されたかと思います。もしエラーが発生するようでしたら、フォルダ構成の見直し、gopathの見直しを行ってください。
chat.protoの編集
chat.proto
syntax = "proto3"; package chat; import "google/api/annotations.proto"; service ChatService { rpc SayHello(MessageRequest) returns (MessageResponse) { option (google.api.http) = { get: "/hello" }; } } message MessageRequest { string body = 1; } message MessageResponse { string body = 1; }こちらはgatewayとは関係ないですが、一般的にリクエストとレスポンスの型を作成することが多いため、変更しました。
import "google/api/annotations.proto"; option (google.api.http) = { get: "/hello" };gatewayに関わるコードは上記です。詳細の動きを追いたい方はこちらをご覧ください
gatewayサーバーの実装
gateway/main.go
package main import ( "flag" "fmt" "net/http" "github.com/golang/glog" "github.com/grpc-ecosystem/grpc-gateway/runtime" "golang.org/x/net/context" "google.golang.org/grpc" "grpc-intro/chat" ) func run() error { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() mux := runtime.NewServeMux() opts := []grpc.DialOption{grpc.WithInsecure()} endpoint := fmt.Sprintf("localhost:9000") err := chat.RegisterChatServiceHandlerFromEndpoint(ctx, mux, endpoint, opts) if err != nil { return err } return http.ListenAndServe(":5000", mux) } func main() { flag.Parse() defer glog.Flush() if err := run(); err != nil { glog.Fatal(err) } }5000ポートでAPIリクエストを受け取り、9000ポートのgrpcサーバーに渡す処理を書いています。
err := chat.RegisterChatServiceHandlerFromEndpoint(ctx, mux, endpoint, opts)この部分が定義したサービスによって変わってきます。
実装完了です!
試してみます。
go run cmd/server/main.go //Go gRPC Beginners Tutorial! // 別ターミナル go run cmd/gateway/main.go //別ターミナル curl localhost:5000/hello // {"body":"Hello From the Server!"}動作確認成功です!
これにてgrpc-gatewayの基本的な使い方は完了です!
Envoy(gRPC-JSON-transcoder)
さて、二つ目のアプローチです。こちらはEnvoyのgRPC-JSON-transcoderというHTTPフィルターを使用します。
↓完成形です。
先ほどのgrpc-gatewayのブランチと比較した変更点はこちらになっています。
フォルダ構造を表示します。
. ├── Makefile ├── chat │ ├── chat.go │ ├── chat.pb.go │ └── chat_grpc.pb.go ├── chat.proto ├── cmd │ └── server │ └── main.go ├── docker-compose.yaml ├── envoy │ ├── envoy.yaml │ └── proto.pb ├── go.mod └── go.sum 4 directories, 11 files
大きく異なる点はenvoyフォルダがあることと、gatewayフォルダがなくなっていることですね。
それでは変更ファイルをみていきます。
Makefile
Makefile
envoy_protoc: protoc -I/usr/local/include -I. -I/usr/local/opt/protobuf/include -I$(GOPATH)/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@v1.16.0/third_party/googleapis \ --include_imports \ --include_source_info \ --descriptor_set_out=./envoy/proto.pb \ ./chat.protoenvoyが読み取るためのproto.pbバイナリを作成してenvoyフォルダに設置します。
バージョンが違う恐れがあるので適宜修正ください。
$ make envoy_protoc
ここでエラーが発生したらバージョン、gopathを確認お願いします。
docker-compose.yaml
docker-compose.yaml
version: "3" services: envoy: image: envoyproxy/envoy:v1.13.1 volumes: - "./envoy:/etc/envoy" expose: - 51051 ports: - 51051:51051envoyのイメージを使います。51051ポートがデフォルトのようなので今回はそのままにしました。
volumesでenvoyフォルダをマウントしています。
envoy.yaml
envoy/envoy.yaml
admin: access_log_path: /tmp/admin_access.log address: socket_address: { address: 0.0.0.0, port_value: 9901 } static_resources: listeners: - name: listener1 address: socket_address: { address: 0.0.0.0, port_value: 51051 } filter_chains: - filters: - name: envoy.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager stat_prefix: grpc_json codec_type: AUTO route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: # NOTE: by default, matching happens based on the gRPC route, and not on the incoming request path. # Reference: https://www.envoyproxy.io/docs/envoy/latest/configuration/http_filters/grpc_json_transcoder_filter#route-configs-for-transcoded-requests - match: { prefix: "/" } route: { cluster: grpc, timeout: { seconds: 60 } } http_filters: - name: envoy.grpc_json_transcoder config: proto_descriptor: "/etc/envoy/proto.pb" services: ["chat.ChatService"] print_options: add_whitespace: true always_print_primitive_fields: true always_print_enums_as_ints: false preserve_proto_field_names: false - name: envoy.router clusters: - name: grpc connect_timeout: 1.25s type: logical_dns lb_policy: round_robin dns_lookup_family: V4_ONLY http2_protocol_options: {} load_assignment: cluster_name: grpc endpoints: - lb_endpoints: - endpoint: address: socket_address: # WARNING: "docker.for.mac.localhost" has been deprecated from Docker v18.03.0. # If you're running an older version of Docker, please use "docker.for.mac.localhost" instead. # Reference: https://docs.docker.com/docker-for-mac/release-notes/#docker-community-edition-18030-ce-mac59-2018-03-26 address: 192.168.xxx.xxx port_value: 50051この設定は公式の設定からほぼ変えていません。
proto_descriptor: "/etc/envoy/proto.pb" services: ["chat.ChatService"]こちらの部分と、アドレス部分を変更しました。
address: 192.168.xxx.xxx port_value: 50051addressは今回goをローカル環境で動かしているため、自分のpcのアドレスを指定してあげる必要があります。
ipconfigなどのコマンドを打てばわかるので各自確認して追加してください。
サーバーポートの変更
envoyの設定を変えればいいのですが、server.goを一行編集すればいいかと思い、ここは楽をしてしまいました。
package main import ( "fmt" "log" "net" "grpc-intro/chat" "google.golang.org/grpc" ) func main() { fmt.Println("Go gRPC Beginners Tutorial!") lis, err := net.Listen("tcp", fmt.Sprintf(":%d", 51051)) // ここだけ変える if err != nil { log.Fatalf("failed to listen: %v", err) } s := chat.Server{} grpcServer := grpc.NewServer() chat.RegisterChatServiceServer(grpcServer, &s) if err := grpcServer.Serve(lis); err != nil { log.Fatalf("failed to serve: %s", err) } }これでポートが変わりました。
実行
$ docker-compose up -d $ go run cmd/server/main.go // ターミナル変える $ curl localhost:51051/hello // {"body":"Hello From the Server!"}完成です!
最後に
ここまででgRPCとRESTをつなげる方法を二つ紹介しました。grpc-gatewayはgolangのみのサポートですが,envoyを使った方法は異なる言語でも活用できるので柔軟です。
ZOZOの導入事例の記事を引用するとgrpc-gatewayがあります。こちらもHTTP JSON APIリクエストをgRPCに変換して背後にあるgRPCサーバーへプロキシするものです。以下の図にgrpc-gatewayを利用する構成の例を示します。gRPCとHTTP JSON APIでリクエストの経路を分離する場合やEnvoyではないコンポーネントを使う場合は良いかもしれません。一方ZOZOMATシステムの場合、両者とも同じ経路となるためEnvoyを利用、かつgRPC-JSON transcoderを有効にした構成となっています。
と書かれています。Envoyを使っている場合はそのままフィルタ機能を使った方が良さそうです。
もしもこの記事に不明瞭な点がありましたら気軽にコメントをお願いします。
ここまで読んでくださりありがとうございました!
参考文献