From dad862e087244caa121336fe88c2973e92611933 Mon Sep 17 00:00:00 2001 From: softprops Date: Fri, 28 Dec 2018 13:00:28 +0900 Subject: [PATCH 1/4] support both apigw and alb events as http::{Request,Response} --- lambda-http/Cargo.toml | 3 +- lambda-http/src/body.rs | 6 +- lambda-http/src/ext.rs | 49 ++++-- lambda-http/src/lib.rs | 29 ++-- lambda-http/src/request.rs | 154 +++++++++++++----- lambda-http/src/response.rs | 71 ++++---- lambda-http/tests/data/alb_health_check.json | 15 ++ .../tests/data/alb_multi_value_request.json | 37 +++++ lambda-http/tests/data/alb_request.json | 24 +++ 9 files changed, 287 insertions(+), 101 deletions(-) create mode 100644 lambda-http/tests/data/alb_health_check.json create mode 100644 lambda-http/tests/data/alb_multi_value_request.json create mode 100644 lambda-http/tests/data/alb_request.json diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 22861f75..828e619e 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Doug Tangren"] edition = "2018" description = "Rust API Gateway proxy event interfaces for AWS Lambda" -keywords = ["AWS", "Lambda", "APIGateway", "Rust", "API"] +keywords = ["AWS", "Lambda", "APIGateway", "ALB", "API"] license = "Apache-2.0" homepage = "https://github.com/awslabs/aws-lambda-rust-runtime" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" @@ -30,3 +30,4 @@ serde_urlencoded = "0.5" [dev-dependencies] log = "^0.4" simple_logger = "^1" + diff --git a/lambda-http/src/body.rs b/lambda-http/src/body.rs index baf954e0..23cf43e2 100644 --- a/lambda-http/src/body.rs +++ b/lambda-http/src/body.rs @@ -1,4 +1,4 @@ -//! Provides an API Gateway oriented request and response body entity interface +//! Provides an ALB / API Gateway oriented request and response body entity interface use std::{borrow::Cow, ops::Deref}; @@ -6,14 +6,14 @@ use base64::display::Base64Display; use serde::ser::{Error as SerError, Serialize, Serializer}; /// Representation of http request and response bodies as supported -/// by API Gateway. +/// by API Gateway and ALBs. /// /// These come in three flavors /// * `Empty` ( no body ) /// * `Text` ( text data ) /// * `Binary` ( binary data ) /// -/// Body types can be `Deref` and `AsRef`'d into `[u8]` types much like the `hyper` crate +/// Body types can be `Deref` and `AsRef`'d into `[u8]` types much like the [hyper crate](https://crates.io/crates/hyper) /// /// # Examples /// diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index 6b4c76ab..84fdead9 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -1,4 +1,4 @@ -//! API Gateway extension methods for `http::Request` types +//! ALB and API Gateway extension methods for `http::Request` types use failure::Fail; use http::{header::CONTENT_TYPE, Request as HttpRequest}; @@ -8,14 +8,18 @@ use serde_urlencoded; use crate::{request::RequestContext, strmap::StrMap}; -/// API gateway pre-parsed http query string parameters +/// ALB/API gateway pre-parsed http query string parameters pub(crate) struct QueryStringParameters(pub(crate) StrMap); /// API gateway pre-extracted url path parameters +/// +/// These will always be empty for ALB requests pub(crate) struct PathParameters(pub(crate) StrMap); /// API gateway configured /// [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html) +/// +/// These will always be empty for ALB requests pub(crate) struct StageVariables(pub(crate) StrMap); /// Payload deserialization errors @@ -30,9 +34,13 @@ pub enum PayloadError { } /// Extentions for `lambda_http::Request` structs that -/// provide access to [API gateway features](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format) +/// 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. +/// +/// # Examples /// -/// In addition, you can also access a request's body in deserialized format +/// You can also access a request's body in deserialized format /// for payloads sent in `application/x-www-form-urlencoded` or /// `application/x-www-form-urlencoded` format /// @@ -87,16 +95,23 @@ pub trait RequestExt { /// No query parameters /// will yield an empty `StrMap`. fn query_string_parameters(&self) -> StrMap; + /// Return pre-extracted path parameters, parameter provided in url placeholders /// `/foo/{bar}/baz/{boom}`, /// associated with the API gateway request. No path parameters /// will yield an empty `StrMap` + /// + /// These will always be empty for ALB triggered requests fn path_parameters(&self) -> StrMap; + /// Return [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html) /// associated with the API gateway request. No stage parameters /// will yield an empty `StrMap` + /// + /// These will always be empty for ALB triggered requests fn stage_variables(&self) -> StrMap; - /// Return request context data assocaited with the API gateway request + + /// Return request context data assocaited with the ALB or API gateway request fn request_context(&self) -> RequestContext; /// Return the Result of a payload parsed into a serde Deserializeable @@ -162,7 +177,7 @@ mod tests { use serde_derive::Deserialize; use std::collections::HashMap; - use crate::{GatewayRequest, RequestExt, StrMap}; + use crate::{LambdaRequest, RequestExt, StrMap}; #[test] fn requests_have_query_string_ext() { @@ -170,13 +185,13 @@ mod tests { headers.insert("Host", "www.rust-lang.org".parse().unwrap()); let mut query = HashMap::new(); query.insert("foo".to_owned(), vec!["bar".to_owned()]); - let gwr: GatewayRequest<'_> = GatewayRequest { + let lambda_request = LambdaRequest { path: "/foo".into(), headers, query_string_parameters: StrMap(query.clone().into()), - ..GatewayRequest::default() + ..LambdaRequest::default() }; - let actual = HttpRequest::from(gwr); + let actual = HttpRequest::from(lambda_request); assert_eq!(actual.query_string_parameters(), StrMap(query.clone().into())); } @@ -190,13 +205,13 @@ mod tests { foo: String, baz: usize, } - let gwr: GatewayRequest<'_> = GatewayRequest { + let lambda_request = LambdaRequest { path: "/foo".into(), headers, body: Some("foo=bar&baz=2".into()), - ..GatewayRequest::default() + ..LambdaRequest::default() }; - let actual = HttpRequest::from(gwr); + let actual = HttpRequest::from(lambda_request); let payload: Option = actual.payload().unwrap_or_default(); assert_eq!( payload, @@ -212,13 +227,13 @@ mod tests { let mut headers = HeaderMap::new(); headers.insert("Host", "www.rust-lang.org".parse().unwrap()); headers.insert("Content-Type", "application/x-www-form-urlencoded".parse().unwrap()); - let gwr: GatewayRequest<'_> = GatewayRequest { + let lambda_request = LambdaRequest { path: "/foo".into(), headers, body: Some("foo=bar&baz=2".into()), - ..GatewayRequest::default() + ..LambdaRequest::default() }; - let actual = HttpRequest::from(gwr); + let actual = HttpRequest::from(lambda_request); let mut expected = HashMap::new(); expected.insert("foo".to_string(), "bar".to_string()); expected.insert("baz".to_string(), "2".to_string()); @@ -236,11 +251,11 @@ mod tests { foo: String, baz: usize, } - let gwr: GatewayRequest<'_> = GatewayRequest { + let gwr: LambdaRequest<'_> = LambdaRequest { path: "/foo".into(), headers, body: Some(r#"{"foo":"bar", "baz": 2}"#.into()), - ..GatewayRequest::default() + ..LambdaRequest::default() }; let actual = HttpRequest::from(gwr); let payload: Option = actual.payload().unwrap_or_default(); diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 8014a0fd..56402a1a 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -1,18 +1,26 @@ #![warn(missing_docs)] //#![deny(warnings)] -//! Enriches `lambda_runtime` with `http` types targeting API Gateway proxy events +//! Enriches the `lambda_runtime` crate with [http](https://github.com/hyperium/http) +//! types targeting ALB and API Gateway proxy events. //! -//! # Example +//! Though ALB and API Gateway proxy events are separate Lambda triggers, they both share +//! similar shapes that contextually map to an http request handler. From a application perspective +//! the differences shouldn't matter. This crate +//! abstracts over both using standard [http](https://github.com/hyperium/http) types allowing +//! you to focus more on your application while giving you to the flexibility to +//! transparently use whichever http trigger suits your application's needs best. +//! +//! # Examples //! //! ```rust,no_run //! use lambda_http::{lambda, IntoResponse, Request, RequestExt}; //! use lambda_runtime::{Context, HandlerError}; //! //! fn main() { -//! lambda!(handler) +//! lambda!(hello) //! } //! -//! fn handler( +//! fn hello( //! request: Request, //! _ctx: Context //! ) -> Result { @@ -56,12 +64,12 @@ mod response; mod strmap; pub use crate::{body::Body, ext::RequestExt, response::IntoResponse, strmap::StrMap}; -use crate::{request::GatewayRequest, response::GatewayResponse}; +use crate::{request::LambdaRequest, response::LambdaResponse}; /// Type alias for `http::Request`s with a fixed `lambda_http::Body` body pub type Request = http::Request; -/// Functions acting as API Gateway handlers must conform to this type. +/// Functions serving as ALB and API Gateway handlers must conform to this type. pub trait Handler { /// Run the handler. fn run(&mut self, event: Request, ctx: Context) -> Result; @@ -76,7 +84,7 @@ where } } -/// Creates a new `lambda_runtime::Runtime` and begins polling for API Gateway events +/// Creates a new `lambda_runtime::Runtime` and begins polling for ALB and API Gateway events /// /// # Arguments /// @@ -91,15 +99,16 @@ where // handler requires a mutable ref let mut func = f; lambda::start( - |req: GatewayRequest<'_>, ctx: Context| { + |req: LambdaRequest<'_>, ctx: Context| { + let is_alb = req.request_context.is_alb(); func.run(req.into(), ctx) - .map(|resp| GatewayResponse::from(resp.into_response())) + .map(|resp| LambdaResponse::from_response(is_alb, resp.into_response())) }, runtime, ) } -/// A macro for starting new handler's poll for API Gateway events +/// A macro for starting new handler's poll for API Gateway and ALB events #[macro_export] macro_rules! lambda { ($handler:expr) => { diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 2fcca05e..4d5fbec5 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -1,6 +1,8 @@ -//! API Gateway request types. Typically these are exposed via the `request_context` -//! request extension method provided by [lambda_http::RequestExt](trait.RequestExt.html) - +//! ALB andAPI Gateway request types. +//! +//! Typically these are exposed via the `request_context` +//! request extension method provided by [lambda_http::RequestExt](../trait.RequestExt.html) +//! use std::{borrow::Cow, collections::HashMap, fmt, mem}; use http::{ @@ -21,25 +23,32 @@ use crate::{ strmap::StrMap, }; -/// Representation of an API Gateway proxy event data +/// Internal representation of an Lambda http event from both +/// both ALB and API Gateway proxy event perspectives #[doc(hidden)] #[derive(Deserialize, Debug, Default)] #[serde(rename_all = "camelCase")] -pub(crate) struct GatewayRequest<'a> { +pub(crate) struct LambdaRequest<'a> { pub(crate) path: Cow<'a, str>, #[serde(deserialize_with = "deserialize_method")] pub(crate) http_method: Method, #[serde(deserialize_with = "deserialize_headers")] pub(crate) headers: HeaderMap, + /// For alb events these are only present when + /// the `lambda.multi_value_headers.enabled` target group setting turned on #[serde(default, deserialize_with = "deserialize_multi_value_headers")] pub(crate) multi_value_headers: HeaderMap, #[serde(deserialize_with = "nullable_default")] pub(crate) query_string_parameters: StrMap, + /// For alb events these are only present when + /// the `lambda.multi_value_headers.enabled` target group setting turned on #[serde(default, deserialize_with = "nullable_default")] pub(crate) multi_value_query_string_parameters: StrMap, - #[serde(deserialize_with = "nullable_default")] + /// alb events do not have path parameters + #[serde(default, deserialize_with = "nullable_default")] pub(crate) path_parameters: StrMap, - #[serde(deserialize_with = "nullable_default")] + /// alb events do not have stage variables + #[serde(default, deserialize_with = "nullable_default")] pub(crate) stage_variables: StrMap, pub(crate) body: Option>, #[serde(default)] @@ -47,24 +56,66 @@ pub(crate) struct GatewayRequest<'a> { pub(crate) request_context: RequestContext, } -/// API Gateway request context +/// Event request context as an enumeration of request contexts +/// for both ALB and API Gateway http events +#[derive(Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum RequestContext { + /// Api Gateway request context + #[serde(rename_all = "camelCase")] + ApiGateway { + //pub path: String, + account_id: String, + resource_id: String, + stage: String, + request_id: String, + resource_path: String, + http_method: String, + #[serde(default)] + authorizer: HashMap, + api_id: String, + identity: Identity, + }, + /// ALB request context + #[serde(rename_all = "camelCase")] + Alb { elb: Elb }, +} + +impl Default for RequestContext { + fn default() -> Self { + RequestContext::ApiGateway { + account_id: Default::default(), + resource_id: Default::default(), + stage: Default::default(), + request_id: Default::default(), + resource_path: Default::default(), + http_method: Default::default(), + authorizer: Default::default(), + api_id: Default::default(), + identity: Default::default(), + } + } +} + +impl RequestContext { + /// Return true if this request context represents an ALB request + pub fn is_alb(&self) -> bool { + match self { + RequestContext::Alb { .. } => true, + _ => false, + } + } +} + +/// Elastic load balancer context information #[derive(Deserialize, Debug, Default, Clone)] #[serde(rename_all = "camelCase")] -pub struct RequestContext { - //pub path: String, - pub account_id: String, - pub resource_id: String, - pub stage: String, - pub request_id: String, - pub resource_path: String, - pub http_method: String, - #[serde(default)] - pub authorizer: HashMap, - pub api_id: String, - pub identity: Identity, +pub struct Elb { + /// AWS ARN identifier for the ELB Target Group this lambda was triggered by + pub target_group_arn: String, } -/// Identity assoicated with request +/// Identity assoicated with API Gateway request #[derive(Deserialize, Debug, Default, Clone)] #[serde(rename_all = "camelCase")] pub struct Identity { @@ -194,9 +245,9 @@ where Ok(opt.unwrap_or_else(T::default)) } -impl<'a> From> for HttpRequest { - fn from(value: GatewayRequest<'_>) -> Self { - let GatewayRequest { +impl<'a> From> for HttpRequest { + fn from(value: LambdaRequest<'_>) -> Self { + let LambdaRequest { path, http_method, headers, @@ -210,12 +261,16 @@ impl<'a> From> for HttpRequest { request_context, } = value; - // build an http::Request from a lambda_http::GatewayRequest + // build an http::Request from a lambda_http::LambdaRequest let mut builder = HttpRequest::builder(); builder.method(http_method); builder.uri({ format!( - "https://{}{}", + "{}://{}{}", + headers + .get("X-Forwarded-Proto") + .map(|val| val.to_str().unwrap_or_else(|_| "https")) + .unwrap_or_else(|| "https"), headers .get(HOST) .map(|val| val.to_str().unwrap_or_default()) @@ -281,10 +336,10 @@ mod tests { fn requests_convert() { let mut headers = HeaderMap::new(); headers.insert("Host", "www.rust-lang.org".parse().unwrap()); - let gwr: GatewayRequest<'_> = GatewayRequest { + let gwr: LambdaRequest<'_> = LambdaRequest { path: "/foo".into(), headers, - ..GatewayRequest::default() + ..LambdaRequest::default() }; let expected = HttpRequest::get("https://www.rust-lang.org/foo").body(()).unwrap(); let actual = HttpRequest::from(gwr); @@ -294,20 +349,29 @@ mod tests { } #[test] - fn deserializes_request_events() { + fn deserializes_apigw_request_events() { // from the docs // https://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-api-gateway-request let input = include_str!("../tests/data/apigw_proxy_request.json"); - let result = serde_json::from_str::>(&input); + let result = serde_json::from_str::>(&input); assert!(result.is_ok(), format!("event was not parsed as expected {:?}", result)); } #[test] - fn deserialize_multi_value_events() { + fn deserialize_alb_request_events() { + // from the docs + // https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers + let input = include_str!("../tests/data/alb_request.json"); + let result = serde_json::from_str::>(&input); + assert!(result.is_ok(), format!("event was not parsed as expected {:?}", result)); + } + + #[test] + fn deserializes_apigw_multi_value_request_events() { // from docs // https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format let input = include_str!("../tests/data/apigw_multi_value_proxy_request.json"); - let result = serde_json::from_str::>(&input); + let result = serde_json::from_str::>(&input); assert!( result.is_ok(), format!("event is was not parsed as expected {:?}", result) @@ -325,15 +389,25 @@ mod tests { } #[test] - fn implements_default() { + fn deserializes_alb_multi_value_request_events() { + // from docs + // https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + let input = include_str!("../tests/data/alb_multi_value_request.json"); + let result = serde_json::from_str::>(&input); + assert!( + result.is_ok(), + format!("event is was not parsed as expected {:?}", result) + ); + let apigw = result.unwrap(); + assert!(!apigw.query_string_parameters.is_empty()); + assert!(!apigw.multi_value_query_string_parameters.is_empty()); + let actual = HttpRequest::from(apigw); + + // test RequestExt#query_string_parameters does the right thing assert_eq!( - GatewayRequest { - path: "/foo".into(), - ..GatewayRequest::default() - } - .path, - "/foo" - ) + actual.query_string_parameters().get_all("myKey"), + Some(vec!["val1", "val2"]) + ); } #[test] diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 5258aa7c..94a77d0a 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -1,8 +1,5 @@ //! Response types -// Std -use std::ops::Not; - use http::{ header::{HeaderMap, HeaderValue, CONTENT_TYPE}, Response, @@ -18,26 +15,28 @@ use crate::body::Body; /// Representation of API Gateway response #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] -pub(crate) struct GatewayResponse { +pub(crate) struct LambdaResponse { pub status_code: u16, - #[serde(skip_serializing_if = "HeaderMap::is_empty", serialize_with = "serialize_headers")] + // ALB requires a statusDescription i.e. "200 OK" field but API Gateway returns an error + // when one is provided. only populate this for ALB responses + #[serde(skip_serializing_if = "Option::is_none")] + pub status_description: Option, + #[serde(serialize_with = "serialize_headers")] pub headers: HeaderMap, - #[serde( - skip_serializing_if = "HeaderMap::is_empty", - serialize_with = "serialize_multi_value_headers" - )] + #[serde(serialize_with = "serialize_multi_value_headers")] pub multi_value_headers: HeaderMap, #[serde(skip_serializing_if = "Option::is_none")] pub body: Option, - #[serde(skip_serializing_if = "Not::not")] + // This field is optional for API Gateway but required for ALB pub is_base64_encoded: bool, } #[cfg(test)] -impl Default for GatewayResponse { +impl Default for LambdaResponse { fn default() -> Self { Self { status_code: 200, + status_description: Default::default(), headers: Default::default(), multi_value_headers: Default::default(), body: Default::default(), @@ -75,19 +74,29 @@ where map.end() } -impl From> for GatewayResponse -where - T: Into, -{ - fn from(value: Response) -> Self { +/// tranformation from http type to internal type +impl LambdaResponse { + pub(crate) fn from_response(is_alb: bool, value: Response) -> Self + where + T: Into, + { let (parts, bod) = value.into_parts(); let (is_base64_encoded, body) = match bod.into() { Body::Empty => (false, None), b @ Body::Text(_) => (false, Some(b)), b @ Body::Binary(_) => (true, Some(b)), }; - GatewayResponse { + Self { status_code: parts.status.as_u16(), + status_description: if is_alb { + Some(format!( + "{} {}", + parts.status.as_u16(), + parts.status.canonical_reason().unwrap_or_default() + )) + } else { + None + }, body, headers: parts.headers.clone(), multi_value_headers: parts.headers, @@ -151,7 +160,7 @@ impl IntoResponse for serde_json::Value { #[cfg(test)] mod tests { - use super::{Body, GatewayResponse, IntoResponse}; + use super::{Body, LambdaResponse, IntoResponse}; use http::{header::CONTENT_TYPE, Response}; use serde_json::{self, json}; @@ -182,39 +191,41 @@ mod tests { #[test] fn default_response() { - assert_eq!(GatewayResponse::default().status_code, 200) + assert_eq!(LambdaResponse::default().status_code, 200) } #[test] fn serialize_default() { assert_eq!( - serde_json::to_string(&GatewayResponse::default()).expect("failed to serialize response"), - r#"{"statusCode":200}"# + serde_json::to_string(&LambdaResponse::default()).expect("failed to serialize response"), + r#"{"statusCode":200,"headers":{},"multiValueHeaders":{},"isBase64Encoded":false}"# ); } #[test] fn serialize_body() { - let mut resp = GatewayResponse::default(); + let mut resp = LambdaResponse::default(); resp.body = Some("foo".into()); assert_eq!( serde_json::to_string(&resp).expect("failed to serialize response"), - r#"{"statusCode":200,"body":"foo"}"# + r#"{"statusCode":200,"headers":{},"multiValueHeaders":{},"body":"foo","isBase64Encoded":false}"# ); } #[test] fn serialize_multi_value_headers() { - let res: GatewayResponse = Response::builder() - .header("multi", "a") - .header("multi", "b") - .body(Body::from(())) - .expect("failed to create response") - .into(); + let res = LambdaResponse::from_response( + false, + Response::builder() + .header("multi", "a") + .header("multi", "b") + .body(Body::from(())) + .expect("failed to create response"), + ); let json = serde_json::to_string(&res).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{"multi":"a"},"multiValueHeaders":{"multi":["a","b"]}}"# + r#"{"statusCode":200,"headers":{"multi":"a"},"multiValueHeaders":{"multi":["a","b"]},"isBase64Encoded":false}"# ) } } diff --git a/lambda-http/tests/data/alb_health_check.json b/lambda-http/tests/data/alb_health_check.json new file mode 100644 index 00000000..e1289abd --- /dev/null +++ b/lambda-http/tests/data/alb_health_check.json @@ -0,0 +1,15 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09" + } + }, + "httpMethod": "GET", + "path": "/", + "queryStringParameters": {}, + "headers": { + "user-agent": "ELB-HealthChecker/2.0" + }, + "body": "", + "isBase64Encoded": false +} \ No newline at end of file diff --git a/lambda-http/tests/data/alb_multi_value_request.json b/lambda-http/tests/data/alb_multi_value_request.json new file mode 100644 index 00000000..10cf1155 --- /dev/null +++ b/lambda-http/tests/data/alb_multi_value_request.json @@ -0,0 +1,37 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09" + } + }, + "httpMethod": "GET", + "path": "/", + "queryStringParameters": { "myKey": "val2" }, + "multiValueQueryStringParameters": { "myKey": ["val1", "val2"] }, + "headers": { + "accept": "text/html,application/xhtml+xml", + "accept-language": "en-US,en;q=0.8", + "content-type": "text/plain", + "cookie": "name1=value1", + "host": "lambda-846800462-us-east-2.elb.amazonaws.com", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)", + "x-amzn-trace-id": "Root=1-5bdb40ca-556d8b0c50dc66f0511bf520", + "x-forwarded-for": "72.21.198.66", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "multiValueHeaders": { + "accept": ["text/html,application/xhtml+xml"], + "accept-language": ["en-US,en;q=0.8"], + "content-type": ["text/plain"], + "cookie": ["name1=value1", "name2=value2"], + "host": ["lambda-846800462-us-east-2.elb.amazonaws.com"], + "user-agent": ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)"], + "x-amzn-trace-id": ["Root=1-5bdb40ca-556d8b0c50dc66f0511bf520"], + "x-forwarded-for": ["72.21.198.66"], + "x-forwarded-port": ["443"], + "x-forwarded-proto": ["https"] + }, + "isBase64Encoded": false, + "body": "request_body" +} \ No newline at end of file diff --git a/lambda-http/tests/data/alb_request.json b/lambda-http/tests/data/alb_request.json new file mode 100644 index 00000000..8ee0432f --- /dev/null +++ b/lambda-http/tests/data/alb_request.json @@ -0,0 +1,24 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09" + } + }, + "httpMethod": "GET", + "path": "/", + "queryStringParameters": { "myKey": "val2"}, + "headers": { + "accept": "text/html,application/xhtml+xml", + "accept-language": "en-US,en;q=0.8", + "content-type": "text/plain", + "cookie": "cookies", + "host": "lambda-846800462-us-east-2.elb.amazonaws.com", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)", + "x-amzn-trace-id": "Root=1-5bdb40ca-556d8b0c50dc66f0511bf520", + "x-forwarded-for": "72.21.198.66", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "isBase64Encoded": false, + "body": "request_body" +} \ No newline at end of file From 748918dba28137cf8f31a5e64bdca3e566abd5ce Mon Sep 17 00:00:00 2001 From: softprops Date: Sat, 29 Dec 2018 23:57:45 +0900 Subject: [PATCH 2/4] rustfmt --- lambda-http/src/response.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 94a77d0a..4e2d3ae5 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -160,7 +160,7 @@ impl IntoResponse for serde_json::Value { #[cfg(test)] mod tests { - use super::{Body, LambdaResponse, IntoResponse}; + use super::{Body, IntoResponse, LambdaResponse}; use http::{header::CONTENT_TYPE, Response}; use serde_json::{self, json}; From d5409ee3454910aa62e9481af984315c7168b864 Mon Sep 17 00:00:00 2001 From: softprops Date: Sun, 30 Dec 2018 18:54:21 +0900 Subject: [PATCH 3/4] not specific apigw now --- lambda-http/src/ext.rs | 4 ++-- lambda-http/src/request.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index 84fdead9..d711ff64 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -251,13 +251,13 @@ mod tests { foo: String, baz: usize, } - let gwr: LambdaRequest<'_> = LambdaRequest { + let lambda_request: LambdaRequest<'_> = LambdaRequest { path: "/foo".into(), headers, body: Some(r#"{"foo":"bar", "baz": 2}"#.into()), ..LambdaRequest::default() }; - let actual = HttpRequest::from(gwr); + let actual = HttpRequest::from(lambda_request); let payload: Option = actual.payload().unwrap_or_default(); assert_eq!( payload, diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 4d5fbec5..e8f78331 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -336,13 +336,13 @@ mod tests { fn requests_convert() { let mut headers = HeaderMap::new(); headers.insert("Host", "www.rust-lang.org".parse().unwrap()); - let gwr: LambdaRequest<'_> = LambdaRequest { + let lambda_request: LambdaRequest<'_> = LambdaRequest { path: "/foo".into(), headers, ..LambdaRequest::default() }; let expected = HttpRequest::get("https://www.rust-lang.org/foo").body(()).unwrap(); - let actual = HttpRequest::from(gwr); + let actual = HttpRequest::from(lambda_request); assert_eq!(expected.method(), actual.method()); assert_eq!(expected.uri(), actual.uri()); assert_eq!(expected.method(), actual.method()); From d5cdf29590f5e1249148c3cf102e8110e8b336dc Mon Sep 17 00:00:00 2001 From: softprops Date: Sun, 30 Dec 2018 18:58:01 +0900 Subject: [PATCH 4/4] sentence casing --- lambda-http/src/request.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index e8f78331..0c3d9d58 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -44,10 +44,10 @@ pub(crate) struct LambdaRequest<'a> { /// the `lambda.multi_value_headers.enabled` target group setting turned on #[serde(default, deserialize_with = "nullable_default")] pub(crate) multi_value_query_string_parameters: StrMap, - /// alb events do not have path parameters + /// ALB events do not have path parameters. #[serde(default, deserialize_with = "nullable_default")] pub(crate) path_parameters: StrMap, - /// alb events do not have stage variables + /// ALB events do not have stage variables. #[serde(default, deserialize_with = "nullable_default")] pub(crate) stage_variables: StrMap, pub(crate) body: Option>,