はじめに
AbemaTV Advent Calendar 2017 12日目の記事です。
最近、個人的にgRPCについて調査する機会があったので、自分への勉強の意味も込めて記事にしたいと思います。
ちなみに、この内容はこちらのLTで発表した物になります。
gRPCとは
gRPCとはRemoteProcedureCallの一つで、ClientとServer間でProtocolBuffersを使って通信します。
HTTP/2を使用するため、双方向ストリーミング、フロー制御、ヘッダー圧縮、多重リクエストなどHTTP/2の特徴を活かすことができます。
また、Protoファイルと呼ばれる通信をする上でのMessage(型)やService(メソッド)の定義ファイルを、Server側とClinet側で共通して使うことになるため、お互いが共通の認識を持って通信を行うことができます。
公式サイトはこちら
手順
- Protoファイルの定義
- Protoファイルからシリアライズ/デシリアライズ用のJavaファイルを生成する
- AndroidでgRPC通信を行う
Protoファイルの定義
まずはProtoファイルの定義を行います。
Protoファイルは、ClinentとServerがどのように通信をするのかを定義するためのファイルです。
まずはMessageと呼ばれるProtocolBuffersで通信をする上での型を定義します。
↓はリクエスト時とレスポンス時に使用する型をProtoファイルに定義しています。
StringのNameを持ったCoffeeRequestという型でRequestを送り、レスポンスとして、price, name, messageを持ったCoffeeResponseとして受け取っています。
プロパティに割り振られている数字はタグのようなものになります。ユニークなnumberを割り当てます。
message CoffeeRequest {
string name = 1;
}
message CoffeeResponse {
int32 price = 1;
string name = 2;
string message = 3;
}
次にgRPC通信を行うために必要なServiceというものを定義します。ここに定義したServiceを通してClientとServerがやりとりします。
ここでは、OrderというServiceを定義しました。
Orderの引数には先程のCoffeeRequest
を指定し、レスポンスとして、CoffeeResponse
を受け取ります。
service Coffee {
rpc Order (CoffeeRequest) returns (CoffeeResponse) {}
}
全体で見ると↓のようになると思います。
syntax = "proto3";
option java_package = "com.takusemba.grpc.android.protos";
option go_package = "protos";
package Coffee;
message CoffeeRequest {
string name = 1;
}
message CoffeeResult {
string message = 1;
}
service Coffee {
rpc Order (CoffeeRequest) returns (CoffeeResult) {}
}
Serviceの種類
ここでServiceの種類について紹介します。
Unary RPCs
service Coffee {
rpc Order (CoffeeRequest) returns (CoffeeResponse) {}
}
先程定義したServiceはUnary RPCs
と呼ばれるServiceでClientが一つリクエストを送るとServer側が一つレスポンスを返す基本的な形です。
Server streaming RPCs その1
service Coffee {
rpc Order (stream CoffeeRequest) returns (CoffeeResponse) {}
}
Orderの引数にstreamというものがつきました。これは、Server streaming RPCs
と呼ばれるもので、Clientが複数のリクエストを送ることができ、Server側は全てのリクエストを受け取り、一つのレスポンスを返すことができます。
Server streaming RPCs その2
service Coffee {
rpc Order (CoffeeRequest) returns (stream CoffeeResponse) {}
}
また、レスポンス側にもstreamをつけることができます。 この場合は、クライアントが一つのリクエストを送るのに対して、server側は複数のレスポンスを送ることができます。このサーバーが複数のレスポンスを返却するような形を利用すると、サーバープッシュのような、あるイベントをclientが購読するような仕組みを作ることができます。
Bidirectional streaming RPCs
service Coffee {
rpc Order (stream CoffeeRequest) returns (stream CoffeeResponse) {}
}
また、リクエスト側とレスポンス側の両方にもstreamをつけることができます。これは、Bidirectional streaming RPCs
と呼ばれています。server側はclientが全てリクエストを送りきったあとにレスポンスを送ることもできますし、severとclientが交互にメッセージを送り合うこともできます。
Protoファイルからシリアライズ/デシリアライズ用のJavaファイルを生成する
今度は、先程定義したProtoファイルからシリアライズ/デシリアライズ用のJavaファイルを生成します。この生成されたファイルを使用して、ClientとServerが通信することになります。
protocコマンド
protocコマンドとは、ProtocolBuffers通信を行う部分のファイルを生成してくれます。
Protoファイルで言うとMessageの部分です。
まずは、protocコマンドが使える状態にします。
curl -OL https://github.com/google/protobuf/releases/download/v3.5.0/protobuf-java-3.5.0.tar.gz
tar -zxvf protobuf-java-3.5.0.tar.gz
cd checkout -b protobuf-3.5.0 origin/protobuf-3.5.0
./configure
make
sudo make install
protoc —version // -> libprotoc 3.5.0 :)
protoc-gen-grpc-javaプラグイン
Protoファイルで定義したService層のファイルを生成するためにはこのprotoc-gen-grpc-java
プラグインが必要になります。
git clone git@github.com:grpc/grpc-java.git
cd grpc-java
git checkout -b v1.7.0
cd compiler
make
../gradlew java_pluginExecutable
cp build/exe/java_plugin/protoc-gen-grpc-java /usr/local/bin
Javaファイルの生成
protoc
とprotoc-gen-grpc-java
が使えるようになると、以下のコマンドを利用して、Javaファイルを生成することができます。
protoc coffee.proto --grpc-java_out=lite:. --java_out=. --plugin=protoc-gen-grpc-java=/usr/local/bin/protoc-gen-grpc-java
するとこののようなファイルが生成されていると思います。
AndroidでgRPC通信を行う
最後にAndroidでgRPC通信を実装していきたいと思います。
まず、build.gradleを編集します。
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.2'
implementation "io.grpc:grpc-okhttp:1.7.0"
implementation "io.grpc:grpc-protobuf:1.7.0"
implementation "io.grpc:grpc-stub:1.7.0"
implementation "javax.annotation:javax.annotation-api:1.2"
app/build.gradle
で追加するio.grpc:grpc-xxx
のバージョンは先程利用した、protoc-gen-grpc-java
と同じバージョンで揃える必要があります。
次に通信部分の実装について見ていきます。
val channel = ManagedChannelBuilder.forAddress("10.0.2.2", 8080)
.usePlaintext(true)
.build()
val stub = CoffeeGrpc.newBlockingStub(channel)
val request = CoffeeOuterClass.CoffeeRequest.newBuilder()
.setName("hot coffee")
.build()
Single
.create<CoffeeOuterClass.CoffeeResponse> {
it.onSuccess(stub.order(request))
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ /** do something */ },
{ /** error handling */ }
)
まず、ipアドレスとportを指定して、channel
を生成しています。ここでは、エミュレータからlocalhostアクセスするため、portに10.0.2.2
を指定しています。
Stubには、non-blocking
または、blocking
なものなど複数種類があるのですが、ここでは、単純にblockingStub
を生成しています。
詳しくは公式のサイトを御覧ください。
実際にリクエストする部分では、RxのSingleを使って、order()
を呼び出し、onNextで結果を受け取っています。
さいごに
今回、gRPCで通信をするまでの流れをざっと書いてきましたが、今回の説明で使った例を使ったサンプルがありますので、よかったら見てみてください。
Clientサイド(java): https://github.com/TakuSemba/grpc-android
Serverサイド(Go): https://github.com/TakuSemba/grpc-go
Protoファイル: https://github.com/TakuSemba/grpc-proto