@@ -14,12 +14,14 @@ use aws_lambda_events::apigw::{ApiGatewayV2httpRequest, ApiGatewayV2httpRequestC
14
14
use aws_lambda_events:: apigw:: { ApiGatewayWebsocketProxyRequest , ApiGatewayWebsocketProxyRequestContext } ;
15
15
use aws_lambda_events:: encodings:: Body ;
16
16
use http:: header:: HeaderName ;
17
+ use http:: HeaderMap ;
17
18
use query_map:: QueryMap ;
18
19
use serde:: Deserialize ;
19
20
use serde_json:: error:: Error as JsonError ;
20
21
use std:: future:: Future ;
21
22
use std:: pin:: Pin ;
22
23
use std:: { io:: Read , mem} ;
24
+ use url:: Url ;
23
25
24
26
/// Internal representation of an Lambda http event from
25
27
/// ALB, API Gateway REST and HTTP API proxy event perspectives
@@ -82,7 +84,13 @@ pub enum RequestOrigin {
82
84
#[ cfg( feature = "apigw_http" ) ]
83
85
fn into_api_gateway_v2_request ( ag : ApiGatewayV2httpRequest ) -> http:: Request < Body > {
84
86
let http_method = ag. request_context . http . method . clone ( ) ;
87
+ let host = ag
88
+ . headers
89
+ . get ( http:: header:: HOST )
90
+ . and_then ( |s| s. to_str ( ) . ok ( ) )
91
+ . or ( ag. request_context . domain_name . as_deref ( ) ) ;
85
92
let raw_path = ag. raw_path . unwrap_or_default ( ) ;
93
+ let path = apigw_path_with_stage ( & ag. request_context . stage , & raw_path) ;
86
94
87
95
// don't use the query_string_parameters from API GW v2 to
88
96
// populate the QueryStringParameters extension because
@@ -95,32 +103,14 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request<Bod
95
103
ag. query_string_parameters
96
104
} ;
97
105
106
+ let mut uri = build_request_uri ( & path, & ag. headers , host, None ) ;
107
+ if let Some ( query) = ag. raw_query_string {
108
+ uri. push ( '?' ) ;
109
+ uri. push_str ( & query) ;
110
+ }
111
+
98
112
let builder = http:: Request :: builder ( )
99
- . uri ( {
100
- let host = ag
101
- . headers
102
- . get ( http:: header:: HOST )
103
- . and_then ( |s| s. to_str ( ) . ok ( ) )
104
- . or ( ag. request_context . domain_name . as_deref ( ) ) ;
105
- let path = apigw_path_with_stage ( & ag. request_context . stage , & raw_path) ;
106
-
107
- let mut url = match host {
108
- None => path,
109
- Some ( host) => {
110
- let scheme = ag
111
- . headers
112
- . get ( x_forwarded_proto ( ) )
113
- . and_then ( |s| s. to_str ( ) . ok ( ) )
114
- . unwrap_or ( "https" ) ;
115
- format ! ( "{}://{}{}" , scheme, host, path)
116
- }
117
- } ;
118
- if let Some ( query) = ag. raw_query_string {
119
- url. push ( '?' ) ;
120
- url. push_str ( & query) ;
121
- }
122
- url
123
- } )
113
+ . uri ( uri)
124
114
. extension ( RawHttpPath ( raw_path) )
125
115
. extension ( QueryStringParameters ( query_string_parameters) )
126
116
. extension ( PathParameters ( QueryMap :: from ( ag. path_parameters ) ) )
@@ -154,34 +144,21 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request<Bod
154
144
#[ cfg( feature = "apigw_rest" ) ]
155
145
fn into_proxy_request ( ag : ApiGatewayProxyRequest ) -> http:: Request < Body > {
156
146
let http_method = ag. http_method ;
147
+ let host = ag
148
+ . headers
149
+ . get ( http:: header:: HOST )
150
+ . and_then ( |s| s. to_str ( ) . ok ( ) )
151
+ . or ( ag. request_context . domain_name . as_deref ( ) ) ;
157
152
let raw_path = ag. path . unwrap_or_default ( ) ;
153
+ let path = apigw_path_with_stage ( & ag. request_context . stage , & raw_path) ;
158
154
159
155
let builder = http:: Request :: builder ( )
160
- . uri ( {
161
- let host = ag. headers . get ( http:: header:: HOST ) . and_then ( |s| s. to_str ( ) . ok ( ) ) ;
162
- let path = apigw_path_with_stage ( & ag. request_context . stage , & raw_path) ;
163
-
164
- let mut url = match host {
165
- None => path,
166
- Some ( host) => {
167
- let scheme = ag
168
- . headers
169
- . get ( x_forwarded_proto ( ) )
170
- . and_then ( |s| s. to_str ( ) . ok ( ) )
171
- . unwrap_or ( "https" ) ;
172
- format ! ( "{}://{}{}" , scheme, host, path)
173
- }
174
- } ;
175
-
176
- if !ag. multi_value_query_string_parameters . is_empty ( ) {
177
- url. push ( '?' ) ;
178
- url. push_str ( & ag. multi_value_query_string_parameters . to_query_string ( ) ) ;
179
- } else if !ag. query_string_parameters . is_empty ( ) {
180
- url. push ( '?' ) ;
181
- url. push_str ( & ag. query_string_parameters . to_query_string ( ) ) ;
182
- }
183
- url
184
- } )
156
+ . uri ( build_request_uri (
157
+ & path,
158
+ & ag. headers ,
159
+ host,
160
+ Some ( ( & ag. multi_value_query_string_parameters , & ag. query_string_parameters ) ) ,
161
+ ) )
185
162
. extension ( RawHttpPath ( raw_path) )
186
163
// multi-valued query string parameters are always a super
187
164
// set of singly valued query string parameters,
@@ -221,34 +198,16 @@ fn into_proxy_request(ag: ApiGatewayProxyRequest) -> http::Request<Body> {
221
198
#[ cfg( feature = "alb" ) ]
222
199
fn into_alb_request ( alb : AlbTargetGroupRequest ) -> http:: Request < Body > {
223
200
let http_method = alb. http_method ;
201
+ let host = alb. headers . get ( http:: header:: HOST ) . and_then ( |s| s. to_str ( ) . ok ( ) ) ;
224
202
let raw_path = alb. path . unwrap_or_default ( ) ;
225
203
226
204
let builder = http:: Request :: builder ( )
227
- . uri ( {
228
- let host = alb. headers . get ( http:: header:: HOST ) . and_then ( |s| s. to_str ( ) . ok ( ) ) ;
229
-
230
- let mut url = match host {
231
- None => raw_path. clone ( ) ,
232
- Some ( host) => {
233
- let scheme = alb
234
- . headers
235
- . get ( x_forwarded_proto ( ) )
236
- . and_then ( |s| s. to_str ( ) . ok ( ) )
237
- . unwrap_or ( "https" ) ;
238
- format ! ( "{}://{}{}" , scheme, host, & raw_path)
239
- }
240
- } ;
241
-
242
- if !alb. multi_value_query_string_parameters . is_empty ( ) {
243
- url. push ( '?' ) ;
244
- url. push_str ( & alb. multi_value_query_string_parameters . to_query_string ( ) ) ;
245
- } else if !alb. query_string_parameters . is_empty ( ) {
246
- url. push ( '?' ) ;
247
- url. push_str ( & alb. query_string_parameters . to_query_string ( ) ) ;
248
- }
249
-
250
- url
251
- } )
205
+ . uri ( build_request_uri (
206
+ & raw_path,
207
+ & alb. headers ,
208
+ host,
209
+ Some ( ( & alb. multi_value_query_string_parameters , & alb. query_string_parameters ) ) ,
210
+ ) )
252
211
. extension ( RawHttpPath ( raw_path) )
253
212
// multi valued query string parameters are always a super
254
213
// set of singly valued query string parameters,
@@ -287,32 +246,20 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request<Body> {
287
246
#[ cfg( feature = "apigw_websockets" ) ]
288
247
fn into_websocket_request ( ag : ApiGatewayWebsocketProxyRequest ) -> http:: Request < Body > {
289
248
let http_method = ag. http_method ;
249
+ let host = ag
250
+ . headers
251
+ . get ( http:: header:: HOST )
252
+ . and_then ( |s| s. to_str ( ) . ok ( ) )
253
+ . or ( ag. request_context . domain_name . as_deref ( ) ) ;
254
+ let path = apigw_path_with_stage ( & ag. request_context . stage , & ag. path . unwrap_or_default ( ) ) ;
255
+
290
256
let builder = http:: Request :: builder ( )
291
- . uri ( {
292
- let host = ag. headers . get ( http:: header:: HOST ) . and_then ( |s| s. to_str ( ) . ok ( ) ) ;
293
- let path = apigw_path_with_stage ( & ag. request_context . stage , & ag. path . unwrap_or_default ( ) ) ;
294
-
295
- let mut url = match host {
296
- None => path,
297
- Some ( host) => {
298
- let scheme = ag
299
- . headers
300
- . get ( x_forwarded_proto ( ) )
301
- . and_then ( |s| s. to_str ( ) . ok ( ) )
302
- . unwrap_or ( "https" ) ;
303
- format ! ( "{}://{}{}" , scheme, host, path)
304
- }
305
- } ;
306
-
307
- if !ag. multi_value_query_string_parameters . is_empty ( ) {
308
- url. push ( '?' ) ;
309
- url. push_str ( & ag. multi_value_query_string_parameters . to_query_string ( ) ) ;
310
- } else if !ag. query_string_parameters . is_empty ( ) {
311
- url. push ( '?' ) ;
312
- url. push_str ( & ag. query_string_parameters . to_query_string ( ) ) ;
313
- }
314
- url
315
- } )
257
+ . uri ( build_request_uri (
258
+ & path,
259
+ & ag. headers ,
260
+ host,
261
+ Some ( ( & ag. multi_value_query_string_parameters , & ag. query_string_parameters ) ) ,
262
+ ) )
316
263
// multi-valued query string parameters are always a super
317
264
// set of singly valued query string parameters,
318
265
// when present, multi-valued query string parameters are preferred
@@ -438,6 +385,40 @@ fn x_forwarded_proto() -> HeaderName {
438
385
HeaderName :: from_static ( "x-forwarded-proto" )
439
386
}
440
387
388
+ fn build_request_uri (
389
+ path : & str ,
390
+ headers : & HeaderMap ,
391
+ host : Option < & str > ,
392
+ queries : Option < ( & QueryMap , & QueryMap ) > ,
393
+ ) -> String {
394
+ let mut url = match host {
395
+ None => {
396
+ let rel_url = Url :: parse ( & format ! ( "http://localhost{}" , path) ) . unwrap ( ) ;
397
+ rel_url. path ( ) . to_string ( )
398
+ }
399
+ Some ( host) => {
400
+ let scheme = headers
401
+ . get ( x_forwarded_proto ( ) )
402
+ . and_then ( |s| s. to_str ( ) . ok ( ) )
403
+ . unwrap_or ( "https" ) ;
404
+ let url = format ! ( "{}://{}{}" , scheme, host, path) ;
405
+ Url :: parse ( & url) . unwrap ( ) . to_string ( )
406
+ }
407
+ } ;
408
+
409
+ if let Some ( ( mv, sv) ) = queries {
410
+ if !mv. is_empty ( ) {
411
+ url. push ( '?' ) ;
412
+ url. push_str ( & mv. to_query_string ( ) ) ;
413
+ } else if !sv. is_empty ( ) {
414
+ url. push ( '?' ) ;
415
+ url. push_str ( & sv. to_query_string ( ) ) ;
416
+ }
417
+ }
418
+
419
+ url
420
+ }
421
+
441
422
#[ cfg( test) ]
442
423
mod tests {
443
424
use super :: * ;
@@ -666,4 +647,25 @@ mod tests {
666
647
assert_eq ! ( req. method( ) , "GET" ) ;
667
648
assert_eq ! ( req. uri( ) , "/v1/health/" ) ;
668
649
}
650
+
651
+ #[ test]
652
+ fn deserialize_apigw_path_with_space ( ) {
653
+ // generated from ALB health checks
654
+ let input = include_str ! ( "../tests/data/apigw_request_path_with_space.json" ) ;
655
+ let result = from_str ( input) ;
656
+ assert ! (
657
+ result. is_ok( ) ,
658
+ "event was not parsed as expected {:?} given {}" ,
659
+ result,
660
+ input
661
+ ) ;
662
+ let req = result. expect ( "failed to parse request" ) ;
663
+ assert_eq ! ( req. uri( ) , "https://id.execute-api.us-east-1.amazonaws.com/my/path-with%20space?parameter1=value1¶meter1=value2¶meter2=value" ) ;
664
+ }
665
+
666
+ #[ test]
667
+ fn parse_paths_with_spaces ( ) {
668
+ let url = build_request_uri ( "/path with spaces/and multiple segments" , & HeaderMap :: new ( ) , None , None ) ;
669
+ assert_eq ! ( "/path%20with%20spaces/and%20multiple%20segments" , url) ;
670
+ }
669
671
}
0 commit comments