Expand description
Implementing a service.
Codegen emits a trait with one method per RPC and a <Service>Server<T> that
wraps your implementation. The server is an ordinary trillium
Handler: it matches the service’s path prefix, validates
the gRPC request preflight, decodes and frames messages, enforces the deadline,
and writes the terminating grpc-status from the Result your method returns.
You implement the trait; the rest is the generated wrapper.
trillium_tokio::run(GreeterServer::new(MyGreeter));§The control surface
Every method receives a GrpcServerConn by &mut. It is the per-call control
surface — it owns the connection for one RPC, value in and value out, the way a
trillium Handler owns its Conn:
received_headers— the request’s initial metadata.response_headers_mut— initial response metadata. It commits when the first message is written (or when the method returns), so set it before then.response_trailers_mut— trailing response metadata, sent alongsidegrpc-statusafter the last message.deadline— the instant derived from the request’sgrpc-timeout, if any. The framework already races your method against it; read it if you want to budget your own work.requests— for the request-streaming shapes, the inboundRequestStreamof decoded messages.
§The four shapes
The method signature follows the RPC’s directionality:
| RPC shape | method receives | method returns |
|---|---|---|
| unary | &mut GrpcServerConn, Req | Result<Resp, Status> |
| server-streaming | &mut GrpcServerConn, Req | Result<impl Stream<Item = Result<Resp, Status>>, Status> |
| client-streaming | &mut GrpcServerConn | Result<Resp, Status> |
| bidirectional | &mut GrpcServerConn | Result<impl BidiResponder, Status> |
Unary is one request in, one response out:
async fn say_hello(
&self,
_conn: &mut GrpcServerConn,
request: HelloRequest,
) -> Result<HelloReply, Status> {
Ok(HelloReply { message: format!("Hello, {}", request.name) })
}Server-streaming returns a Stream the framework pulls responses from until
it ends. The returned stream is + use<> so it doesn’t capture &self:
async fn say_hello_stream(
&self,
_conn: &mut GrpcServerConn,
request: HelloRequest,
) -> Result<impl Stream<Item = Result<HelloReply, Status>> + Send + use<>, Status> {
Ok(futures_lite::stream::iter((1..=3).map(move |i| {
Ok(HelloReply { message: format!("Hello #{i}, {}", request.name) })
})))
}Client-streaming reads the inbound messages off the conn with
requests, then returns one response.
RequestStream::recv yields Ok(None) at end of stream:
async fn say_hello_many(&self, conn: &mut GrpcServerConn) -> Result<HelloReply, Status> {
let mut names = Vec::new();
let mut requests = conn.requests::<HelloRequest>();
while let Some(request) = requests.recv().await? {
names.push(request.name);
}
Ok(HelloReply { message: format!("Hello, {}", names.join(" and ")) })
}Bidirectional-streaming needs to read and write at the same time, which can
only happen after the response head is on the wire. So the trait method is a
prologue: it runs first, may read early requests and set initial metadata, and
returns a BidiResponder. The responder’s
respond method gets a Channel and drives the
read-while-write loop afterward — Channel::recv for requests,
Channel::send for responses:
async fn say_hello_chat(
&self,
_conn: &mut GrpcServerConn,
) -> Result<impl BidiResponder<HelloRequest, HelloReply> + use<>, Status> {
Ok(Chat)
}
struct Chat;
impl BidiResponder<HelloRequest, HelloReply> for Chat {
async fn respond(self, mut channel: Channel<'_, HelloRequest, HelloReply>) -> Result<(), Status> {
while let Some(request) = channel.recv().await.transpose()? {
channel.send(HelloReply { message: format!("Hey, {}", request.name) }).await?;
}
Ok(())
}
}The server::bidi module explains why bidi splits into two
functions and what crosses between them.
§Returning errors
Return Err(Status) from any shape and the framework ends the call with that
grpc-status and message — for unary and the streaming shapes alike, on the
prologue or mid-loop. Status has a constructor per gRPC code
(Status::not_found, Status::invalid_argument, …). Trailing metadata you
set via response_trailers_mut (or
Channel::response_trailers_mut) still rides the error trailers.