From 31275929cf4dd796811b37e61f17479bc2dc4fff Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 28 Jun 2023 20:18:18 -0700 Subject: [PATCH 1/2] Fix APIGW path with stage APIGW HTTP has started adding the stage to the path in the event. This change checks if the stage is already a prefix in the path, and skips adding it if so. Signed-off-by: David Calavera --- lambda-http/src/request.rs | 40 +++++++++++-- lambda-http/tests/data/apigw_no_host.json | 2 +- .../tests/data/apigw_proxy_request.json | 2 +- ...w_v2_proxy_request_with_stage_in_path.json | 57 +++++++++++++++++++ 4 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 lambda-http/tests/data/apigw_v2_proxy_request_with_stage_in_path.json diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index ea418595..8f21cd9c 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -327,10 +327,17 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< #[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] fn apigw_path_with_stage(stage: &Option, path: &str) -> String { - match stage { - None => path.into(), - Some(stage) if stage == "$default" => path.into(), - Some(stage) => format!("/{stage}{path}"), + let stage = match stage { + None => return path.into(), + Some(stage) if stage == "$default" => return path.into(), + Some(stage) => stage, + }; + + let prefix = format!("/{stage}/"); + if path.starts_with(&prefix) { + path.into() + } else { + format!("/{stage}{path}") } } @@ -531,7 +538,7 @@ mod tests { assert_eq!(req.method(), "GET"); assert_eq!( req.uri(), - "https://wt6mne2s9k.execute-api.us-west-2.amazonaws.com/test/test/hello?name=me" + "https://wt6mne2s9k.execute-api.us-west-2.amazonaws.com/test/hello?name=me" ); // Ensure this is an APIGW request @@ -733,7 +740,7 @@ mod tests { ); let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); - assert_eq!(req.uri(), "/test/test/hello?name=me"); + assert_eq!(req.uri(), "/test/hello?name=me"); } #[test] @@ -768,4 +775,25 @@ mod tests { let url = build_request_uri("/path with spaces/and multiple segments", &HeaderMap::new(), None, None); assert_eq!("/path%20with%20spaces/and%20multiple%20segments", url); } + + #[test] + fn deserializes_apigw_http_request_with_stage_in_path() { + let input = include_str!("../tests/data/apigw_v2_proxy_request_with_stage_in_path.json"); + let result = from_str(input); + assert!( + result.is_ok(), + "event was not parsed as expected {result:?} given {input}" + ); + let req = result.expect("failed to parse request"); + assert_eq!("/Prod/my/path", req.uri().path()); + assert_eq!("/Prod/my/path", req.raw_http_path()); + } + + #[test] + fn test_apigw_path_with_stage() { + assert_eq!("/path", apigw_path_with_stage(&None, "/path")); + assert_eq!("/path", apigw_path_with_stage(&Some("$default".into()), "/path")); + assert_eq!("/Prod/path", apigw_path_with_stage(&Some("Prod".into()), "/Prod/path")); + assert_eq!("/Prod/path", apigw_path_with_stage(&Some("Prod".into()), "/path")); + } } diff --git a/lambda-http/tests/data/apigw_no_host.json b/lambda-http/tests/data/apigw_no_host.json index 3143c81b..78a40dee 100644 --- a/lambda-http/tests/data/apigw_no_host.json +++ b/lambda-http/tests/data/apigw_no_host.json @@ -1,5 +1,5 @@ { - "path": "/test/hello", + "path": "/hello", "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, lzma, sdch, br", diff --git a/lambda-http/tests/data/apigw_proxy_request.json b/lambda-http/tests/data/apigw_proxy_request.json index 3b7cc9d2..61183846 100644 --- a/lambda-http/tests/data/apigw_proxy_request.json +++ b/lambda-http/tests/data/apigw_proxy_request.json @@ -1,5 +1,5 @@ { - "path": "/test/hello", + "path": "/hello", "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, lzma, sdch, br", diff --git a/lambda-http/tests/data/apigw_v2_proxy_request_with_stage_in_path.json b/lambda-http/tests/data/apigw_v2_proxy_request_with_stage_in_path.json new file mode 100644 index 00000000..86e173c6 --- /dev/null +++ b/lambda-http/tests/data/apigw_v2_proxy_request_with_stage_in_path.json @@ -0,0 +1,57 @@ +{ + "version": "2.0", + "routeKey": "Prod", + "rawPath": "/Prod/my/path", + "rawQueryString": "parameter1=value1¶meter1=value2¶meter2=value", + "cookies": [ + "cookie1=value1", + "cookie2=value2" + ], + "headers": { + "Header1": "value1", + "Header2": "value2" + }, + "queryStringParameters": { + "parameter1": "value1,value2", + "parameter2": "value" + }, + "requestContext": { + "accountId": "123456789012", + "apiId": "api-id", + "authorizer": { + "jwt": { + "claims": { + "claim1": "value1", + "claim2": "value2" + }, + "scopes": [ + "scope1", + "scope2" + ] + } + }, + "domainName": "id.execute-api.us-east-1.amazonaws.com", + "domainPrefix": "id", + "http": { + "method": "POST", + "path": "/Prod/my/path", + "protocol": "HTTP/1.1", + "sourceIp": "IP", + "userAgent": "agent" + }, + "requestId": "id", + "routeKey": "Prod", + "stage": "Prod", + "time": "12/Mar/2020:19:03:58 +0000", + "timeEpoch": 1583348638390 + }, + "body": "Hello from Lambda", + "pathParameters": { + "parameter1": "value1" + }, + "isBase64Encoded": false, + "stageVariables": { + "stageVariable1": "value1", + "stageVariable2": "value2" + } +} \ No newline at end of file From 5dd0fbce42ae3c1902c88569f4ce2b12897f29c8 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 28 Jun 2023 20:34:48 -0700 Subject: [PATCH 2/2] Add env variable to shortcircuit stage behavior. There might be cases when you don't want the runtime to do anything with paths and stages. By setting AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH in the environment, we ignore this behavior completely. Signed-off-by: David Calavera --- lambda-http/src/request.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 8f21cd9c..bdb755ed 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -327,6 +327,10 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< #[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] fn apigw_path_with_stage(stage: &Option, path: &str) -> String { + if env::var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH").is_ok() { + return path.into(); + } + let stage = match stage { None => return path.into(), Some(stage) if stage == "$default" => return path.into(),