From 44d54a027bedbd07c9b52d91d6268822a703ff70 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 21 Mar 2022 19:11:13 -0700 Subject: [PATCH] Expose the raw http path coming from the lambda event. The API GW url includes th API GW stage information, so it's not easy to know what's the raw http path coming into the event. With this change, we expose the raw http path in a general way, so people that need to know the exact raw path have a common interface, regardless of where the event comes from. Signed-off-by: David Calavera --- lambda-http/examples/hello-raw-http-path.rs | 13 ++++++++++ lambda-http/src/ext.rs | 28 +++++++++++++++++++++ lambda-http/src/request.rs | 17 ++++++++++--- 3 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 lambda-http/examples/hello-raw-http-path.rs diff --git a/lambda-http/examples/hello-raw-http-path.rs b/lambda-http/examples/hello-raw-http-path.rs new file mode 100644 index 00000000..06bcdf71 --- /dev/null +++ b/lambda-http/examples/hello-raw-http-path.rs @@ -0,0 +1,13 @@ +use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt}; + +#[tokio::main] +async fn main() -> Result<(), Error> { + lambda_http::run(service_fn(func)).await?; + Ok(()) +} + +async fn func(event: Request) -> Result { + let res = format!("The raw path for this request is: {}", event.raw_http_path()).into_response(); + + Ok(res) +} diff --git a/lambda-http/src/ext.rs b/lambda-http/src/ext.rs index e33e639c..b53cd851 100644 --- a/lambda-http/src/ext.rs +++ b/lambda-http/src/ext.rs @@ -20,6 +20,9 @@ pub(crate) struct PathParameters(pub(crate) QueryMap); /// These will always be empty for ALB requests pub(crate) struct StageVariables(pub(crate) QueryMap); +/// ALB/API gateway raw http path without any stage information +pub(crate) struct RawHttpPath(pub(crate) String); + /// Request payload deserialization errors /// /// Returned by [`RequestExt#payload()`](trait.RequestExt.html#tymethod.payload) @@ -104,6 +107,12 @@ impl Error for PayloadError { /// } /// ``` pub trait RequestExt { + /// Return the raw http path for a request without any stage information. + fn raw_http_path(&self) -> String; + + /// Configures instance with the raw http path. + fn with_raw_http_path(self, path: &str) -> Self; + /// Return pre-parsed http query string parameters, parameters /// provided after the `?` portion of a url, /// associated with the API gateway request. @@ -177,6 +186,19 @@ pub trait RequestExt { } impl RequestExt for http::Request { + fn raw_http_path(&self) -> String { + self.extensions() + .get::() + .map(|ext| ext.0.clone()) + .unwrap_or_default() + } + + fn with_raw_http_path(self, path: &str) -> Self { + let mut s = self; + s.extensions_mut().insert(RawHttpPath(path.into())); + s + } + fn query_string_parameters(&self) -> QueryMap { self.extensions() .get::() @@ -401,4 +423,10 @@ mod tests { let payload: Option = request.payload().unwrap_or_default(); assert_eq!(payload, None); } + + #[test] + fn requests_can_mock_raw_http_path_ext() { + let request = Request::default().with_raw_http_path("/raw-path"); + assert_eq!("/raw-path", request.raw_http_path().as_str()); + } } diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index cff30fc7..9c9d84db 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -3,7 +3,7 @@ //! Typically these are exposed via the `request_context` //! request extension method provided by [lambda_http::RequestExt](../trait.RequestExt.html) //! -use crate::ext::{PathParameters, QueryStringParameters, StageVariables}; +use crate::ext::{PathParameters, QueryStringParameters, RawHttpPath, StageVariables}; use aws_lambda_events::alb::{AlbTargetGroupRequest, AlbTargetGroupRequestContext}; use aws_lambda_events::apigw::{ ApiGatewayProxyRequest, ApiGatewayProxyRequestContext, ApiGatewayV2httpRequest, ApiGatewayV2httpRequestContext, @@ -61,6 +61,8 @@ pub enum RequestOrigin { fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request { let http_method = ag.request_context.http.method.clone(); + let raw_path = ag.raw_path.unwrap_or_default(); + let builder = http::Request::builder() .uri({ let scheme = ag @@ -75,7 +77,7 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request http::Request http::Request http::Request { let http_method = ag.http_method; + let raw_path = ag.path.unwrap_or_default(); + let builder = http::Request::builder() .uri({ let host = ag.headers.get(http::header::HOST).and_then(|s| s.to_str().ok()); - let path = apigw_path_with_stage(&ag.request_context.stage, &ag.path.unwrap_or_default()); + let path = apigw_path_with_stage(&ag.request_context.stage, &raw_path); let mut url = match host { None => path, @@ -141,6 +146,7 @@ fn into_proxy_request(ag: ApiGatewayProxyRequest) -> http::Request { } url }) + .extension(RawHttpPath(raw_path)) // multi-valued query string parameters are always a super // set of singly valued query string parameters, // when present, multi-valued query string parameters are preferred @@ -178,6 +184,8 @@ fn into_proxy_request(ag: ApiGatewayProxyRequest) -> http::Request { fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { let http_method = alb.http_method; + let raw_path = alb.path.unwrap_or_default(); + let builder = http::Request::builder() .uri({ let scheme = alb @@ -191,7 +199,7 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { .and_then(|s| s.to_str().ok()) .unwrap_or_default(); - let mut url = format!("{}://{}{}", scheme, host, alb.path.unwrap_or_default()); + let mut url = format!("{}://{}{}", scheme, host, &raw_path); if !alb.multi_value_query_string_parameters.is_empty() { url.push('?'); url.push_str(&alb.multi_value_query_string_parameters.to_query_string()); @@ -202,6 +210,7 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { url }) + .extension(RawHttpPath(raw_path)) // multi valued query string parameters are always a super // set of singly valued query string parameters, // when present, multi-valued query string parameters are preferred