tarpcとは
tarpcはRustでRPCを書くためのフレームワークで、Googleのオープンソースプロジェクトです。
公式には以下のように書いてあります。
tarpc is an RPC framework for rust with a focus on ease of use. Defining a service can be done in just a few lines of code, and most of the boilerplate of writing a server is taken care of for you.
簡単にサービス定義がかけることを重視しているようです。
また、
Disclaimer: This is not an official Google product.
とあるように、Googleが会社として開発しているわけではなく、Googleの1従業員が20%ルールとか、趣味とかで作ったものをGoogleの許可を得て公開しているものと思われます。
どんな感じで使うのか
以下に完全なサンプルがあるのでこれをベースにすれば簡単にできそうです。
注意:
このexample-service
というサンプルでは、lib.rs
にサービス定義が書いてあるんですが、これをservice
という名前空間にセットしています。
なので、client.rs
にあるservice::WorldClient
というのはlib.rsに書かれたWorldClient
(#[tarpc::service]
により自動生成)のことを意味しています。
サーバーのほうにはWorldServer
みたいなのは自動実装されません。
サービス定義
サービス定義は以下のような感じです。通常traitではasyncなfunctionは定義出来ない(async-traitを使う)ので、#[tarpc::service]
がいい感じに中身を展開してサービス定義にしてくれているようです。
#[tarpc::service]
pub trait World {
/// Returns a greeting for name.
async fn hello(name: String) -> String;
}
サービスの実装(サーバー側)
サービスは以下のような感じで実装します。README.mdにあるサンプルより簡単になっています。特に返り値がFuture
じゃなくString
になっているあたり進化していますね。
#[derive(Clone)]
struct HelloServer(SocketAddr);
#[tarpc::server]
impl World for HelloServer {
async fn hello(self, _: context::Context, name: String) -> String {
format!("Hello, {}! You are connected from {:?}.", name, self.0)
}
}
サンプルなので、引数と接続情報を返す意味のない関数になっています。
各バイナリの実装
あとは上記サービス定義を使ってサービスの実行ファイルを作ればよいです。
クライアント
trapc::serde_transport::tcp
にRustではおなじみのserdeによるシリアライゼーションを利用したtcp通信が用意されています。
let mut transport = tarpc::serde_transport::tcp::connect(server_addr, Json::default);
transport.config_mut().max_frame_length(4294967296);
let mut client =
service::WorldClient::new(client::Config::default(), transport.await?).spawn()?;
let hello = client.hello(context::current(), name).await?;
サーバー
こちらはクライントに比べるとちょっと複雑です、というか用意されているサンプルが最小限ではなくいろんなオプションを指定しています。
let mut listener = tarpc::serde_transport::tcp::listen(&server_addr, Json::default).await?;
listener.config_mut().max_frame_length(4294967296);
listener
.filter_map(|r| future::ready(r.ok()))
.map(server::BaseChannel::with_defaults)
.max_channels_per_key(1, |t| t.as_ref().peer_addr().unwrap().ip())
.map(|channel| {
let server = HelloServer(channel.as_ref().as_ref().peer_addr().unwrap());
channel.respond_with(server.serve()).execute()
})
.buffer_unordered(10)
.for_each(|_| async {})
.await;
実行してみる
ビルド
gh repo clone google/tarpc
cd tarpc/example-service
cargo build --release
サーバーの実行
../target/release/server --port 12345
クライントの実行
$ ../target/release/client --name OTL --server_addr 127.0.0.1:12345
Hello, OTL! You are connected from 127.0.0.1:61610.
その他のサンプル
example-service以外にもtarpc/examples
にいろんなサンプルが用意されていますので一読の価値ありです。
終わりに
async-trait
と同時に使えると面白いなー、と思って調査を始めたのですが、関数名、trait名がconflictしてしまうので不可能そうでした。
Rustだとserdeでシリアライズの問題がほぼ完全にとけているのでこういうフレームワークもマクロを利用して簡単に(?)作れそうだなぁ、と思いました。
使ってみた、というより試してみた、って感じの内容になってしまっていますが、examplesのほうは結構充実しているのでいろいろできそうです。pubsubとかありました。
以上、元Googlerのスマイルロボティクス株式会社代表取締役社長OTLこと小倉がお送りしました〜。