@@ -14,12 +14,14 @@ use aws_lambda_events::apigw::{ApiGatewayV2httpRequest, ApiGatewayV2httpRequestC
1414use aws_lambda_events:: apigw:: { ApiGatewayWebsocketProxyRequest , ApiGatewayWebsocketProxyRequestContext } ;
1515use aws_lambda_events:: encodings:: Body ;
1616use http:: header:: HeaderName ;
17+ use http:: HeaderMap ;
1718use query_map:: QueryMap ;
1819use serde:: Deserialize ;
1920use serde_json:: error:: Error as JsonError ;
2021use std:: future:: Future ;
2122use std:: pin:: Pin ;
2223use std:: { io:: Read , mem} ;
24+ use url:: Url ;
2325
2426/// Internal representation of an Lambda http event from
2527/// ALB, API Gateway REST and HTTP API proxy event perspectives
@@ -82,7 +84,13 @@ pub enum RequestOrigin {
8284#[ cfg( feature = "apigw_http" ) ]
8385fn into_api_gateway_v2_request ( ag : ApiGatewayV2httpRequest ) -> http:: Request < Body > {
8486 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 ( ) ) ;
8592 let raw_path = ag. raw_path . unwrap_or_default ( ) ;
93+ let path = apigw_path_with_stage ( & ag. request_context . stage , & raw_path) ;
8694
8795 // don't use the query_string_parameters from API GW v2 to
8896 // populate the QueryStringParameters extension because
@@ -95,32 +103,14 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request<Bod
95103 ag. query_string_parameters
96104 } ;
97105
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+
98112 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)
124114 . extension ( RawHttpPath ( raw_path) )
125115 . extension ( QueryStringParameters ( query_string_parameters) )
126116 . extension ( PathParameters ( QueryMap :: from ( ag. path_parameters ) ) )
@@ -154,34 +144,21 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request<Bod
154144#[ cfg( feature = "apigw_rest" ) ]
155145fn into_proxy_request ( ag : ApiGatewayProxyRequest ) -> http:: Request < Body > {
156146 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 ( ) ) ;
157152 let raw_path = ag. path . unwrap_or_default ( ) ;
153+ let path = apigw_path_with_stage ( & ag. request_context . stage , & raw_path) ;
158154
159155 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+ ) )
185162 . extension ( RawHttpPath ( raw_path) )
186163 // multi-valued query string parameters are always a super
187164 // set of singly valued query string parameters,
@@ -221,34 +198,16 @@ fn into_proxy_request(ag: ApiGatewayProxyRequest) -> http::Request<Body> {
221198#[ cfg( feature = "alb" ) ]
222199fn into_alb_request ( alb : AlbTargetGroupRequest ) -> http:: Request < Body > {
223200 let http_method = alb. http_method ;
201+ let host = alb. headers . get ( http:: header:: HOST ) . and_then ( |s| s. to_str ( ) . ok ( ) ) ;
224202 let raw_path = alb. path . unwrap_or_default ( ) ;
225203
226204 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+ ) )
252211 . extension ( RawHttpPath ( raw_path) )
253212 // multi valued query string parameters are always a super
254213 // set of singly valued query string parameters,
@@ -287,32 +246,20 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request<Body> {
287246#[ cfg( feature = "apigw_websockets" ) ]
288247fn into_websocket_request ( ag : ApiGatewayWebsocketProxyRequest ) -> http:: Request < Body > {
289248 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+
290256 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+ ) )
316263 // multi-valued query string parameters are always a super
317264 // set of singly valued query string parameters,
318265 // when present, multi-valued query string parameters are preferred
@@ -438,6 +385,40 @@ fn x_forwarded_proto() -> HeaderName {
438385 HeaderName :: from_static ( "x-forwarded-proto" )
439386}
440387
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+
441422#[ cfg( test) ]
442423mod tests {
443424 use super :: * ;
@@ -666,4 +647,25 @@ mod tests {
666647 assert_eq ! ( req. method( ) , "GET" ) ;
667648 assert_eq ! ( req. uri( ) , "/v1/health/" ) ;
668649 }
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+ }
669671}
0 commit comments