Rust版gRPCのtonic 0.14のメモ。
|
Rustのtonic(tonic-prost)0.14でgRPCのサーバーとクライアントを作ってみる。
(tonic 0.12からの変更点は、Cargo.tomlのdependenciesにtonic-prostとtonic-prost-buildを指定すること。それに伴い、build.rsでtonic_prost_buildを使う)
tonicを使うために、Cargo.tomlに以下の設定を追加する。
〜
[dependencies]
tonic = "0.14.5"
tonic-prost = "0.14.5"
prost = "0.14"
tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] }
[build-dependencies]
tonic-prost-build = "0.14.5"
tonic本体およびtonic-prostと、ビルドに使用するtonic-prost-buildを追加する。
また、protobufをビルドして生成された構造体を使う為にprostを使用する。
それと、tokioも追加しておく。
C++版gRPCサーバーの例のprotoファイルと同じものを使ってみる。
syntax = "proto3";
package example.grpc;
message Int64PairRequest {
int64 first_value = 1;
int64 second_value = 2;
}
message Int64PairResponse {
int64 first_value = 1;
int64 second_value = 2;
}
service ExampleService {
rpc SendInt64Pair(Int64PairRequest) returns (Int64PairResponse);
}
そして、protoファイルをビルドする為のbuild.rsを用意する。
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_prost_build::configure().compile_protos(&["proto/example.proto"], &["proto"])?;
Ok(())
}
compile_protos()の第2引数はprotocコマンドの「-I」相当。(インポートするファイルをそこから検索する)
use tonic::{Request, Response, Status, transport::Server};
use crate::example::grpc::{
Int64PairRequest, Int64PairResponse,
example_service_server::{ExampleService, ExampleServiceServer},
};
pub mod example {
pub mod grpc {
tonic::include_proto!("example.grpc");
}
}
↑tonic::include_proto!マクロの引数には、protoファイルに定義されたパッケージ名を指定する。
↓生成されたExampleServiceトレイトの実装を作成する。
struct ExampleServiceImpl {}
#[tonic::async_trait]
impl ExampleService for ExampleServiceImpl {
async fn send_int64_pair(
&self,
request: Request<Int64PairRequest>,
) -> Result<Response<Int64PairResponse>, Status> {
let request = request.into_inner();
println!("request = {:?}", request);
let response = Int64PairResponse {
first_value: request.first_value,
second_value: request.second_value,
};
Ok(Response::new(response))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let port = 50051;
let addr = format!("0.0.0.0:{port}").parse()?;
println!("address = {}", addr);
let service = ExampleServiceImpl {};
Server::builder()
.add_service(ExampleServiceServer::new(service))
.serve(addr)
.await?;
Ok(())
}
cargo run --bin server
Ctrl+Cで止める。
use tonic::Request;
use crate::example::grpc::{Int64PairRequest, example_service_client::ExampleServiceClient};
pub mod example {
pub mod grpc {
tonic::include_proto!("example.grpc");
}
}
↑tonic::include_proto!マクロの引数には、protoファイルに定義されたパッケージ名を指定する。
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let port = 50051;
let url = format!("http://localhost:{port}");
println!("address = {}", url);
let mut client = ExampleServiceClient::connect(url).await?;
let request = Request::new(Int64PairRequest {
first_value: 123,
second_value: 456,
});
let response = client.send_int64_pair(request).await?;
println!("response = {:?}", response.into_inner());
Ok(())
}
Endpoint構造体を使って接続する例。[2026-03-16]
let port = 50051;
let url = format!("http://localhost:{port}");
println!("address = {}", url);
let uri: tonic::transport::Uri = url.parse()?;
let endpoint = tonic::transport::Endpoint::from(uri);
let mut client = ExampleServiceClient::connect(endpoint).await?;
Channel構造体を使って接続する例。[2026-03-16]
let port = 50051;
let url = format!("http://localhost:{port}");
println!("address = {}", url);
let uri: tonic::transport::Uri = url.parse()?;
let endpoint = tonic::transport::Endpoint::from(uri);
let channel = endpoint.connect().await?; // Channel
let mut client = ExampleServiceClient::new(channel);
cargo run --bin client
gRPCではUNIXドメインソケットを使って通信することが出来る。[2026-03-16]
ただしtonic(というかtokio)では、UNIXドメインソケットのライブラリーはUNIXでしか使用できない。(Windowsではコンパイルエラーになる)
UNIXドメインソケットを受け付けるgRPCサーバーでは、tokio-streamクレートを使う。(Windowsではコンパイルできない)
〜
[dependencies]
〜
tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] }
tokio-stream = "0.1.18"
use tokio::net::UnixListener; use tokio_stream::wrappers::UnixListenerStream; 〜
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = "/tmp/grpc-example.sock"; // UNIXドメインソケット
let _ = std::fs::remove_file(path);
let uds = UnixListener::bind(path)?;
let uds_stream = UnixListenerStream::new(uds);
let service = ExampleServiceImpl {};
Server::builder()
.add_service(ExampleServiceServer::new(service))
.serve_with_incoming(uds_stream)
.await?;
Ok(())
}
cargo run --bin uds_server
UNIXドメインソケットで接続するgRPCクライアントでは、connect_with_connector()でChannelを構築する方法と、connect()にパスを渡す方法がある。
hyper-utilとtowerクレートを使う。(Windowsではコンパイルできない)
〜
[dependencies]
〜
tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] }
hyper-util = "0.1.20"
tower = "0.5.3"
use hyper_util::rt::TokioIo; use tokio::net::UnixStream; use tonic::Request; use tower::service_fn; 〜
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = "/tmp/grpc-example.sock"; // UNIXドメインソケット
// let path = "unix:///tmp/grpc-example.sock";
let endpoint = tonic::transport::Endpoint::try_from("http://[::]:50051")?;
let channel = endpoint
.connect_with_connector(service_fn(move |_: tonic::transport::Uri| async move {
let stream = UnixStream::connect(path).await?;
Ok::<_, std::io::Error>(TokioIo::new(stream))
}))
.await?;
let mut client = ExampleServiceClient::new(channel);
let request = Request::new(Int64PairRequest {
first_value: 123,
second_value: 456,
});
let response = client.send_int64_pair(request).await?;
println!("response = {:?}", response.into_inner());
Ok(())
}
Endpoint::connect_with_connector()でChannelを作る。
この中でUNIXドメインソケットのパスを指定するので、Endpointインスタンスの生成の際には適当なURL(実際には使われない)を指定しておく。
パスを「unix://」で始めれば、Client::connect()で単純に指定できる。
(UNIX専用の構造体は使わないので、Windowsでもコンパイルできる)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = "unix:///tmp/grpc-example.sock";
let mut client = ExampleServiceClient::connect(path).await?;
let request = Request::new(Int64PairRequest {
first_value: 123,
second_value: 456,
});
let response = client.send_int64_pair(request).await?;
println!("response = {:?}", response.into_inner());
Ok(())
}
cargo run --bin uds_client