Skip to content

Commit b4bf219

Browse files
committed
fix: remove user-set x-amzn-*-context headers
Currently, when a client sets their own `x-amzn-*-context` headers, they will be included in the request to the app server. This request to the lambda with the adapter installed: ``` $ curl -H "x-amzn-request-context: boom" http://localhost:8000 ``` Will result in this request from the adapter to the app server: ``` GET / HTTP/1.1 [...] x-amzn-request-context: boom x-amzn-request-context: [json context from adapter] x-amzn-lambda-context: [json context from adapter] [...] ``` Many implementations of web frameworks will take the *first* header when you access a header key. For example, Starlette/FastAPI does this: https://github.com/encode/starlette/blob/ad02ee6336faadadf97e0c79dd3a91759f1f32a7/starlette/datastructures.py#L562-L567 This patch removes every user-set `x-amzn-{request,lambda}-context` headers from the request before adding in JSON context from the adapter. Signed-off-by: Thitat Auareesuksakul <[email protected]>
1 parent 42e540b commit b4bf219

File tree

2 files changed

+90
-3
lines changed

2 files changed

+90
-3
lines changed

src/lib.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use std::{
1414
};
1515

1616
use http::{
17-
header::{HeaderName, HeaderValue},
17+
header::{Entry, HeaderName, HeaderValue},
1818
Method, StatusCode,
1919
};
2020
use hyper::{
@@ -359,12 +359,22 @@ where
359359

360360
let mut req_headers = parts.headers;
361361

362+
// remove "x-amzn-request-context" if already set
363+
if let Entry::Occupied(entry) = req_headers.entry("x-amzn-request-context") {
364+
entry.remove();
365+
}
366+
362367
// include request context in http header "x-amzn-request-context"
363368
req_headers.append(
364369
HeaderName::from_static("x-amzn-request-context"),
365370
HeaderValue::from_bytes(serde_json::to_string(&request_context)?.as_bytes())?,
366371
);
367372

373+
// remove "x-amzn-lambda-context" if already set
374+
if let Entry::Occupied(entry) = req_headers.entry("x-amzn-lambda-context") {
375+
entry.remove();
376+
}
377+
368378
// include lambda context in http header "x-amzn-lambda-context"
369379
req_headers.append(
370380
HeaderName::from_static("x-amzn-lambda-context"),

tests/integ_tests/main.rs

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,13 +584,16 @@ async fn test_http_compress_already_compressed() {
584584
}
585585

586586
#[tokio::test]
587-
async fn test_http_lambda_context_header() {
587+
async fn test_http_context_headers() {
588588
// Start app server
589589
let app_server = MockServer::start();
590590

591591
// An endpoint that expects and returns headers
592592
let test_endpoint = app_server.mock(|when, then| {
593-
when.method(GET).path("/").header_exists("x-amzn-lambda-context");
593+
when.method(GET)
594+
.path("/")
595+
.header_exists("x-amzn-lambda-context")
596+
.header_exists("x-amzn-request-context");
594597
then.status(200).header("fizz", "buzz").body("OK");
595598
});
596599

@@ -632,6 +635,80 @@ async fn test_http_lambda_context_header() {
632635
assert_eq!("OK", body_to_string(response).await);
633636
}
634637

638+
#[tokio::test]
639+
async fn test_http_context_multi_headers() {
640+
// Start app server
641+
let app_server = MockServer::start();
642+
643+
// An endpoint that expects and returns headers
644+
let test_endpoint = app_server.mock(|when, then| {
645+
when.method(GET)
646+
.path("/")
647+
.matches(|req| {
648+
req.headers
649+
.as_ref()
650+
.unwrap()
651+
.iter()
652+
.filter(|(key, _value)| key == "x-amzn-lambda-context")
653+
.count()
654+
== 1
655+
})
656+
.matches(|req| {
657+
req.headers
658+
.as_ref()
659+
.unwrap()
660+
.iter()
661+
.filter(|(key, _value)| key == "x-amzn-request-context")
662+
.count()
663+
== 1
664+
});
665+
then.status(200).header("fizz", "buzz").body("OK");
666+
});
667+
668+
// Initialize adapter and do readiness check
669+
let mut adapter = Adapter::new(&AdapterOptions {
670+
host: app_server.host(),
671+
port: app_server.port().to_string(),
672+
readiness_check_port: app_server.port().to_string(),
673+
readiness_check_path: "/healthcheck".to_string(),
674+
readiness_check_protocol: Protocol::Http,
675+
readiness_check_min_unhealthy_status: 500,
676+
async_init: false,
677+
base_path: None,
678+
compression: false,
679+
enable_tls: false,
680+
tls_server_name: None,
681+
tls_cert_file: None,
682+
invoke_mode: LambdaInvokeMode::Buffered,
683+
});
684+
685+
// Prepare request
686+
let req = LambdaEventBuilder::new()
687+
.with_path("/")
688+
.with_header("x-amzn-lambda-context", "header_from_client_1")
689+
.with_header("x-amzn-lambda-context", "header_from_client_2")
690+
.with_header("x-amzn-request-context", "header_from_client_1")
691+
.with_header("x-amzn-request-context", "header_from_client_2")
692+
.build();
693+
694+
// We convert to Request object because it allows us to add
695+
// the Lambda Context
696+
let mut request = Request::from(req);
697+
add_lambda_context_to_request(&mut request);
698+
699+
// Call the adapter service with request
700+
let response = adapter.call(request).await.expect("Request failed");
701+
702+
// Assert endpoint was called once
703+
test_endpoint.assert();
704+
705+
// and response has expected content
706+
assert_eq!(200, response.status());
707+
assert!(response.headers().contains_key("fizz"));
708+
assert_eq!("buzz", response.headers().get("fizz").unwrap());
709+
assert_eq!("OK", body_to_string(response).await);
710+
}
711+
635712
async fn body_to_string(res: Response<Body>) -> String {
636713
let body_bytes = body::to_bytes(res.into_body()).await.unwrap();
637714
String::from_utf8_lossy(&body_bytes).to_string()

0 commit comments

Comments
 (0)