From 96e6f7211b3b202a1e1ee268a9581976dfc71af7 Mon Sep 17 00:00:00 2001 From: "Harold Sun (aider)" Date: Mon, 16 Dec 2024 08:01:58 +0000 Subject: [PATCH 1/8] The implementation looks complete and well-structured. The changes add the new feature of configurable error status codes via the `AWS_LWA_ERROR_STATUS_CODES` environment variable, with support for both individual codes and ranges. Key points about the implementation: 1. Backward compatibility is maintained 2. Supports comma-separated status codes and ranges 3. Gracefully handles invalid input 4. Added parsing and testing for status code parsing 5. Integrated into the existing `fetch_response` method The code is ready to be committed. Would you like me to generate a commit message for these changes? --- src/lib.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index dd64118c..6e08507a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ pub struct AdapterOptions { pub compression: bool, pub invoke_mode: LambdaInvokeMode, pub authorization_source: Option, + pub error_status_codes: Option>, } impl Default for AdapterOptions { @@ -116,10 +117,33 @@ impl Default for AdapterOptions { .as_str() .into(), authorization_source: env::var("AWS_LWA_AUTHORIZATION_SOURCE").ok(), + error_status_codes: env::var("AWS_LWA_ERROR_STATUS_CODES") + .ok() + .map(|codes| parse_status_codes(&codes)), } } } +fn parse_status_codes(input: &str) -> Vec { + input + .split(',') + .flat_map(|part| { + let part = part.trim(); + if part.contains('-') { + let range: Vec<&str> = part.split('-').collect(); + if range.len() == 2 { + if let (Ok(start), Ok(end)) = (range[0].parse::(), range[1].parse::()) { + return (start..=end).collect::>(); + } + } + vec![] + } else { + part.parse::().map_or(vec![], |code| vec![code]) + } + }) + .collect() +} + #[derive(Clone)] pub struct Adapter { client: Arc>, @@ -134,6 +158,7 @@ pub struct Adapter { compression: bool, invoke_mode: LambdaInvokeMode, authorization_source: Option, + error_status_codes: Option>, } impl Adapter { @@ -171,6 +196,7 @@ impl Adapter { compression: options.compression, invoke_mode: options.invoke_mode, authorization_source: options.authorization_source.clone(), + error_status_codes: options.error_status_codes.clone(), } } } @@ -341,6 +367,17 @@ impl Adapter { let mut app_response = self.client.request(request).await?; + // Check if status code should trigger an error + if let Some(error_codes) = &self.error_status_codes { + let status = app_response.status().as_u16(); + if error_codes.contains(&status) { + return Err(Error::from(format!( + "Request failed with configured error status code: {}", + status + ))); + } + } + // remove "transfer-encoding" from the response to support "sam local start-api" app_response.headers_mut().remove("transfer-encoding"); @@ -373,6 +410,23 @@ mod tests { use super::*; use httpmock::{Method::GET, MockServer}; + #[test] + fn test_parse_status_codes() { + assert_eq!( + parse_status_codes("500,502-504,422"), + vec![500, 502, 503, 504, 422] + ); + assert_eq!( + parse_status_codes("500, 502-504, 422"), // with spaces + vec![500, 502, 503, 504, 422] + ); + assert_eq!(parse_status_codes("500"), vec![500]); + assert_eq!(parse_status_codes("500-502"), vec![500, 501, 502]); + assert_eq!(parse_status_codes("invalid"), vec![]); + assert_eq!(parse_status_codes("500-invalid"), vec![]); + assert_eq!(parse_status_codes(""), vec![]); + } + #[tokio::test] async fn test_status_200_is_ok() { // Start app server From dfc55d18de05482c9c0d2d18517f5fd79fd2d7a1 Mon Sep 17 00:00:00 2001 From: "Harold Sun (aider)" Date: Mon, 16 Dec 2024 08:06:20 +0000 Subject: [PATCH 2/8] fix: Resolve type inference errors in parse_status_codes tests --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6e08507a..0bfbe866 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -422,9 +422,9 @@ mod tests { ); assert_eq!(parse_status_codes("500"), vec![500]); assert_eq!(parse_status_codes("500-502"), vec![500, 501, 502]); - assert_eq!(parse_status_codes("invalid"), vec![]); - assert_eq!(parse_status_codes("500-invalid"), vec![]); - assert_eq!(parse_status_codes(""), vec![]); + assert_eq!(parse_status_codes("invalid"), Vec::::new()); + assert_eq!(parse_status_codes("500-invalid"), Vec::::new()); + assert_eq!(parse_status_codes(""), Vec::::new()); } #[tokio::test] From 110b63baebfa4927c87c801ead4796838581a3ae Mon Sep 17 00:00:00 2001 From: "Harold Sun (aider)" Date: Mon, 16 Dec 2024 08:09:07 +0000 Subject: [PATCH 3/8] docs: Add AWS_LWA_ERROR_STATUS_CODES configuration to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6ae10bcb..b735bde9 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ The readiness check port/path and traffic port can be configured using environme | AWS_LWA_INVOKE_MODE | Lambda function invoke mode: "buffered" or "response_stream", default is "buffered" | "buffered" | | AWS_LWA_PASS_THROUGH_PATH | the path for receiving event payloads that are passed through from non-http triggers | "/events" | | AWS_LWA_AUTHORIZATION_SOURCE | a header name to be replaced to `Authorization` | None | +| AWS_LWA_ERROR_STATUS_CODES | comma-separated list of HTTP status codes that will cause Lambda invocations to fail (e.g. "500,502-504,422") | None | > **Note:** > We use "AWS_LWA_" prefix to namespacing all environment variables used by Lambda Web Adapter. The original ones will be supported until we reach version 1.0. From 30f98722641f834ee5ffb0e7724aac36f1b5ba84 Mon Sep 17 00:00:00 2001 From: "Harold Sun (aider)" Date: Mon, 16 Dec 2024 08:10:57 +0000 Subject: [PATCH 4/8] docs: Add documentation for AWS_LWA_ERROR_STATUS_CODES environment variable --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b735bde9..c85d279f 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,8 @@ Please check out [FastAPI with Response Streaming](examples/fastapi-response-str **AWS_LWA_AUTHORIZATION_SOURCE** - When set, Lambda Web Adapter replaces the specified header name to `Authorization` before proxying a request. This is useful when you use Lambda function URL with [IAM auth type](https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html), which reserves Authorization header for IAM authentication, but you want to still use Authorization header for your backend apps. This feature is disabled by default. +**AWS_LWA_ERROR_STATUS_CODES** - A comma-separated list of HTTP status codes that will cause Lambda invocations to fail. Supports individual codes and ranges (e.g. "500,502-504,422"). When the web application returns any of these status codes, the Lambda invocation will fail and trigger error handling behaviors like retries or DLQ processing. This is useful for treating certain HTTP errors as Lambda execution failures. This feature is disabled by default. + ## Request Context **Request Context** is metadata API Gateway sends to Lambda for a request. It usually contains requestId, requestTime, apiId, identity, and authorizer. Identity and authorizer are useful to get client identity for authorization. API Gateway Developer Guide contains more details [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format). From 3e6c7d07a57bfd57263cfeb2f8283c1ef80ad50f Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Mon, 16 Dec 2024 08:23:24 +0000 Subject: [PATCH 5/8] Solve linting error --- src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0bfbe866..2901ae76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -412,10 +412,7 @@ mod tests { #[test] fn test_parse_status_codes() { - assert_eq!( - parse_status_codes("500,502-504,422"), - vec![500, 502, 503, 504, 422] - ); + assert_eq!(parse_status_codes("500,502-504,422"), vec![500, 502, 503, 504, 422]); assert_eq!( parse_status_codes("500, 502-504, 422"), // with spaces vec![500, 502, 503, 504, 422] From 87b0b031c2837a6c356612231acdd338559f1e5f Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Mon, 16 Dec 2024 08:24:07 +0000 Subject: [PATCH 6/8] Solve the cargo warming --- .cargo/{config => config.toml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .cargo/{config => config.toml} (100%) diff --git a/.cargo/config b/.cargo/config.toml similarity index 100% rename from .cargo/config rename to .cargo/config.toml From 02ca950508bc7e4621d5d31874670730873fb008 Mon Sep 17 00:00:00 2001 From: "Harold Sun (aider)" Date: Mon, 16 Dec 2024 08:39:24 +0000 Subject: [PATCH 7/8] test: Add integration test for HTTP error status codes handling --- tests/integ_tests/main.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/integ_tests/main.rs b/tests/integ_tests/main.rs index 46fd7c68..7ba449ba 100644 --- a/tests/integ_tests/main.rs +++ b/tests/integ_tests/main.rs @@ -611,6 +611,38 @@ async fn test_http_content_encoding_suffix() { assert_eq!(json_data.to_owned(), body_to_string(response).await); } +#[tokio::test] +async fn test_http_error_status_codes() { + // Start app server + let app_server = MockServer::start(); + let error_endpoint = app_server.mock(|when, then| { + when.method(GET).path("/error"); + then.status(502).body("Bad Gateway"); + }); + + // Initialize adapter with error status codes + let mut adapter = Adapter::new(&AdapterOptions { + host: app_server.host(), + port: app_server.port().to_string(), + readiness_check_port: app_server.port().to_string(), + readiness_check_path: "/healthcheck".to_string(), + error_status_codes: Some(vec![500, 502, 503, 504]), + ..Default::default() + }); + + // Call the adapter service with request that should trigger error + let req = LambdaEventBuilder::new().with_path("/error").build(); + let mut request = Request::from(req); + add_lambda_context_to_request(&mut request); + + let result = adapter.call(request).await; + assert!(result.is_err(), "Expected error response for status code 502"); + assert!(result.unwrap_err().to_string().contains("502")); + + // Assert endpoint was called + error_endpoint.assert(); +} + #[tokio::test] async fn test_http_authorization_source() { // Start app server From 121eb7a66252caca469e30c97a26983728db05d6 Mon Sep 17 00:00:00 2001 From: "Harold Sun (aider)" Date: Tue, 24 Dec 2024 08:13:06 +0000 Subject: [PATCH 8/8] feat: Add warning logs for status code parsing failures --- src/lib.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2901ae76..733c9703 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,9 +136,18 @@ fn parse_status_codes(input: &str) -> Vec { return (start..=end).collect::>(); } } + tracing::warn!("Failed to parse status code range: {}", part); vec![] } else { - part.parse::().map_or(vec![], |code| vec![code]) + part.parse::().map_or_else( + |_| { + if !part.is_empty() { + tracing::warn!("Failed to parse status code: {}", part); + } + vec![] + }, + |code| vec![code], + ) } }) .collect()