6 分で読了 #rust #async-ecosystem
このページの目次

tonic (gRPC)

gRPC は protobuf を用いて強型付けされたサービスインターフェースを定義する——.proto を変更すると、呼び出し元はすべて更新しなければならない。さもないとコンパイルエラーになる(規約ではなく、コンパイラで検証される契約)。tonic は Rust 用の gRPC 実装であり、codegen は .proto から Rust の trait やストリーミング型(unary/client/server/bidi)を生成し、interceptor 層は異なるユースケースや横断的ロジックに対応する。

gRPC と REST の違い

REST は HTTP メソッドと URL を用いて操作を表現し、ペイロードは通常 JSON です。gRPC は protobuf を用いて強型付けされたサービスインターフェースを定義し、ペイロードはバイナリエンコードされた protobuf メッセージです。違いはフォーマットだけでなく、⁠プログラミングモデルにあります。REST のインターフェース定義は「規約」(API ドキュメント + 定型コード)です。gRPC のインターフェース定義はコンパイラで検証される契約⁠——.proto を変更すると、呼び出し元はすべて更新しなければならない。さもないとコンパイルエラーになります。

tonic は Rust エコシステムで最も成熟した gRPC 実装であり、tokio + tower + prost (protobuf コンパイラ) を基盤としています。

.proto から Rust 型へ

syntax = "proto3";
package greeter;

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply);           // unary: 1 リクエスト → 1 レスポンス
    rpc StreamHello (HelloRequest) returns (stream HelloReply); // server streaming
    rpc Chat (stream ChatMessage) returns (stream ChatMessage); // bidi streaming
}

message HelloRequest { string name = 1; }
message HelloReply { string message = 1; }

build.rs 内の tonic_build::compile_protos() は protoc と prost を呼び出し、以下を生成します:

  • GreeterServer<T: Greeter> — サーバーラッパー(Greeter trait を tower Service にラップ)
  • Greeter trait — ビジネスロジックのインターフェース(あなたが実装するメソッド)
  • GreeterClient — クライアントスタブ(呼び出し側が使用する非同期メソッド)

生成されたコードは #[tonic::async_trait] を用いて trait メソッドを実装しています。これは、trait 内に async fn を含めることを可能にします(Rust のネイティブな trait はまだ async fn をサポートしていません)。

サーバー: trait の実装

struct MyGreeter;
#[tonic::async_trait]
impl Greeter for MyGreeter {
    async fn say_hello(&self, req: Request<HelloRequest>) -> Result<Response<HelloReply>, Status> {
        let name = req.into_inner().name;
        if name.is_empty() {
            return Err(Status::invalid_argument("name is required"));
        }
        Ok(Response::new(HelloReply {
            message: format!("Hello {}!", name),
        }))
    }
}

tonic::transport::Server::builder()
    .add_service(GreeterServer::new(MyGreeter))
    .serve("[::1]:50051".parse().unwrap()).await?;

Status は gRPC の標準エラー型です——HTTP ステータスコードではありません。gRPC は 17 種類のステータスコードを定義しています:Ok, Cancelled, InvalidArgument, NotFound, AlreadyExists, PermissionDenied, Unauthenticated, ResourceExhausted, Unimplemented, Internal, Unavailable, DeadlineExceeded。各コードには明確なセマンティクスがあり、HTTP の 400 が「フォーマットエラー」也可能是「パラメータ不足」を表すのとは異なります。

ストリーミング: 「1 リクエスト 1 レスポンス」だけではない

// Server streaming: サーバーが段階的にデータを送信
async fn stream_hello(&self, req: Request<HelloRequest>) -> Result<Response<Self::StreamHelloStream>, Status> {
    let mut stream = tokio_stream::iter(vec!["Hello", "Bonjour", "Hola"].into_iter()
        .map(|s| Ok(HelloReply { message: s.into() })));
    Ok(Response::new(Box::pin(stream)))
}

// Bidirectional streaming: 両側が同時に送信
async fn chat(&self, req: Request<Streaming<ChatMessage>>) -> Result<Response<Self::ChatStream>, Status> {
    let mut in_stream = req.into_inner();
    let (tx, rx) = tokio::sync::mpsc::channel(4);
    tokio::spawn(async move {
        while let Some(Ok(msg)) = in_stream.next().await {
            tx.send(Ok(ChatReply { response: format!("echo: {}", msg.text) })).await.unwrap();
        }
    });
    Ok(Response::new(Box::pin(tokio_stream::wrappers::ReceiverStream::new(rx))))
}

ストリーミングの鍵:server streaming = サーバーが stream T を返し、クライアントが逐次受信する。bidirectional = 両側がそれぞれ stream を送信する。これは WebSocket の「双方向チャネル」とは異なります——gRPC ストリーミングの各メッセージは依然として独立した、型付きの protobuf メッセージです。

Interceptor: リクエストレベルのミドルウェア

fn auth_interceptor(mut req: Request<()>) -> Result<Request<()>, Status> {
    let token = req.metadata().get("authorization")
        .ok_or_else(|| Status::unauthenticated("missing token"))?;
    validate_token(token)?;
    Ok(req)
}

Server::builder()
    .add_service(GreeterServer::with_interceptor(MyGreeter, auth_interceptor))

interceptor はハンドラーの前に実行されます——認証、レート制限、メタデータの注入などが可能です。tower Layer との違い:interceptor は tonic 固有の概念であり、Layer は tower の汎用概念です。tonic の interceptor は実際には tower Service の薄いラッパーです。

参考

  • tonic: github.com/hyperium/tonic
  • gRPC: grpc.io (仕様、ステータスコード)
  • protobuf: protobuf.dev (エンコーディング、proto3 言語ガイド)

Keywords: tonic, gRPC, protobuf, streaming, interceptor, Status, unary, bidirectional