🐙 tako
Transports

gRPC

Serve gRPC unary RPCs over HTTP/2 with protobuf using the GrpcRequest extractor and GrpcResponse responder on ordinary Tako routes.

gRPC

Tako serves gRPC unary RPCs by treating each method as a normal HTTP route. There is no separate gRPC server: a gRPC handler is an ordinary async function that takes a GrpcRequest<T> extractor and returns a GrpcResponse<U> responder, registered on the same Router as your REST endpoints. The helpers live in tako-rs-core behind the grpc feature and require the tokio runtime — gRPC is not available on compio.

[dependencies]
tako-rs = { version = "2", features = ["grpc"] }
prost = "0.13"

The grpc feature gates the tako::grpc module. Protobuf message types come from prost; in production they are generated by prost-build from a .proto file. See the feature reference for the related protobuf extractor flag.

Defining messages

Request and reply types are prost::Message derives. Inline definitions are fine for a quick service; real projects generate them:

use prost::Message;

#[derive(Clone, PartialEq, Message)]
pub struct HelloRequest {
  #[prost(string, tag = "1")]
  pub name: String,
}

#[derive(Clone, PartialEq, Message)]
pub struct HelloReply {
  #[prost(string, tag = "1")]
  pub message: String,
}

Writing a unary handler

GrpcRequest<T> decodes the gRPC-framed protobuf body and exposes it as req.message. GrpcResponse<U> frames the reply and sets the gRPC headers. Build a success with GrpcResponse::ok(msg) or an error with GrpcResponse::error(code, message) where code is a GrpcStatusCode:

use tako::grpc::{GrpcRequest, GrpcResponse, GrpcStatusCode};

async fn say_hello(req: GrpcRequest<HelloRequest>) -> GrpcResponse<HelloReply> {
  let name = &req.message.name;
  if name.is_empty() {
    return GrpcResponse::error(GrpcStatusCode::InvalidArgument, "name must not be empty");
  }
  GrpcResponse::ok(HelloReply {
    message: format!("Hello, {name}!"),
  })
}

GrpcStatusCode is the full canonical set (Ok, InvalidArgument, NotFound, Unimplemented, ResourceExhausted, …). Errors are returned at HTTP 200 OK with the status carried in the grpc-status header, per the gRPC HTTP/2 mapping.

Attaching the service to the router

A gRPC method maps to POST /<package>.<Service>/<Method>. Register it like any other route. gRPC requires HTTP/2 on the wire, so enable the http2 feature and serve over a transport that negotiates h2 — spawn_h2c for cleartext behind a proxy, or spawn_tls for TLS with ALPN:

use anyhow::Result;
use tako::Method;
use tako::router::Router;

#[tokio::main]
async fn main() -> Result<()> {
  let mut router = Router::new();
  router.route(Method::POST, "/grpc.Greeter/SayHello", say_hello);

  let listener = tokio::net::TcpListener::bind("127.0.0.1:8080").await?;
  tako::serve(listener, router).await;
  Ok(())
}

The path string is the gRPC contract — it must be the exact /<package>.<Service>/<Method> a client like grpcurl, Envoy, or a generated stub will call. There is no reflection-based auto-registration; you list each method explicitly.

Message size limits

Inbound frames are capped at MAX_GRPC_MESSAGE_SIZE (4 MiB), matching the default grpc-go and tonic server limits. A length prefix that advertises more is rejected with ResourceExhausted rather than pre-allocating the claimed buffer, so a hostile client cannot force a large allocation with a few bytes.

The unary path does not support compressed frames. A request whose compressed-flag byte is set is rejected with Unimplemented, signalling the client to retry uncompressed.

Deadlines

If a client sends a grpc-timeout header, tako::grpc::read_grpc_deadline(&mut req) parses it and inserts a GrpcDeadline into the request extensions so handlers and middleware can honor the cancellation contract. Parsing is overflow-safe: an absurdly large value is treated as "no deadline" rather than panicking.

Beyond unary

The module also contains scaffolding for server-streaming (GrpcServerStream), client-streaming (GrpcClientStream), bidirectional (GrpcBidi) RPCs, a gRPC-Web bridge, and grpc.health.v1 / reflection helpers. The stable, documented surface is the unary GrpcRequest / GrpcResponse pair shown above; treat the streaming types as building blocks. For browser-facing realtime, prefer SSE or WebSocket.

Example

  • examples/grpc-unary — a Greeter and an Echo service registered as POST routes, testable with grpcurl.

On this page