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