Skip to content

Commit 43cee81

Browse files
committed
first cut of http support for VPC Lattice
1 parent ef22e58 commit 43cee81

6 files changed

Lines changed: 104 additions & 6 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ build
1616

1717
node_modules
1818
cdk.out
19+
/.idea/

lambda-http/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ categories = ["web-programming::http-server"]
1717
readme = "README.md"
1818

1919
[features]
20-
default = ["apigw_rest", "apigw_http", "apigw_websockets", "alb", "tracing"]
20+
default = ["apigw_rest", "apigw_http", "apigw_websockets", "alb", "vpc_lattice", "tracing"]
2121
apigw_rest = []
2222
apigw_http = []
2323
apigw_websockets = []
2424
alb = []
25+
vpc_lattice = []
2526
pass_through = []
2627
catch-all-fields = ["aws_lambda_events/catch-all-fields"]
2728
tracing = ["lambda_runtime/tracing"] # enables access to the Tracing utilities

lambda-http/src/deserializer.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use aws_lambda_events::apigw::ApiGatewayProxyRequest;
77
use aws_lambda_events::apigw::ApiGatewayV2httpRequest;
88
#[cfg(feature = "apigw_websockets")]
99
use aws_lambda_events::apigw::ApiGatewayWebsocketProxyRequest;
10+
use aws_lambda_events::vpc_lattice::VpcLatticeRequestV2;
1011
use serde::{de::Error, Deserialize};
1112
use serde_json::value::RawValue;
1213

@@ -39,6 +40,10 @@ impl<'de> Deserialize<'de> for LambdaRequest {
3940
if let Ok(res) = serde_json::from_str::<ApiGatewayWebsocketProxyRequest>(data) {
4041
return Ok(LambdaRequest::WebSocket(res));
4142
}
43+
#[cfg(feature = "vpc_lattice")]
44+
if let Ok(res) = serde_json::from_str::<VpcLatticeRequestV2>(data) {
45+
return Ok(LambdaRequest::VpcLattice(res));
46+
}
4247
#[cfg(feature = "pass_through")]
4348
if PASS_THROUGH_ENABLED {
4449
return Ok(LambdaRequest::PassThrough(data.to_string()));

lambda-http/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
#![cfg_attr(docsrs, feature(doc_cfg))]
33
//#![deny(warnings)]
44
//! Enriches the `lambda` crate with [`http`](https://github.com/hyperium/http)
5-
//! types targeting AWS [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html), [API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html) REST and HTTP API lambda integrations.
5+
//! types targeting AWS [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html),
6+
//! [API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html),
7+
//! [VPC Lattice]() REST and HTTP API lambda integrations
68
//!
79
//! This crate abstracts over all of these trigger events using standard [`http`](https://github.com/hyperium/http) types minimizing the mental overhead
810
//! of understanding the nuances and variation between trigger details allowing you to focus more on your application while also giving you to the maximum flexibility to

lambda-http/src/request.rs

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! ALB and API Gateway request adaptations
1+
//! ALB and API Gateway and VPC Lattice request adaptations
22
//!
33
//! Typically these are exposed via the [`request_context()`] or [`request_context_ref()`]
44
//! request extension methods provided by the [`RequestExt`] trait.
@@ -12,7 +12,8 @@ use crate::ext::extensions::{PathParameters, StageVariables};
1212
feature = "apigw_rest",
1313
feature = "apigw_http",
1414
feature = "alb",
15-
feature = "apigw_websockets"
15+
feature = "apigw_websockets",
16+
feature = "vpc_lattice"
1617
))]
1718
use crate::ext::extensions::{QueryStringParameters, RawHttpPath};
1819
#[cfg(feature = "alb")]
@@ -25,6 +26,9 @@ use aws_lambda_events::apigw::{ApiGatewayProxyRequest, ApiGatewayProxyRequestCon
2526
use aws_lambda_events::apigw::{ApiGatewayV2httpRequest, ApiGatewayV2httpRequestContext};
2627
#[cfg(feature = "apigw_websockets")]
2728
use aws_lambda_events::apigw::{ApiGatewayWebsocketProxyRequest, ApiGatewayWebsocketProxyRequestContext};
29+
#[cfg(feature = "vpc_lattice")]
30+
use aws_lambda_events::vpc_lattice::{VpcLatticeRequestV2, VpcLatticeRequestV2Context};
31+
2832
use aws_lambda_events::{encodings::Body, query_map::QueryMap};
2933
use http::{header::HeaderName, HeaderMap, HeaderValue};
3034

@@ -50,6 +54,8 @@ pub enum LambdaRequest {
5054
Alb(AlbTargetGroupRequest),
5155
#[cfg(feature = "apigw_websockets")]
5256
WebSocket(ApiGatewayWebsocketProxyRequest),
57+
#[cfg(feature = "vpc_lattice")]
58+
VpcLattice(VpcLatticeRequestV2),
5359
#[cfg(feature = "pass_through")]
5460
PassThrough(String),
5561
}
@@ -68,6 +74,8 @@ impl LambdaRequest {
6874
LambdaRequest::Alb { .. } => RequestOrigin::Alb,
6975
#[cfg(feature = "apigw_websockets")]
7076
LambdaRequest::WebSocket { .. } => RequestOrigin::WebSocket,
77+
#[cfg(feature = "vpc_lattice")]
78+
LambdaRequest::VpcLattice { .. } => RequestOrigin::VpcLattice,
7179
#[cfg(feature = "pass_through")]
7280
LambdaRequest::PassThrough { .. } => RequestOrigin::PassThrough,
7381
#[cfg(not(any(
@@ -100,6 +108,9 @@ pub enum RequestOrigin {
100108
/// API Gateway WebSocket
101109
#[cfg(feature = "apigw_websockets")]
102110
WebSocket,
111+
/// VPC Lattice origin
112+
#[cfg(feature = "vpc_lattice")]
113+
VpcLattice,
103114
/// PassThrough request origin
104115
#[cfg(feature = "pass_through")]
105116
PassThrough,
@@ -282,6 +293,58 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request<Body> {
282293
req
283294
}
284295

296+
#[cfg(feature = "vpc_lattice")]
297+
fn into_vpc_lattice_request(vpclat: VpcLatticeRequestV2) -> http::Request<Body> {
298+
let http_method = vpclat.method;
299+
let host = vpclat.headers.get(http::header::HOST).and_then(|s| s.to_str().ok());
300+
let raw_path = vpclat.path.unwrap_or_default();
301+
302+
let query_string_parameters = decode_query_map(vpclat.query_string_parameters);
303+
//let multi_value_query_string_parameters = decode_query_map(vpclat.multi_value_query_string_parameters);
304+
305+
let builder = http::Request::builder()
306+
.uri(build_request_uri(
307+
&raw_path,
308+
&vpclat.headers,
309+
host,
310+
Some((&query_string_parameters, &query_string_parameters)),
311+
))
312+
.extension(RawHttpPath(raw_path))
313+
// multi valued query string parameters are always a super
314+
// set of singly valued query string parameters,
315+
// when present, multi-valued query string parameters are preferred
316+
.extension(QueryStringParameters(
317+
//if multi_value_query_string_parameters.is_empty() {
318+
query_string_parameters, //} else {
319+
// multi_value_query_string_parameters
320+
//},
321+
))
322+
.extension(RequestContext::VpcLattice(vpclat.request_context));
323+
324+
// merge headers into multi_value_headers and make
325+
// multi-value_headers our canonical source of request headers
326+
let mut headers = vpclat.headers;
327+
//headers.extend(vpclat.headers);
328+
update_xray_trace_id_header(&mut headers);
329+
330+
let base64 = vpclat.is_base64_encoded;
331+
332+
let mut req = builder
333+
.body(
334+
vpclat
335+
.body
336+
.as_deref()
337+
.map_or_else(Body::default, |b| Body::from_maybe_encoded(base64, b)),
338+
)
339+
.expect("failed to build request");
340+
341+
// no builder method that sets headers in batch
342+
let _ = std::mem::replace(req.headers_mut(), headers);
343+
let _ = std::mem::replace(req.method_mut(), http_method);
344+
345+
req
346+
}
347+
285348
#[cfg(feature = "alb")]
286349
fn decode_query_map(query_map: QueryMap) -> QueryMap {
287350
use std::str::FromStr;
@@ -403,6 +466,8 @@ pub enum RequestContext {
403466
/// WebSocket request context
404467
#[cfg(feature = "apigw_websockets")]
405468
WebSocket(ApiGatewayWebsocketProxyRequestContext),
469+
#[cfg(feature = "vpc_lattice")]
470+
VpcLattice(VpcLatticeRequestV2Context),
406471
/// Custom request context
407472
#[cfg(feature = "pass_through")]
408473
PassThrough,
@@ -420,6 +485,8 @@ impl From<LambdaRequest> for http::Request<Body> {
420485
LambdaRequest::Alb(alb) => into_alb_request(alb),
421486
#[cfg(feature = "apigw_websockets")]
422487
LambdaRequest::WebSocket(ag) => into_websocket_request(ag),
488+
#[cfg(feature = "vpc_lattice")]
489+
LambdaRequest::VpcLattice(vpclat) => into_vpc_lattice_request(vpclat),
423490
#[cfg(feature = "pass_through")]
424491
LambdaRequest::PassThrough(data) => into_pass_through_request(data),
425492
}

lambda-http/src/response.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use aws_lambda_events::apigw::ApiGatewayProxyResponse;
88
#[cfg(feature = "apigw_http")]
99
use aws_lambda_events::apigw::ApiGatewayV2httpResponse;
1010
use aws_lambda_events::encodings::Body;
11+
#[cfg(feature = "vpc_lattice")]
12+
use aws_lambda_events::vpc_lattice::VpcLatticeResponse;
1113
use encoding_rs::Encoding;
1214
use http::{
1315
header::{CONTENT_ENCODING, CONTENT_TYPE},
@@ -50,6 +52,8 @@ pub enum LambdaResponse {
5052
ApiGatewayV2(ApiGatewayV2httpResponse),
5153
#[cfg(feature = "alb")]
5254
Alb(AlbTargetGroupResponse),
55+
#[cfg(feature = "vpc_lattice")]
56+
VpcLattice(VpcLatticeResponse),
5357
#[cfg(feature = "pass_through")]
5458
PassThrough(serde_json::Value),
5559
}
@@ -141,6 +145,23 @@ impl LambdaResponse {
141145
#[cfg(feature = "catch-all-fields")]
142146
other: Default::default(),
143147
}),
148+
#[cfg(feature = "vpc_lattice")]
149+
RequestOrigin::VpcLattice => LambdaResponse::VpcLattice(VpcLatticeResponse {
150+
body,
151+
is_base64_encoded,
152+
status_code: status_code as u16,
153+
status_description: Some(format!(
154+
"{} {}",
155+
status_code,
156+
parts.status.canonical_reason().unwrap_or_default()
157+
)),
158+
// Explicitly empty, as API gateway v1 will merge "headers" and
159+
// "multi_value_headers" fields together resulting in duplicate response headers.
160+
headers: HeaderMap::new(),
161+
// Today, this implementation doesn't provide any additional fields
162+
#[cfg(feature = "catch-all-fields")]
163+
other: Default::default(),
164+
}),
144165
#[cfg(feature = "pass_through")]
145166
RequestOrigin::PassThrough => {
146167
match body {
@@ -154,9 +175,10 @@ impl LambdaResponse {
154175
feature = "apigw_rest",
155176
feature = "apigw_http",
156177
feature = "alb",
157-
feature = "apigw_websockets"
178+
feature = "apigw_websockets",
179+
feature = "vpc_lattice"
158180
)))]
159-
_ => compile_error!("Either feature `apigw_rest`, `apigw_http`, `alb`, or `apigw_websockets` must be enabled for the `lambda-http` crate."),
181+
_ => compile_error!("Either feature `apigw_rest`, `apigw_http`, `alb`, `apigw_websockets` or `vpc_lattice` must be enabled for the `lambda-http` crate."),
160182
}
161183
}
162184
}

0 commit comments

Comments
 (0)