Skip to content

Commit 2318e3e

Browse files
authored
Force Lambda failure (#558)
* 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? * fix: Resolve type inference errors in parse_status_codes tests * docs: Add AWS_LWA_ERROR_STATUS_CODES configuration to README * docs: Add documentation for AWS_LWA_ERROR_STATUS_CODES environment variable * Solve linting error * Solve the cargo warming * test: Add integration test for HTTP error status codes handling * feat: Add warning logs for status code parsing failures
1 parent 7e35c3f commit 2318e3e

File tree

4 files changed

+95
-0
lines changed

4 files changed

+95
-0
lines changed
File renamed without changes.

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ The readiness check port/path and traffic port can be configured using environme
105105
| AWS_LWA_INVOKE_MODE | Lambda function invoke mode: "buffered" or "response_stream", default is "buffered" | "buffered" |
106106
| AWS_LWA_PASS_THROUGH_PATH | the path for receiving event payloads that are passed through from non-http triggers | "/events" |
107107
| AWS_LWA_AUTHORIZATION_SOURCE | a header name to be replaced to `Authorization` | None |
108+
| 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 |
108109

109110
> **Note:**
110111
> 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.
@@ -137,6 +138,8 @@ Please check out [FastAPI with Response Streaming](examples/fastapi-response-str
137138

138139
**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.
139140

141+
**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.
142+
140143
## Request Context
141144

142145
**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).

src/lib.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ pub struct AdapterOptions {
7878
pub compression: bool,
7979
pub invoke_mode: LambdaInvokeMode,
8080
pub authorization_source: Option<String>,
81+
pub error_status_codes: Option<Vec<u16>>,
8182
}
8283

8384
impl Default for AdapterOptions {
@@ -116,10 +117,42 @@ impl Default for AdapterOptions {
116117
.as_str()
117118
.into(),
118119
authorization_source: env::var("AWS_LWA_AUTHORIZATION_SOURCE").ok(),
120+
error_status_codes: env::var("AWS_LWA_ERROR_STATUS_CODES")
121+
.ok()
122+
.map(|codes| parse_status_codes(&codes)),
119123
}
120124
}
121125
}
122126

127+
fn parse_status_codes(input: &str) -> Vec<u16> {
128+
input
129+
.split(',')
130+
.flat_map(|part| {
131+
let part = part.trim();
132+
if part.contains('-') {
133+
let range: Vec<&str> = part.split('-').collect();
134+
if range.len() == 2 {
135+
if let (Ok(start), Ok(end)) = (range[0].parse::<u16>(), range[1].parse::<u16>()) {
136+
return (start..=end).collect::<Vec<_>>();
137+
}
138+
}
139+
tracing::warn!("Failed to parse status code range: {}", part);
140+
vec![]
141+
} else {
142+
part.parse::<u16>().map_or_else(
143+
|_| {
144+
if !part.is_empty() {
145+
tracing::warn!("Failed to parse status code: {}", part);
146+
}
147+
vec![]
148+
},
149+
|code| vec![code],
150+
)
151+
}
152+
})
153+
.collect()
154+
}
155+
123156
#[derive(Clone)]
124157
pub struct Adapter<C, B> {
125158
client: Arc<Client<C, B>>,
@@ -134,6 +167,7 @@ pub struct Adapter<C, B> {
134167
compression: bool,
135168
invoke_mode: LambdaInvokeMode,
136169
authorization_source: Option<String>,
170+
error_status_codes: Option<Vec<u16>>,
137171
}
138172

139173
impl Adapter<HttpConnector, Body> {
@@ -171,6 +205,7 @@ impl Adapter<HttpConnector, Body> {
171205
compression: options.compression,
172206
invoke_mode: options.invoke_mode,
173207
authorization_source: options.authorization_source.clone(),
208+
error_status_codes: options.error_status_codes.clone(),
174209
}
175210
}
176211
}
@@ -341,6 +376,17 @@ impl Adapter<HttpConnector, Body> {
341376

342377
let mut app_response = self.client.request(request).await?;
343378

379+
// Check if status code should trigger an error
380+
if let Some(error_codes) = &self.error_status_codes {
381+
let status = app_response.status().as_u16();
382+
if error_codes.contains(&status) {
383+
return Err(Error::from(format!(
384+
"Request failed with configured error status code: {}",
385+
status
386+
)));
387+
}
388+
}
389+
344390
// remove "transfer-encoding" from the response to support "sam local start-api"
345391
app_response.headers_mut().remove("transfer-encoding");
346392

@@ -373,6 +419,20 @@ mod tests {
373419
use super::*;
374420
use httpmock::{Method::GET, MockServer};
375421

422+
#[test]
423+
fn test_parse_status_codes() {
424+
assert_eq!(parse_status_codes("500,502-504,422"), vec![500, 502, 503, 504, 422]);
425+
assert_eq!(
426+
parse_status_codes("500, 502-504, 422"), // with spaces
427+
vec![500, 502, 503, 504, 422]
428+
);
429+
assert_eq!(parse_status_codes("500"), vec![500]);
430+
assert_eq!(parse_status_codes("500-502"), vec![500, 501, 502]);
431+
assert_eq!(parse_status_codes("invalid"), Vec::<u16>::new());
432+
assert_eq!(parse_status_codes("500-invalid"), Vec::<u16>::new());
433+
assert_eq!(parse_status_codes(""), Vec::<u16>::new());
434+
}
435+
376436
#[tokio::test]
377437
async fn test_status_200_is_ok() {
378438
// Start app server

tests/integ_tests/main.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,38 @@ async fn test_http_content_encoding_suffix() {
611611
assert_eq!(json_data.to_owned(), body_to_string(response).await);
612612
}
613613

614+
#[tokio::test]
615+
async fn test_http_error_status_codes() {
616+
// Start app server
617+
let app_server = MockServer::start();
618+
let error_endpoint = app_server.mock(|when, then| {
619+
when.method(GET).path("/error");
620+
then.status(502).body("Bad Gateway");
621+
});
622+
623+
// Initialize adapter with error status codes
624+
let mut adapter = Adapter::new(&AdapterOptions {
625+
host: app_server.host(),
626+
port: app_server.port().to_string(),
627+
readiness_check_port: app_server.port().to_string(),
628+
readiness_check_path: "/healthcheck".to_string(),
629+
error_status_codes: Some(vec![500, 502, 503, 504]),
630+
..Default::default()
631+
});
632+
633+
// Call the adapter service with request that should trigger error
634+
let req = LambdaEventBuilder::new().with_path("/error").build();
635+
let mut request = Request::from(req);
636+
add_lambda_context_to_request(&mut request);
637+
638+
let result = adapter.call(request).await;
639+
assert!(result.is_err(), "Expected error response for status code 502");
640+
assert!(result.unwrap_err().to_string().contains("502"));
641+
642+
// Assert endpoint was called
643+
error_endpoint.assert();
644+
}
645+
614646
#[tokio::test]
615647
async fn test_http_authorization_source() {
616648
// Start app server

0 commit comments

Comments
 (0)