lambda-grpc-web 0.1.0

grpc-web framed tonic service adaptor to aws lambda runtime
Documentation

lambda-grpc-web

Run Tonic gRPC services on AWS Lambda.

This crate makes gRPC usable on Lambda, but with unavoidable tradeoffs.

It is designed for:

  • Unary gRPC APIs
  • Short-lived server streaming
  • Scale-to-zero, low-ops deployments

It is not a full replacement for a native HTTP/2 gRPC server.


What this is (and is not)

This crate does

  • Run existing tonic services on AWS Lambda
  • Preserve Tower middleware composition
  • Support gRPC-Web clients
  • Work with Lambda Function URLs / API Gateway

This crate does not

  • Provide full HTTP/2 semantics
  • Support bidirectional or client streaming
  • Preserve connection-level state (note client cancellations is not possible)
  • Eliminate Lambda cold starts or latency tradeoffs

Quick start

1. Write service

Define a normal Tonic service, and substitute only the tonic::transport::Server builder with lambda_grpc_web::LambdaServer

use hello_world::greeter_server::{Greeter, GreeterServer};
use hello_world::{HelloReply, HelloRequest};
use lambda_grpc_web::lambda_runtime::Error;
use lambda_grpc_web::LambdaServer;
use tonic::{Request, Response, Status};

// note everything from here until the main fn is vanilla tonic service

pub mod hello_world {
    tonic::include_proto!("helloworld");
}

#[derive(Debug, Default)]
pub struct MyGreeter {}

#[tonic::async_trait]
impl Greeter for MyGreeter {
    async fn say_hello(
        &self,
        request: Request<HelloRequest>,
    ) -> Result<Response<HelloReply>, Status> {
        println!("Got a request: {:?}", request);

        let reply = HelloReply {
            message: format!("Hello {}!", request.into_inner().name),
        };

        Ok(Response::new(reply))
    }

}

#[tokio::main]
async fn main() -> Result<(), Error> { // <- note error here is `lambda_grpc_web::lambda_runtime::Error` 
    let greeter = MyGreeter::default();

    LambdaServer::builder() // <- Different builder
        .add_service(GreeterServer::new(greeter))
        .serve() // <- no socket declared
        .await?;

    Ok(())
}

2. Test locally with cargo lambda

Refer to https://cargo-lambda.info for more information

cargo lambda watch

Important note - the grpc service frames messages with grpc-web - your test client must be able to talk this protocol.

3. Deploy

Compile with cargo lambda (refer to their docs)

Tips:

  • Make sure to configure invoke mode as RESPONSE_STREAM
  • Configure a sensible timeout as client disconnects cannot propagate to lambda cancellation.

Supported features

Feature Status
Unary RPCs Supported
Server streaming Limited
Client streaming Not supported
Bidirectional streaming Not supported
Interceptors / Tower layers Supported
Trailers Supported

Compatibility notes

Transport

  • Requests are handled via HTTP/1.1
  • gRPC-Web framing is used
  • HTTP/2-specific features are unavailable

Middleware

Works:

  • Logging
  • Auth
  • Metrics
  • Deadlines (request-scoped)

Does not work as expected:

  • Middleware requiring connection state
  • Peer IP / TLS inspection
  • Channel-level interceptors

Performance expectations

This crate optimizes for:

  • Operational simplicity
  • Cost efficiency
  • Scale-to-zero workloads

It does not optimize for:

  • Lowest possible latency (cold starts while fast in rust still add latency)
  • Long-lived connections (Lambda has a 15-minute hard maximum duration)

If you need those, run native gRPC behind ALB / ECS / EC2.


Future work

  • Support managed lambdas - should mostly just work
  • Flesh out docs as more of a tutorial style including deployment