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— aGreeterand anEchoservice registered as POST routes, testable withgrpcurl.