Skip to content

Commit 14ce8e1

Browse files
authored
Pass Lambda Context to web app in a new header (#248)
* Pass Lambda Context to web app in a new header * Added tests to make sure x-amzn-lambda-context header is present
1 parent 5096f3a commit 14ce8e1

File tree

2 files changed

+129
-10
lines changed

2 files changed

+129
-10
lines changed

src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ where
332332
}
333333

334334
let request_context = event.request_context();
335+
let lambda_context = &event.lambda_context();
335336
let path = event.raw_http_path().to_string();
336337
let mut path = path.as_str();
337338
let (parts, body) = event.into_parts();
@@ -349,6 +350,11 @@ where
349350
HeaderValue::from_bytes(serde_json::to_string(&request_context)?.as_bytes())?,
350351
);
351352

353+
// include lambda context in http header "x-amzn-lambda-context"
354+
req_headers.append(
355+
HeaderName::from_static("x-amzn-lambda-context"),
356+
HeaderValue::from_bytes(serde_json::to_string(&lambda_context)?.as_bytes())?,
357+
);
352358
let mut app_url = self.domain.clone();
353359
app_url.set_path(path);
354360
app_url.set_query(parts.uri.query());

tests/integ_tests/main.rs

Lines changed: 123 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ use std::io;
55
use std::io::prelude::*;
66

77
use crate::common::LambdaEventBuilder;
8+
use http::HeaderMap;
9+
use http::Request;
810
use http::{Method, Response};
911
use httpmock::{
1012
Method::{DELETE, GET, POST, PUT},
1113
MockServer,
1214
};
1315
use hyper::{body, Body};
16+
use lambda_http::Context;
1417
use lambda_web_adapter::{Adapter, AdapterOptions, LambdaInvokeMode, Protocol};
1518
use tower::{Service, ServiceBuilder};
1619

@@ -158,7 +161,13 @@ async fn test_http_basic_request() {
158161

159162
// // Call the adapter service with basic request
160163
let req = LambdaEventBuilder::new().with_path("/hello").build();
161-
let response = adapter.call(req.into()).await.expect("Request failed");
164+
165+
// We convert to Request object because it allows us to add
166+
// the lambda Context
167+
let mut request = Request::from(req);
168+
add_lambda_context_to_request(&mut request);
169+
170+
let response = adapter.call(request).await.expect("Request failed");
162171

163172
// Assert endpoint was called once
164173
hello.assert();
@@ -202,8 +211,13 @@ async fn test_http_headers() {
202211
.with_header("foo", "bar")
203212
.build();
204213

214+
// We convert to Request object because it allows us to add
215+
// the Lambda Context
216+
let mut request = Request::from(req);
217+
add_lambda_context_to_request(&mut request);
218+
205219
// Call the adapter service with request
206-
let response = adapter.call(req.into()).await.expect("Request failed");
220+
let response = adapter.call(request).await.expect("Request failed");
207221

208222
// Assert endpoint was called once
209223
test_endpoint.assert();
@@ -245,8 +259,13 @@ async fn test_http_path_encoding() {
245259
// Prepare request
246260
let req = LambdaEventBuilder::new().with_path("/Año_1234").build();
247261

262+
// We convert to Request object because it allows us to add
263+
// the lambda Context
264+
let mut request = Request::from(req);
265+
add_lambda_context_to_request(&mut request);
266+
248267
// Call the adapter service with request
249-
let response = adapter.call(req.into()).await.expect("Request failed");
268+
let response = adapter.call(request).await.expect("Request failed");
250269

251270
// Assert endpoint was called once
252271
test_endpoint.assert();
@@ -293,8 +312,13 @@ async fn test_http_query_params() {
293312
.with_query("fizz", "buzz")
294313
.build();
295314

315+
// We convert to Request object because it allows us to add
316+
// the lambda Context
317+
let mut request = Request::from(req);
318+
add_lambda_context_to_request(&mut request);
319+
296320
// Call the adapter service with request
297-
let response = adapter.call(req.into()).await.expect("Request failed");
321+
let response = adapter.call(request).await.expect("Request failed");
298322

299323
// Assert endpoint was called once
300324
test_endpoint.assert();
@@ -354,11 +378,20 @@ async fn test_http_post_put_delete() {
354378
.with_method(Method::DELETE)
355379
.with_path("/")
356380
.build();
381+
// We convert to Request object because it allows us to add the lambda Context
382+
let mut post_request = Request::from(post_req);
383+
add_lambda_context_to_request(&mut post_request);
384+
385+
let mut put_request = Request::from(put_req);
386+
add_lambda_context_to_request(&mut put_request);
387+
388+
let mut delete_request = Request::from(delete_req);
389+
add_lambda_context_to_request(&mut delete_request);
357390

358391
// Call the adapter service with requests
359-
let post_response = adapter.call(post_req.into()).await.expect("Request failed");
360-
let put_response = adapter.call(put_req.into()).await.expect("Request failed");
361-
let delete_response = adapter.call(delete_req.into()).await.expect("Request failed");
392+
let post_response = adapter.call(post_request).await.expect("Request failed");
393+
let put_response = adapter.call(put_request).await.expect("Request failed");
394+
let delete_response = adapter.call(delete_request).await.expect("Request failed");
362395

363396
// Assert endpoints were called
364397
post_endpoint.assert();
@@ -407,7 +440,13 @@ async fn test_http_compress() {
407440
.with_path("/hello")
408441
.with_header("accept-encoding", "gzip")
409442
.build();
410-
let response = svc.call(req.into()).await.expect("Request failed");
443+
444+
// We convert to Request object because it allows us to add
445+
// the lambda Context
446+
let mut request = Request::from(req);
447+
add_lambda_context_to_request(&mut request);
448+
449+
let response = svc.call(request).await.expect("Request failed");
411450

412451
// Assert endpoint was called once
413452
hello.assert();
@@ -453,7 +492,13 @@ async fn test_http_compress_disallowed_type() {
453492
.with_path("/hello")
454493
.with_header("accept-encoding", "gzip")
455494
.build();
456-
let response = adapter.call(req.into()).await.expect("Request failed");
495+
496+
// We convert to Request object because it allows us to add
497+
// the lambda Context
498+
let mut request = Request::from(req);
499+
add_lambda_context_to_request(&mut request);
500+
501+
let response = adapter.call(request).await.expect("Request failed");
457502

458503
// Assert endpoint was called once
459504
hello.assert();
@@ -506,7 +551,13 @@ async fn test_http_compress_already_compressed() {
506551
.with_path("/hello")
507552
.with_header("accept-encoding", "gzip")
508553
.build();
509-
let response = svc.call(req.into()).await.expect("Request failed");
554+
555+
// We convert to Request object because it allows us to add
556+
// the lambda Context
557+
let mut request = Request::from(req);
558+
add_lambda_context_to_request(&mut request);
559+
560+
let response = svc.call(request).await.expect("Request failed");
510561

511562
// Assert endpoint was called once
512563
hello.assert();
@@ -521,6 +572,54 @@ async fn test_http_compress_already_compressed() {
521572
);
522573
}
523574

575+
#[tokio::test]
576+
async fn test_http_lambda_context_header() {
577+
// Start app server
578+
let app_server = MockServer::start();
579+
580+
// An endpoint that expects and returns headers
581+
let test_endpoint = app_server.mock(|when, then| {
582+
when.method(GET).path("/").header_exists("x-amzn-lambda-context");
583+
then.status(200).header("fizz", "buzz").body("OK");
584+
});
585+
586+
// Initialize adapter and do readiness check
587+
let mut adapter = Adapter::new(&AdapterOptions {
588+
host: app_server.host(),
589+
port: app_server.port().to_string(),
590+
readiness_check_port: app_server.port().to_string(),
591+
readiness_check_path: "/healthcheck".to_string(),
592+
readiness_check_protocol: Protocol::Http,
593+
async_init: false,
594+
base_path: None,
595+
compression: false,
596+
enable_tls: false,
597+
tls_server_name: None,
598+
tls_cert_file: None,
599+
invoke_mode: LambdaInvokeMode::Buffered,
600+
});
601+
602+
// Prepare request
603+
let req = LambdaEventBuilder::new().with_path("/").build();
604+
605+
// We convert to Request object because it allows us to add
606+
// the Lambda Context
607+
let mut request = Request::from(req);
608+
add_lambda_context_to_request(&mut request);
609+
610+
// Call the adapter service with request
611+
let response = adapter.call(request).await.expect("Request failed");
612+
613+
// Assert endpoint was called once
614+
test_endpoint.assert();
615+
616+
// and response has expected content
617+
assert_eq!(200, response.status());
618+
assert!(response.headers().contains_key("fizz"));
619+
assert_eq!("buzz", response.headers().get("fizz").unwrap());
620+
assert_eq!("OK", body_to_string(response).await);
621+
}
622+
524623
async fn body_to_string(res: Response<Body>) -> String {
525624
let body_bytes = body::to_bytes(res.into_body()).await.unwrap();
526625
String::from_utf8_lossy(&body_bytes).to_string()
@@ -537,3 +636,17 @@ fn decode_reader(bytes: &[u8]) -> io::Result<String> {
537636
gz.read_to_string(&mut s)?;
538637
Ok(s)
539638
}
639+
640+
fn add_lambda_context_to_request(request: &mut Request<lambda_http::Body>) {
641+
// create a HeaderMap to build the lambda context
642+
let mut headers = HeaderMap::new();
643+
headers.insert("lambda-runtime-aws-request-id", "my_id".parse().unwrap());
644+
headers.insert("lambda-runtime-deadline-ms", "123".parse().unwrap());
645+
headers.insert("lambda-runtime-client-context", "{}".parse().unwrap());
646+
647+
// converts HeaderMap to Context
648+
let context = Context::try_from(headers).expect("Couldn't convert HeaderMap to Context");
649+
650+
// add Context to the request
651+
request.extensions_mut().insert(context);
652+
}

0 commit comments

Comments
 (0)