LoginSignup
2
0

More than 1 year has passed since last update.

grpcurl をコードリーディング

Last updated at Posted at 2022-12-22

こんにちは、しゅん(@MxShun)です。
今年11月にサイバーエージェントに中途入社、AI事業本部オペレーションテクノロジーに仲間入りしました。

<宣伝>

👉 同じく本日投稿の、CyberAgent Advent Calendar 2022「私がやった Amazon S3 コストカット全ステップ」よければご覧ください!
👉 社内の方は、#times_mxshun よければ覗いてあげてください!

</宣伝>

grpcurl

gRPC 界隈では言わずと知れた、gRPC サーバに対して cURL できるコマンドラインツールです。

grpcurl is a command-line tool that lets you interact with gRPC servers. It's basically curl for gRPC servers.

コードリーディング

バージョンは v1.8.7 です。

main.grpcurl

処理の冒頭は、コマンドラインツールの所謂バリデーション処理です。読み飛ばします。

最初に着目すべきは、grpcurl.BlockingDial呼び出している箇所です。つまり、ここで gRPC サーバとのコネクションを確立しています。

cc, err := grpcurl.BlockingDial(ctx, network, target, creds, opts...)
grpcurl/grpcurl.go
// BlockingDial is a helper method to dial the given address, using optional TLS credentials,
// and blocking until the returned connection is ready. If the given credentials are nil, the
// connection will be insecure (plain-text).
func BlockingDial(ctx context.Context, network, address string, creds credentials.TransportCredentials, opts ...grpc.DialOption) (*grpc.ClientConn, error)

少し戻って ctx を追うと、タイムアウトだけコンテキストに詰めていました

ctx := context.Background()
// 中略
ctx, cancel = context.WithTimeout(ctx, timeout)

DescriptorSource は、ProtoSets もしくは ProtoFiles、リフレクションに対応しているのであれば gRPC サーバからの情報をもとに生成します。

if len(protoset) > 0 {
    // 中略
    fileSource, err = grpcurl.DescriptorSourceFromProtoSets(protoset...)
    // 中略
} else if len(protoFiles) > 0 {
    // 中略
    fileSource, err = grpcurl.DescriptorSourceFromProtoFiles(importPaths, protoFiles...)
    // 中略
}
if reflection.val {
    md := grpcurl.MetadataFromHeaders(append(addlHeaders, reflHeaders...))
    refCtx := metadata.NewOutgoingContext(ctx, md)
    cc = dial()
    refClient = grpcreflect.NewClient(refCtx, reflectpb.NewServerReflectionClient(cc))
    reflSource := grpcurl.DescriptorSourceFromServer(ctx, refClient)
    if fileSource != nil {
        descSource = compositeSource{reflSource, fileSource}
    } else {
        descSource = reflSource
    }
} else {
    descSource = fileSource
}

そして、grpcurl.InvokeRPC実際に RPC しています
cc には DescriptorSource でこそっと dial() を代入しており、ch として確立されたチャネルが渡されています。

err = grpcurl.InvokeRPC(ctx, descSource, cc, symbol, append(addlHeaders, rpcHeaders...), h, rf.Next)
grpcurl/invoke.go
// InvokeRPC uses the given gRPC channel to invoke the given method. The given descriptor source
// is used to determine the type of method and the type of request and response message. The given
// headers are sent as request metadata. Methods on the given event handler are called as the
// invocation proceeds.
//
// The given requestData function supplies the actual data to send. It should return io.EOF when
// there is no more request data. If the method being invoked is a unary or server-streaming RPC
// (e.g. exactly one request message) and there is no request data (e.g. the first invocation of
// the function returns io.EOF), then an empty request message is sent.
//
// If the requestData function and the given event handler coordinate or share any state, they should
// be thread-safe. This is because the requestData function may be called from a different goroutine
// than the one invoking event callbacks. (This only happens for bi-directional streaming RPCs, where
// one goroutine sends request messages and another consumes the response messages).
func InvokeRPC(ctx context.Context, source DescriptorSource, ch grpcdynamic.Channel, methodName string,
	headers []string, handler InvocationEventHandler, requestData RequestSupplier)

grpcurl.InvokeRPC に直接的な戻り値はないですが、EventHandler を通じてレスポンスやステータスコードが返却されるようです。そして、EventHandler に乗っかってきた値をもとにクライアントに結果を返却しています。

意外とシンプルな作りでした。

grpcurl.grpcurl

main.grpcurl はコマンドラインツール機能を提供しますが、grpcurl.grpcurl は grpcurl のコア機能を提供しています。

// Package grpcurl provides the core functionality exposed by the grpcurl command, for
// dynamically connecting to a server, using the reflection service to inspect the server,
// and invoking RPCs. The grpcurl command-line tool constructs a DescriptorSource, based
// on the command-line parameters, and supplies an InvocationEventHandler to supply request
// data (which can come from command-line args or the process's stdin) and to log the
// events (to the process's stdout).

API ドキュメント的に一部を抜粋してまとめてみます。

戻り値型 メソッド 説明
[]string, error ListServices(source DescriptorSource) DescriptorSource をもとに gRPC サービス一覧を取得します。
[]*desc.FileDescriptor, error GetAllFiles(source DescriptorSource) DescriptorSource からファイルの descriptor 一覧を取得します。
int (f filesByName) Len() []*desc.FileDescriptor に対して sort.Interface を満たすための Len を提供します。
bool (f filesByName) Less(i, j int) []*desc.FileDescriptor に対して sort.Interface を満たすための Less を提供します。
- (f filesByName) Swap(i, j int) []*desc.FileDescriptor に対して sort.Interface を満たすための Swap を提供します。
[]string, error ListMethods(source DescriptorSource, serviceName string) DescriptorSource とサービス名をもとに提供されるメソッド一覧を取得します。
metadata.MD MetadataFromHeaders(headers []string) "Header-Name: Header-Value" というキーバリュー形式のヘッダーをメタデータに変換します。
[]string, error ExpandHeaders(headers []string) ヘッダーに含まれる環境変数を展開します。
string MetadataToString(md metadata.MD) メタデータを標準出力用テキストに変換します。
string, error GetDescriptorText(dsc desc.Descriptor, _ DescriptorSource) Descriptor が提供するものをスニペットとしてテキスト形式で取得します。
proto.Message EnsureExtensions(source DescriptorSource, msg proto.Message) 渡した proto.Message に対し DescriptorSource が提供する gRPC サーバ拡張機能を加味した動的メッセ―ジとして取得する。
error fetchAllExtensions(source DescriptorSource, ext *dynamic.ExtensionRegistry, md *desc.MessageDescriptor, alreadyFetched map[string]bool) gRPC サーバ拡張機能一覧を ext に取得します。
proto.Message MakeTemplate(md *desc.MessageDescriptor) MessageDescriptor に適した JSON 形式の proto.Message を返却します。
credentials.TransportCredentials, error ClientTransportCredentials(insecureSkipVerify bool, cacertFile, clientCertFile, clientKeyFile string) 与えられたプロパティをもとに TLS 設定を構築し、gRPC クライアントのTransportCredentials を生成・返却する。
*tls.Config, error ClientTLSConfig(insecureSkipVerify bool, cacertFile, clientCertFile, clientKeyFile string) gRPC クライアントの TLS 設定を生成する。
credentials.TransportCredentials, error ServerTransportCredentials(cacertFile, serverCertFile, serverKeyFile string, requireClientCerts bool) 与えられたプロパティをもとに TLS 設定を構築し、gRPC サーバのTransportCredentials を生成・返却する。
*grpc.ClientConn, error BlockingDial(ctx context.Context, network, address string, creds credentials.TransportCredentials, opts ...grpc.DialOption) TLS Credentials をもとに確率したコネクションを返却する。

所感

gRPC クライアントを知ることで、gRPC そのものの仕組みも再確認できました。

また興味本位でコードリーディングする機会があれば記事にしたいと思います。それではよいクリスマスを!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0