Skip to content

Commit ff948fa

Browse files
authored
Feature flags for lambda_http (#497)
* Feature flags for lambda_http Add feature flags to lambda_http so consumer can decide which service their events come from. This makes compilation much faster when you don't put the same Lambda function behind multiple services. Signed-off-by: David Calavera <[email protected]> * Rename features to match AWS documentation. Use the names present in the API GW documentation to differentiate the APIs. Signed-off-by: David Calavera <[email protected]> * Fix file names Signed-off-by: David Calavera <[email protected]> * Mention Function URLs in the readme. Signed-off-by: David Calavera <[email protected]> * Fix typo. Signed-off-by: David Calavera <[email protected]>
1 parent 28b4296 commit ff948fa

File tree

5 files changed

+91
-27
lines changed

5 files changed

+91
-27
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ output.json
1212

1313
.aws-sam
1414
build
15+
.vscode

README.md

+24-6
Original file line numberDiff line numberDiff line change
@@ -330,12 +330,6 @@ You can read more about how [cargo lambda start](https://github.com/calavera/car
330330

331331
Lambdas can be run and debugged locally using a special [Lambda debug proxy](https://github.com/rimutaka/lambda-debug-proxy) (a non-AWS repo maintained by @rimutaka), which is a Lambda function that forwards incoming requests to one AWS SQS queue and reads responses from another queue. A local proxy running on your development computer reads the queue, calls your Lambda locally and sends back the response. This approach allows debugging of Lambda functions locally while being part of your AWS workflow. The Lambda handler code does not need to be modified between the local and AWS versions.
332332

333-
## `lambda_runtime`
334-
335-
`lambda_runtime` is a library for authoring reliable and performant Rust-based AWS Lambda functions. At a high level, it provides `lambda_runtime::run`, a function that runs a `tower::Service<LambdaEvent>`.
336-
337-
To write a function that will handle request, you need to pass it through `service_fn`, which will convert your function into a `tower::Service<LambdaEvent>`, which can then be run by `lambda_runtime::run`.
338-
339333
## AWS event objects
340334

341335
This project does not currently include Lambda event struct definitions. Instead, the community-maintained [`aws_lambda_events`](https://crates.io/crates/aws_lambda_events) crate can be leveraged to provide strongly-typed Lambda event structs. You can create your own custom event objects and their corresponding structs as well.
@@ -378,6 +372,30 @@ fn main() -> Result<(), Box<Error>> {
378372
}
379373
```
380374

375+
## Feature flags in lambda_http
376+
377+
`lambda_http` is a wrapper for HTTP events coming from three different services, Amazon Load Balancer (ALB), Amazon Api Gateway (APIGW), and AWS Lambda Function URLs. Amazon Api Gateway can also send events from three different endpoints, REST APIs, HTTP APIs, and WebSockets. `lambda_http` transforms events from all these sources into native `http::Request` objects, so you can incorporate Rust HTTP semantics into your Lambda functions.
378+
379+
By default, `lambda_http` compiles your function to support any of those services. This increases the compile time of your function because we have to generate code for all the sources. In reality, you'll usually put a Lambda function only behind one of those sources. You can choose which source to generate code for with feature flags.
380+
381+
The available features flags for `lambda_http` are the following:
382+
383+
- `alb`: for events coming from [Amazon Elastic Load Balancer](https://aws.amazon.com/elasticloadbalancing/).
384+
- `apigw_rest`: for events coming from [Amazon API Gateway Rest APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html).
385+
- `apigw_http`: for events coming from [Amazon API Gateway HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) and [AWS Lambda Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html).
386+
- `apigw_websockets`: for events coming from [Amazon API Gateway WebSockets](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api.html).
387+
388+
If you only want to support one of these sources, you can disable the default features, and enable only the source that you care about in your package's `Cargo.toml` file. Substitute the dependency line for `lambda_http` for the snippet below, changing the feature that you want to enable:
389+
390+
```toml
391+
[dependencies.lambda_http]
392+
version = "0.5.3"
393+
default-features = false
394+
features = ["apigw_rest"]
395+
```
396+
397+
This will make your function compile much faster.
398+
381399
## Supported Rust Versions (MSRV)
382400

383401
The AWS Lambda Rust Runtime requires a minimum of Rust 1.54, and is not guaranteed to build on compiler versions earlier than that.

lambda-http/Cargo.toml

+11-4
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ documentation = "https://docs.rs/lambda_runtime"
1212
categories = ["web-programming::http-server"]
1313
readme = "../README.md"
1414

15-
[badges]
16-
travis-ci = { repository = "awslabs/aws-lambda-rust-runtime" }
17-
maintenance = { status = "actively-developed" }
15+
[features]
16+
default = ["apigw_rest", "apigw_http", "apigw_websockets", "alb"]
17+
apigw_rest = []
18+
apigw_http = []
19+
apigw_websockets = []
20+
alb = []
1821

1922
[dependencies]
20-
aws_lambda_events = { version = "^0.6.3", default-features = false, features = ["alb", "apigw"]}
2123
base64 = "0.13.0"
2224
bytes = "1"
2325
http = "0.2"
@@ -28,6 +30,11 @@ serde_json = "^1"
2830
serde_urlencoded = "0.7.0"
2931
query_map = { version = "0.5", features = ["url-query"] }
3032

33+
[dependencies.aws_lambda_events]
34+
version = "^0.6.3"
35+
default-features = false
36+
features = ["alb", "apigw"]
37+
3138
[dev-dependencies]
3239
log = "^0.4"
3340
maplit = "1.0"

lambda-http/src/request.rs

+35-8
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
//! request extension method provided by [lambda_http::RequestExt](../trait.RequestExt.html)
55
//!
66
use crate::ext::{PathParameters, QueryStringParameters, RawHttpPath, StageVariables};
7+
#[cfg(feature = "alb")]
78
use aws_lambda_events::alb::{AlbTargetGroupRequest, AlbTargetGroupRequestContext};
8-
use aws_lambda_events::apigw::{
9-
ApiGatewayProxyRequest, ApiGatewayProxyRequestContext, ApiGatewayV2httpRequest, ApiGatewayV2httpRequestContext,
10-
ApiGatewayWebsocketProxyRequest, ApiGatewayWebsocketProxyRequestContext,
11-
};
9+
#[cfg(feature = "apigw_rest")]
10+
use aws_lambda_events::apigw::{ApiGatewayProxyRequest, ApiGatewayProxyRequestContext};
11+
#[cfg(feature = "apigw_http")]
12+
use aws_lambda_events::apigw::{ApiGatewayV2httpRequest, ApiGatewayV2httpRequestContext};
13+
#[cfg(feature = "apigw_websockets")]
14+
use aws_lambda_events::apigw::{ApiGatewayWebsocketProxyRequest, ApiGatewayWebsocketProxyRequestContext};
1215
use aws_lambda_events::encodings::Body;
1316
use http::header::HeaderName;
1417
use query_map::QueryMap;
@@ -25,9 +28,13 @@ use std::{io::Read, mem};
2528
#[derive(Deserialize, Debug)]
2629
#[serde(untagged)]
2730
pub enum LambdaRequest {
31+
#[cfg(feature = "apigw_rest")]
2832
ApiGatewayV1(ApiGatewayProxyRequest),
33+
#[cfg(feature = "apigw_http")]
2934
ApiGatewayV2(ApiGatewayV2httpRequest),
35+
#[cfg(feature = "alb")]
3036
Alb(AlbTargetGroupRequest),
37+
#[cfg(feature = "apigw_websockets")]
3138
WebSocket(ApiGatewayWebsocketProxyRequest),
3239
}
3340

@@ -37,9 +44,13 @@ impl LambdaRequest {
3744
/// type of response the request origin expects.
3845
pub fn request_origin(&self) -> RequestOrigin {
3946
match self {
47+
#[cfg(feature = "apigw_rest")]
4048
LambdaRequest::ApiGatewayV1 { .. } => RequestOrigin::ApiGatewayV1,
49+
#[cfg(feature = "apigw_http")]
4150
LambdaRequest::ApiGatewayV2 { .. } => RequestOrigin::ApiGatewayV2,
51+
#[cfg(feature = "alb")]
4252
LambdaRequest::Alb { .. } => RequestOrigin::Alb,
53+
#[cfg(feature = "apigw_websockets")]
4354
LambdaRequest::WebSocket { .. } => RequestOrigin::WebSocket,
4455
}
4556
}
@@ -50,15 +61,20 @@ impl LambdaRequest {
5061
#[derive(Debug)]
5162
pub enum RequestOrigin {
5263
/// API Gateway request origin
64+
#[cfg(feature = "apigw_rest")]
5365
ApiGatewayV1,
5466
/// API Gateway v2 request origin
67+
#[cfg(feature = "apigw_http")]
5568
ApiGatewayV2,
5669
/// ALB request origin
70+
#[cfg(feature = "alb")]
5771
Alb,
5872
/// API Gateway WebSocket
73+
#[cfg(feature = "apigw_websockets")]
5974
WebSocket,
6075
}
6176

77+
#[cfg(feature = "apigw_http")]
6278
fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request<Body> {
6379
let http_method = ag.request_context.http.method.clone();
6480
let raw_path = ag.raw_path.unwrap_or_default();
@@ -130,6 +146,7 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request<Bod
130146
req
131147
}
132148

149+
#[cfg(feature = "apigw_rest")]
133150
fn into_proxy_request(ag: ApiGatewayProxyRequest) -> http::Request<Body> {
134151
let http_method = ag.http_method;
135152
let raw_path = ag.path.unwrap_or_default();
@@ -196,6 +213,7 @@ fn into_proxy_request(ag: ApiGatewayProxyRequest) -> http::Request<Body> {
196213
req
197214
}
198215

216+
#[cfg(feature = "alb")]
199217
fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request<Body> {
200218
let http_method = alb.http_method;
201219
let raw_path = alb.path.unwrap_or_default();
@@ -261,6 +279,7 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request<Body> {
261279
req
262280
}
263281

282+
#[cfg(feature = "apigw_websockets")]
264283
fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request<Body> {
265284
let http_method = ag.http_method;
266285
let builder = http::Request::builder()
@@ -338,22 +357,30 @@ fn apigw_path_with_stage(stage: &Option<String>, path: &str) -> String {
338357
#[serde(untagged)]
339358
pub enum RequestContext {
340359
/// API Gateway proxy request context
360+
#[cfg(feature = "apigw_rest")]
341361
ApiGatewayV1(ApiGatewayProxyRequestContext),
342362
/// API Gateway v2 request context
363+
#[cfg(feature = "apigw_http")]
343364
ApiGatewayV2(ApiGatewayV2httpRequestContext),
344365
/// ALB request context
366+
#[cfg(feature = "alb")]
345367
Alb(AlbTargetGroupRequestContext),
346368
/// WebSocket request context
369+
#[cfg(feature = "apigw_websockets")]
347370
WebSocket(ApiGatewayWebsocketProxyRequestContext),
348371
}
349372

350373
/// Converts LambdaRequest types into `http::Request<Body>` types
351374
impl<'a> From<LambdaRequest> for http::Request<Body> {
352375
fn from(value: LambdaRequest) -> Self {
353376
match value {
354-
LambdaRequest::ApiGatewayV2(ag) => into_api_gateway_v2_request(ag),
377+
#[cfg(feature = "apigw_rest")]
355378
LambdaRequest::ApiGatewayV1(ag) => into_proxy_request(ag),
379+
#[cfg(feature = "apigw_http")]
380+
LambdaRequest::ApiGatewayV2(ag) => into_api_gateway_v2_request(ag),
381+
#[cfg(feature = "alb")]
356382
LambdaRequest::Alb(alb) => into_alb_request(alb),
383+
#[cfg(feature = "apigw_websockets")]
357384
LambdaRequest::WebSocket(ag) => into_websocket_request(ag),
358385
}
359386
}
@@ -422,7 +449,7 @@ mod tests {
422449
}
423450

424451
#[test]
425-
fn deserializes_minimal_apigw_v2_request_events() {
452+
fn deserializes_minimal_apigw_http_request_events() {
426453
// from the docs
427454
// https://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-api-gateway-request
428455
let input = include_str!("../tests/data/apigw_v2_proxy_request_minimal.json");
@@ -450,7 +477,7 @@ mod tests {
450477
}
451478

452479
#[test]
453-
fn deserializes_apigw_v2_request_events() {
480+
fn deserializes_apigw_http_request_events() {
454481
// from the docs
455482
// https://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-api-gateway-request
456483
let input = include_str!("../tests/data/apigw_v2_proxy_request.json");
@@ -593,7 +620,7 @@ mod tests {
593620
}
594621

595622
#[test]
596-
fn deserialize_apigw_v2_sam_local() {
623+
fn deserialize_apigw_http_sam_local() {
597624
// manually generated from AWS SAM CLI
598625
// Steps to recreate:
599626
// * sam init

lambda-http/src/response.rs

+20-9
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
33
use crate::request::RequestOrigin;
44
use aws_lambda_events::encodings::Body;
5+
#[cfg(feature = "alb")]
56
use aws_lambda_events::event::alb::AlbTargetGroupResponse;
6-
use aws_lambda_events::event::apigw::{ApiGatewayProxyResponse, ApiGatewayV2httpResponse};
7+
#[cfg(any(feature = "apigw_rest", feature = "apigw_websockets"))]
8+
use aws_lambda_events::event::apigw::ApiGatewayProxyResponse;
9+
#[cfg(feature = "apigw_http")]
10+
use aws_lambda_events::event::apigw::ApiGatewayV2httpResponse;
711
use http::{
812
header::{CONTENT_TYPE, SET_COOKIE},
913
Response,
@@ -15,8 +19,11 @@ use serde::Serialize;
1519
#[derive(Serialize, Debug)]
1620
#[serde(untagged)]
1721
pub enum LambdaResponse {
18-
ApiGatewayV2(ApiGatewayV2httpResponse),
22+
#[cfg(any(feature = "apigw_rest", feature = "apigw_websockets"))]
1923
ApiGatewayV1(ApiGatewayProxyResponse),
24+
#[cfg(feature = "apigw_http")]
25+
ApiGatewayV2(ApiGatewayV2httpResponse),
26+
#[cfg(feature = "alb")]
2027
Alb(AlbTargetGroupResponse),
2128
}
2229

@@ -37,6 +44,15 @@ impl LambdaResponse {
3744
let status_code = parts.status.as_u16();
3845

3946
match request_origin {
47+
#[cfg(feature = "apigw_rest")]
48+
RequestOrigin::ApiGatewayV1 => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse {
49+
body,
50+
status_code: status_code as i64,
51+
is_base64_encoded: Some(is_base64_encoded),
52+
headers: headers.clone(),
53+
multi_value_headers: headers,
54+
}),
55+
#[cfg(feature = "apigw_http")]
4056
RequestOrigin::ApiGatewayV2 => {
4157
// ApiGatewayV2 expects the set-cookies headers to be in the "cookies" attribute,
4258
// so remove them from the headers.
@@ -57,13 +73,7 @@ impl LambdaResponse {
5773
multi_value_headers: headers,
5874
})
5975
}
60-
RequestOrigin::ApiGatewayV1 => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse {
61-
body,
62-
status_code: status_code as i64,
63-
is_base64_encoded: Some(is_base64_encoded),
64-
headers: headers.clone(),
65-
multi_value_headers: headers,
66-
}),
76+
#[cfg(feature = "alb")]
6777
RequestOrigin::Alb => LambdaResponse::Alb(AlbTargetGroupResponse {
6878
body,
6979
status_code: status_code as i64,
@@ -76,6 +86,7 @@ impl LambdaResponse {
7686
parts.status.canonical_reason().unwrap_or_default()
7787
)),
7888
}),
89+
#[cfg(feature = "apigw_websockets")]
7990
RequestOrigin::WebSocket => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse {
8091
body,
8192
status_code: status_code as i64,

0 commit comments

Comments
 (0)