本页目录

tonic (gRPC)

gRPC 用 protobuf 定义强类型的 service 接口——改了 .proto,所有调用方必须更新否则编译失败(编译器验证的合约,不是约定)。tonic 是 Rust 的 gRPC 实现,codegen 从 .proto 生成 Rust trait,streaming 类型(unary/client/server/bidi)和 interceptor 层分别对应不同的使用场景和横切逻辑。

gRPC 与 REST 的区别

REST 基于 HTTP 方法 + URL 表达操作,payload 通常是 JSON。gRPC 用 protobuf 定义强类型的 service 接口,payload 是二进制编码的 protobuf message。区别不仅是格式——而是编程模型⁠。REST 的接口定义是"约定"(API doc + 样板)。gRPC 的接口定义是编译器验证的合约⁠——改了 .proto,所有调用方必须更新否则编译失败。

tonic 是 Rust 生态中最成熟的 gRPC 实现,基于 tokio + tower + prost (protobuf 编译器)。

从 .proto 到 Rust 类型

syntax = "proto3";
package greeter;

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply);           // unary: 1 request → 1 response
    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.rstonic_build::compile_protos() 调用 protoc + prost 生成:

  • GreeterServer<T: Greeter> — server wrapper(把 Greeter trait 包装为 tower Service
  • Greeter trait — 业务逻辑的接口(你实现的方法)
  • GreeterClient — client stub(调用方使用的 async 方法)

生成的代码用 #[tonic::async_trait] 实现 trait 方法——这允许 trait 中包含 async fn(原生 Rust trait 尚不支持 async fn)。

Server: 实现 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 status code。gRPC 定义了 17 个 status codes:Ok, Cancelled, InvalidArgument, NotFound, AlreadyExists, PermissionDenied, Unauthenticated, ResourceExhausted, Unimplemented, Internal, Unavailable, DeadlineExceeded。每个 code 有明确的语义——不像 HTTP 中 400 可能代表"格式错误"也可能代表"参数缺少"。

Streaming: 不只是"一次请求一次响应"

// Server streaming: server 逐步发送数据
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))))
}

streaming 的关键:server streaming = server 返回 stream T,client 逐个接收。bidirectional = 两端各自发送 stream。这与 WebSocket 的"双向通道"不同——gRPC streaming 的每个消息仍是独立的、有类型的 protobuf message。

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 在 handler 之前执行——可以做认证、限流、metadata 注入。与 tower Layer 的区别:interceptor 是 tonic 的专属概念,Layer 是 tower 的通用概念。tonic 的 interceptor 实际上就是 tower Service 的一个薄封装。

参考

  • tonic: github.com/hyperium/tonic
  • gRPC: grpc.io (spec, status codes)
  • protobuf: protobuf.dev (encoding, proto3 language guide)

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