Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions examples/http-tonic/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "http-tonic"
version = "0.1.0"
edition = "2021"

[dependencies]
lambda_http = { path = "../../lambda-http" }
prost = "0.14"
tokio = { version = "1", features = ["macros"] }
tonic = { version = "0.14", default-features = false, features = ["codegen", "router"] }
tonic-prost = "0.14"
tonic-web = "0.14"

[build-dependencies]
protoc-bin-vendored = "3"
tonic-prost-build = "0.14"
56 changes: 56 additions & 0 deletions examples/http-tonic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# AWS Lambda Function example

This example serves a [tonic] gRPC service from Lambda.

Lambda function URLs and API Gateway don't support native gRPC, because it needs HTTP/2 trailers. The function speaks
[gRPC-Web] instead, which works over HTTP/1.1. Clients must use a gRPC-Web transport, such as [`tonic-web`] for Rust,
[`grpc-web`] or [Connect] for browsers, or [`grpcurl`] through a gRPC-Web proxy.

If you use an API Gateway REST API, add `application/grpc-web+proto` to its binary media types. Function URLs and HTTP
APIs need no extra setup.

The build script compiles `proto/helloworld.proto` with a vendored `protoc`, so you don't need to install it.

## Build & Deploy

1. Install [cargo-lambda]
2. Build the function with `cargo lambda build --release`
3. Deploy the function to AWS Lambda with `cargo lambda deploy`. It creates an execution role with
`AWSLambdaBasicExecutionRole`; pass `--iam-role YOUR_ROLE` to use an existing one.

## Call the function

Create a function URL that requires IAM authentication. Don't use `cargo lambda deploy --enable-function-url`, which
creates a URL that anyone can call.

```sh
aws lambda create-function-url-config --function-name http-tonic --auth-type AWS_IAM
```

Send a SigV4-signed gRPC-Web request for `SayHello { name: "Lambda" }` with curl (7.75 or later):

```sh
printf '\x00\x00\x00\x00\x08\x0a\x06Lambda' > request.bin
eval "$(aws configure export-credentials --format env)"
curl -s -X POST "https://<url-id>.lambda-url.<region>.on.aws/helloworld.Greeter/SayHello" \
--aws-sigv4 "aws:amz:<region>:lambda" \
--user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
-H "x-amz-security-token: $AWS_SESSION_TOKEN" \
-H "content-type: application/grpc-web+proto" \
-H "x-grpc-web: 1" \
--data-binary @request.bin --output - | xxd
```

The response body holds a data frame with `Hello Lambda!` followed by a trailer frame with `grpc-status:0`.

## Build for ARM 64

Build the function with `cargo lambda build --release --arm64`

[tonic]: https://github.com/hyperium/tonic
[gRPC-Web]: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md
[`tonic-web`]: https://docs.rs/tonic-web
[`grpc-web`]: https://github.com/grpc/grpc-web
[Connect]: https://connectrpc.com/docs/web/
[`grpcurl`]: https://github.com/fullstorydev/grpcurl
[cargo-lambda]: https://github.com/cargo-lambda/cargo-lambda#installation
9 changes: 9 additions & 0 deletions examples/http-tonic/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Use a vendored `protoc` so the example builds without a system-wide install.
std::env::set_var("PROTOC", protoc_bin_vendored::protoc_bin_path()?);

tonic_prost_build::configure()
.build_client(false)
.compile_protos(&["proto/helloworld.proto"], &["proto"])?;
Ok(())
}
15 changes: 15 additions & 0 deletions examples/http-tonic/proto/helloworld.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
syntax = "proto3";

package helloworld;

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}
47 changes: 47 additions & 0 deletions examples/http-tonic/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! This is an example function that serves a [tonic](https://docs.rs/tonic/latest/tonic/)
//! gRPC service through the Lambda Rust runtime HTTP support.
//!
//! Lambda function URLs and API Gateway don't support native gRPC, which needs HTTP/2
//! trailers. This example uses [gRPC-Web](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md)
//! instead: it works over HTTP/1.1 and carries trailers in the response body, so
//! `tonic_web::GrpcWebLayer` translates it to regular gRPC for the tonic service.
//!
//! The tonic routes are converted into an axum `Router`, which implements `tower::Service`
//! and can be passed straight to `lambda_http::run`.
use lambda_http::{run, tracing, Error};
use tonic::{service::Routes, Request, Response, Status};
use tonic_web::GrpcWebLayer;

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

use hello_world::{
greeter_server::{Greeter, GreeterServer},
HelloReply, HelloRequest,
};

#[derive(Default)]
pub struct MyGreeter;

#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(&self, request: Request<HelloRequest>) -> Result<Response<HelloReply>, Status> {
let reply = HelloReply {
message: format!("Hello {}!", request.into_inner().name),
};
Ok(Response::new(reply))
}
}

#[tokio::main]
async fn main() -> Result<(), Error> {
// required to enable CloudWatch error logging by the runtime
tracing::init_default_subscriber();

let app = Routes::new(GreeterServer::new(MyGreeter))
.into_axum_router()
.layer(GrpcWebLayer::new());

run(app).await
}
Loading