Skip to content

Commit 2201109

Browse files
authored
Decode query paramters for ALB event source (#545)
* decode ALB query strings
1 parent 45211b0 commit 2201109

File tree

5 files changed

+127
-5
lines changed

5 files changed

+127
-5
lines changed

lambda-http/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ query_map = { version = "0.5", features = ["url-query"] }
3636
mime = "0.3.16"
3737
encoding_rs = "0.8.31"
3838
url = "2.2.2"
39+
percent-encoding = "2.2.0"
3940

4041
[dependencies.aws_lambda_events]
4142
version = "^0.6.3"

lambda-http/src/request.rs

+64-4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use serde::Deserialize;
2020
use serde_json::error::Error as JsonError;
2121
use std::future::Future;
2222
use std::pin::Pin;
23+
use std::str::FromStr;
2324
use std::{io::Read, mem};
2425
use url::Url;
2526

@@ -201,22 +202,25 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request<Body> {
201202
let host = alb.headers.get(http::header::HOST).and_then(|s| s.to_str().ok());
202203
let raw_path = alb.path.unwrap_or_default();
203204

205+
let query_string_parameters = decode_query_map(alb.query_string_parameters);
206+
let multi_value_query_string_parameters = decode_query_map(alb.multi_value_query_string_parameters);
207+
204208
let builder = http::Request::builder()
205209
.uri(build_request_uri(
206210
&raw_path,
207211
&alb.headers,
208212
host,
209-
Some((&alb.multi_value_query_string_parameters, &alb.query_string_parameters)),
213+
Some((&multi_value_query_string_parameters, &query_string_parameters)),
210214
))
211215
.extension(RawHttpPath(raw_path))
212216
// multi valued query string parameters are always a super
213217
// set of singly valued query string parameters,
214218
// when present, multi-valued query string parameters are preferred
215219
.extension(QueryStringParameters(
216-
if alb.multi_value_query_string_parameters.is_empty() {
217-
alb.query_string_parameters
220+
if multi_value_query_string_parameters.is_empty() {
221+
query_string_parameters
218222
} else {
219-
alb.multi_value_query_string_parameters
223+
multi_value_query_string_parameters
220224
},
221225
))
222226
.extension(RequestContext::Alb(alb.request_context));
@@ -243,6 +247,12 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request<Body> {
243247
req
244248
}
245249

250+
fn decode_query_map(query_map: QueryMap) -> QueryMap {
251+
let query_string = query_map.to_query_string();
252+
let decoded = percent_encoding::percent_decode(query_string.as_bytes()).decode_utf8_lossy();
253+
QueryMap::from_str(&decoded).unwrap_or_default()
254+
}
255+
246256
#[cfg(feature = "apigw_websockets")]
247257
fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request<Body> {
248258
let http_method = ag.http_method;
@@ -548,6 +558,34 @@ mod tests {
548558
);
549559
}
550560

561+
#[test]
562+
fn deserializes_alb_request_encoded_query_parameters_events() {
563+
// from the docs
564+
// https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers
565+
let input = include_str!("../tests/data/alb_request_encoded_query_parameters.json");
566+
let result = from_str(input);
567+
assert!(
568+
result.is_ok(),
569+
"event was not parsed as expected {:?} given {}",
570+
result,
571+
input
572+
);
573+
let req = result.expect("failed to parse request");
574+
assert_eq!(req.method(), "GET");
575+
assert_eq!(
576+
req.uri(),
577+
"https://lambda-846800462-us-east-2.elb.amazonaws.com/?myKey=%3FshowAll%3Dtrue"
578+
);
579+
580+
// Ensure this is an ALB request
581+
let req_context = req.request_context();
582+
assert!(
583+
matches!(req_context, RequestContext::Alb(_)),
584+
"expected Alb context, got {:?}",
585+
req_context
586+
);
587+
}
588+
551589
#[test]
552590
fn deserializes_apigw_multi_value_request_events() {
553591
// from docs
@@ -593,6 +631,28 @@ mod tests {
593631
);
594632
}
595633

634+
#[test]
635+
fn deserializes_alb_multi_value_request_encoded_query_parameters_events() {
636+
// from docs
637+
// https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
638+
let input = include_str!("../tests/data/alb_multi_value_request_encoded_query_parameters.json");
639+
let result = from_str(input);
640+
assert!(
641+
result.is_ok(),
642+
"event is was not parsed as expected {:?} given {}",
643+
result,
644+
input
645+
);
646+
let request = result.expect("failed to parse request");
647+
assert!(!request.query_string_parameters().is_empty());
648+
649+
// test RequestExt#query_string_parameters does the right thing
650+
assert_eq!(
651+
request.query_string_parameters().all("myKey"),
652+
Some(vec!["?showAll=true", "?showAll=false"])
653+
);
654+
}
655+
596656
#[test]
597657
fn deserialize_apigw_http_sam_local() {
598658
// manually generated from AWS SAM CLI
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"requestContext": {
3+
"elb": {
4+
"targetGroupArn": "arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09"
5+
}
6+
},
7+
"httpMethod": "GET",
8+
"path": "/",
9+
"queryStringParameters": { "myKey": "%3FshowAll%3Dtrue" },
10+
"multiValueQueryStringParameters": { "myKey": ["%3FshowAll%3Dtrue", "%3FshowAll%3Dfalse"] },
11+
"headers": {
12+
"accept": "text/html,application/xhtml+xml",
13+
"accept-language": "en-US,en;q=0.8",
14+
"content-type": "text/plain",
15+
"cookie": "name1=value1",
16+
"host": "lambda-846800462-us-east-2.elb.amazonaws.com",
17+
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)",
18+
"x-amzn-trace-id": "Root=1-5bdb40ca-556d8b0c50dc66f0511bf520",
19+
"x-forwarded-for": "72.21.198.66",
20+
"x-forwarded-port": "443",
21+
"x-forwarded-proto": "https"
22+
},
23+
"multiValueHeaders": {
24+
"accept": ["text/html,application/xhtml+xml"],
25+
"accept-language": ["en-US,en;q=0.8"],
26+
"content-type": ["text/plain"],
27+
"cookie": ["name1=value1", "name2=value2"],
28+
"host": ["lambda-846800462-us-east-2.elb.amazonaws.com"],
29+
"user-agent": ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)"],
30+
"x-amzn-trace-id": ["Root=1-5bdb40ca-556d8b0c50dc66f0511bf520"],
31+
"x-forwarded-for": ["72.21.198.66"],
32+
"x-forwarded-port": ["443"],
33+
"x-forwarded-proto": ["https"]
34+
},
35+
"isBase64Encoded": false,
36+
"body": "request_body"
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"requestContext": {
3+
"elb": {
4+
"targetGroupArn": "arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09"
5+
}
6+
},
7+
"httpMethod": "GET",
8+
"path": "/",
9+
"queryStringParameters": { "myKey": "%3FshowAll%3Dtrue"},
10+
"headers": {
11+
"accept": "text/html,application/xhtml+xml",
12+
"accept-language": "en-US,en;q=0.8",
13+
"content-type": "text/plain",
14+
"cookie": "cookies",
15+
"host": "lambda-846800462-us-east-2.elb.amazonaws.com",
16+
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)",
17+
"x-amzn-trace-id": "Root=1-5bdb40ca-556d8b0c50dc66f0511bf520",
18+
"x-forwarded-for": "72.21.198.66",
19+
"x-forwarded-port": "443",
20+
"x-forwarded-proto": "https"
21+
},
22+
"isBase64Encoded": false,
23+
"body": "request_body"
24+
}

lambda-runtime-api-client/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@ async fn main() -> Result<(), Error> {
3232
3333
client.call(request).await
3434
}
35-
```
35+
```

0 commit comments

Comments
 (0)