Skip to content

Commit f8706e3

Browse files
authored
Dedicated README for lambda_http (#554)
* add readme * fix location of Readme in the cargo.toml file #554 (review) * amend example based on the comments * make the code example to compile * amend example * add reference for Lambda function URLs Co-authored-by: Daniele <>
1 parent a0f6828 commit f8706e3

File tree

2 files changed

+234
-1
lines changed

2 files changed

+234
-1
lines changed

lambda-http/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ homepage = "https://github.com/awslabs/aws-lambda-rust-runtime"
1313
repository = "https://github.com/awslabs/aws-lambda-rust-runtime"
1414
documentation = "https://docs.rs/lambda_runtime"
1515
categories = ["web-programming::http-server"]
16-
readme = "../README.md"
16+
readme = "README.md"
1717

1818
[features]
1919
default = ["apigw_rest", "apigw_http", "apigw_websockets", "alb"]

lambda-http/README.md

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# lambda-http for AWS Lambda in Rust
2+
3+
[![Docs](https://docs.rs/lambda_http/badge.svg)](https://docs.rs/lambda_http)
4+
5+
**`lambda-http`** is an abstraction that takes payloads from different services and turns them into http objects, making it easy to write API Gateway proxy event focused Lambda functions in Rust.
6+
7+
lambda-http handler is made of:
8+
* Request - Represents an HTTP request
9+
* IntoResponse - Future that will convert an [`IntoResponse`] into an actual [`LambdaResponse`]
10+
11+
We are able to handle requests from:
12+
* [API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html) REST, HTTP and WebSockets API lambda integrations
13+
* AWS [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html)
14+
* AWS [Lambda function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html)
15+
16+
Thanks to the Request type we can seemsly handle proxy integrations without the worry to specify the specific service type.
17+
18+
There is also an Extentions for `lambda_http::Request` structs that provide access to [API gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format) and [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html) features.
19+
20+
For example some handy extensions:
21+
22+
* query_string_parameters - Return pre-parsed http query string parameters, parameters provided after the `?` portion of a url associated with the request
23+
* path_parameters - Return pre-extracted path parameters, parameter provided in url placeholders `/foo/{bar}/baz/{boom}` associated with the request
24+
* payload - Return the Result of a payload parsed into a serde Deserializeable type
25+
26+
## Examples
27+
28+
Here you will find a few examples to handle basic scenarions:
29+
30+
* Reading a JSON from a body and deserialise into a structure
31+
* Reading querystring parameters
32+
* Lambda Request Authorizer
33+
* Passing the Lambda execution context initialisation to the handler
34+
35+
### Reading a JSON from a body and deserialise into a structure
36+
37+
The code below creates a simple API Gateway proxy (HTTP, REST) that accept in input a JSON payload.
38+
39+
```rust
40+
use http::Response;
41+
use lambda_http::{run, http::StatusCode, service_fn, Error, IntoResponse, Request, RequestExt};
42+
use serde::{Deserialize, Serialize};
43+
use serde_json::json;
44+
45+
#[tokio::main]
46+
async fn main() -> Result<(), Error> {
47+
tracing_subscriber::fmt()
48+
.with_ansi(false)
49+
.without_time()
50+
.with_max_level(tracing_subscriber::filter::LevelFilter::INFO)
51+
.init();
52+
53+
run(service_fn(function_handler)).await
54+
}
55+
56+
pub async fn function_handler(event: Request) -> Result<impl IntoResponse, Error> {
57+
let body = event.payload::<MyPayload>()?;
58+
59+
let response = Response::builder()
60+
.status(StatusCode::OK)
61+
.header("Content-Type", "application/json")
62+
.body(json!({
63+
"message": "Hello World",
64+
"payload": body,
65+
}).to_string())
66+
.map_err(Box::new)?;
67+
68+
Ok(response)
69+
}
70+
71+
#[derive(Deserialize, Serialize, Debug, Clone)]
72+
pub struct MyPayload {
73+
pub prop1: String,
74+
pub prop2: String,
75+
}
76+
```
77+
78+
### Reading querystring parameters
79+
80+
```rust
81+
use http::Response;
82+
use lambda_http::{run, http::StatusCode, service_fn, Error, IntoResponse, Request, RequestExt};
83+
use serde_json::json;
84+
85+
#[tokio::main]
86+
async fn main() -> Result<(), Error> {
87+
tracing_subscriber::fmt()
88+
.with_ansi(false)
89+
.without_time()
90+
.with_max_level(tracing_subscriber::filter::LevelFilter::INFO)
91+
.init();
92+
93+
run(service_fn(function_handler)).await
94+
}
95+
96+
pub async fn function_handler(event: Request) -> Result<impl IntoResponse, Error> {
97+
let name = event.query_string_parameters()
98+
.first("name")
99+
.unwrap_or_else(|| "stranger")
100+
.to_string();
101+
102+
// Represents an HTTP response
103+
let response = Response::builder()
104+
.status(StatusCode::OK)
105+
.header("Content-Type", "application/json")
106+
.body(json!({
107+
"message": format!("Hello, {}!", name),
108+
}).to_string())
109+
.map_err(Box::new)?;
110+
111+
Ok(response)
112+
}
113+
```
114+
115+
### Lambda Request Authorizer
116+
117+
Because **`lambda-http`** is an abstraction, we cannot use it for the Lambda Request Authorizer case.
118+
If you remove the abstraction, you need to handle the request/response for your service.
119+
120+
121+
```rust
122+
use aws_lambda_events::apigw::{
123+
ApiGatewayCustomAuthorizerRequestTypeRequest, ApiGatewayCustomAuthorizerResponse, ApiGatewayCustomAuthorizerPolicy, IamPolicyStatement,
124+
};
125+
use lambda_runtime::{run, service_fn, Error, LambdaEvent};
126+
use serde_json::json;
127+
128+
#[tokio::main]
129+
async fn main() -> Result<(), Error> {
130+
tracing_subscriber::fmt()
131+
.with_ansi(false)
132+
.without_time()
133+
.with_max_level(tracing_subscriber::filter::LevelFilter::INFO)
134+
.init();
135+
136+
run(service_fn(function_handler)).await
137+
}
138+
139+
pub async fn function_handler(event: LambdaEvent<ApiGatewayCustomAuthorizerRequestTypeRequest>) -> Result<ApiGatewayCustomAuthorizerResponse, Error> {
140+
// do something with the event payload
141+
let method_arn = event.payload.method_arn.unwrap();
142+
// for example we could use the authorization header
143+
if let Some(token) = event.payload.headers.get("authorization") {
144+
// do something
145+
146+
return Ok(custom_authorizer_response(
147+
"ALLOW",
148+
"some_principal",
149+
&method_arn,
150+
));
151+
}
152+
153+
Ok(custom_authorizer_response(
154+
&"DENY".to_string(),
155+
"",
156+
&method_arn))
157+
}
158+
159+
pub fn custom_authorizer_response(effect: &str, principal: &str, method_arn: &str) -> ApiGatewayCustomAuthorizerResponse {
160+
let stmt = IamPolicyStatement {
161+
action: vec!["execute-api:Invoke".to_string()],
162+
resource: vec![method_arn.to_owned()],
163+
effect: Some(effect.to_owned()),
164+
};
165+
let policy = ApiGatewayCustomAuthorizerPolicy {
166+
version: Some("2012-10-17".to_string()),
167+
statement: vec![stmt],
168+
};
169+
ApiGatewayCustomAuthorizerResponse {
170+
principal_id: Some(principal.to_owned()),
171+
policy_document: policy,
172+
context: json!({ "email": principal }), // https://github.com/awslabs/aws-lambda-rust-runtime/discussions/548
173+
usage_identifier_key: None,
174+
}
175+
}
176+
```
177+
178+
## Passing the Lambda execution context initialisation to the handler
179+
180+
One of the [best practices](https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html) is to take advantage of execution environment reuse to improve the performance of your function. Initialize SDK clients and database connections outside the function handler. Subsequent invocations processed by the same instance of your function can reuse these resources. This saves cost by reducing function run time.
181+
182+
```rust
183+
use aws_sdk_dynamodb::model::AttributeValue;
184+
use chrono::Utc;
185+
use http::Response;
186+
use lambda_http::{run, http::StatusCode, service_fn, Error, IntoResponse, Request, RequestExt};
187+
use serde_json::json;
188+
189+
#[tokio::main]
190+
async fn main() -> Result<(), Error> {
191+
tracing_subscriber::fmt()
192+
.with_ansi(false)
193+
.without_time()
194+
.with_max_level(tracing_subscriber::filter::LevelFilter::INFO)
195+
.init();
196+
197+
let config = aws_config::from_env()
198+
.load()
199+
.await;
200+
201+
let dynamodb_client = aws_sdk_dynamodb::Client::new(&config);
202+
203+
run(service_fn(|event: Request| function_handler(&dynamodb_client, event))).await
204+
}
205+
206+
pub async fn function_handler(dynamodb_client: &aws_sdk_dynamodb::Client, event: Request) -> Result<impl IntoResponse, Error> {
207+
let table = std::env::var("TABLE_NAME").expect("TABLE_NAME must be set");
208+
209+
let name = event.query_string_parameters()
210+
.first("name")
211+
.unwrap_or_else(|| "stranger")
212+
.to_string();
213+
214+
dynamodb_client
215+
.put_item()
216+
.table_name(table)
217+
.item("ID", AttributeValue::S(Utc::now().timestamp().to_string()))
218+
.item("name", AttributeValue::S(name.to_owned()))
219+
.send()
220+
.await?;
221+
222+
// Represents an HTTP response
223+
let response = Response::builder()
224+
.status(StatusCode::OK)
225+
.header("Content-Type", "application/json")
226+
.body(json!({
227+
"message": format!("Hello, {}!", name),
228+
}).to_string())
229+
.map_err(Box::new)?;
230+
231+
Ok(response)
232+
}
233+
```

0 commit comments

Comments
 (0)