Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH does not ignore stage in API Gateway V2 (HTTP) #919

Open
artemudovyk opened this issue Aug 27, 2024 · 4 comments

Comments

@artemudovyk
Copy link

artemudovyk commented Aug 27, 2024

The AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH env variable doesn't seem to work on API Gateway V2 (HTTP).

Example:

use axum::{response::Json, routing::get, Router};
use lambda_http::request::RequestContext::ApiGatewayV2;
use lambda_http::{run, tracing, Error};
use serde_json::{json, Value};

// Sample middleware that logs the request id
async fn mw_sample(
    req: axum::extract::Request,
    next: axum::middleware::Next,
) -> impl axum::response::IntoResponse {
    let context = req
        .extensions()
        .get::<lambda_http::request::RequestContext>();
    if let Some(ApiGatewayV2(ctx)) = context {
        tracing::info!("RequestId = {:?}", ctx);
    }
    next.run(req).await
}

async fn handler_sample() -> Json<Value> {
    Json(json!({ "echo":  "hello" }))
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    std::env::set_var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH", "true"); // ignore stages in routing

    tracing::init_default_subscriber();

    let app = Router::new()
        .route("/not-staged", get(handler_sample)) // stage not specified, not working
        .route("/v1/staged", get(handler_sample)) // stage specified, working
        .layer(axum::middleware::from_fn(mw_sample));

    run(app).await
}

Cargo.toml:

[package]
name = "test"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.7.5"
lambda_http = "0.13.0"
serde = "1.0.209"
serde_json = "1.0.127"
tokio = { version = "1", features = ["macros"] }

I will attach my Terraform code as well:

module "api_lambda_function" {
  source = "terraform-aws-modules/lambda/aws"

  function_name = "some-api"

  runtime       = "provided.al2023"
  architectures = ["arm64"]
  timeout       = 15

  create_package         = false
  // Change path to your bin
  local_existing_package = "../../../target/lambda/simple2/bootstrap.zip"
  handler                = "bootstrap"

  create_current_version_allowed_triggers = false
  allowed_triggers = {
    AllowExecutionFromAPIGateway = {
      service    = "apigateway"
      source_arn = "${aws_apigatewayv2_api.main.execution_arn}/*"
    }
  }
}

resource "aws_apigatewayv2_api" "main" {
  name          = "${local.name_prefix}-api"
  protocol_type = "HTTP"
}

resource "aws_apigatewayv2_stage" "v1" {
  api_id      = aws_apigatewayv2_api.main.id
  name        = "v1"
  auto_deploy = true
}

resource "aws_apigatewayv2_integration" "api_lambda" {
  api_id                 = aws_apigatewayv2_api.main.id
  integration_type       = "AWS_PROXY"
  integration_method     = "POST"
  integration_uri        = module.api_lambda_function.lambda_function_arn
  payload_format_version = "2.0"
  depends_on             = [module.api_lambda_function.lambda_function_arn]
}

resource "aws_apigatewayv2_route" "get_1" {
  api_id    = aws_apigatewayv2_api.main.id
  route_key = "GET /not-staged"
  target    = "integrations/${aws_apigatewayv2_integration.api_lambda.id}"
}

resource "aws_apigatewayv2_route" "get_2" {
  api_id    = aws_apigatewayv2_api.main.id
  route_key = "GET /staged"
  target    = "integrations/${aws_apigatewayv2_integration.api_lambda.id}"
}

Build with:

cargo lambda build --release --arm64 --output-format zip

And then apply Terraform.

When calling a /v1/staged route, response is returned:
image

But not for /v1/not-staged - response is 404:
image

Am I doing something wrong?
Can someone please suggest how to debug this issue?

@jfkisafk
Copy link
Contributor

jfkisafk commented Aug 28, 2024

It seems this is where the env_variable is handled:

#[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))]
fn apigw_path_with_stage(stage: &Option<String>, path: &str) -> String {
if env::var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH").is_ok() {
return path.into();
}
let stage = match stage {
None => return path.into(),
Some(stage) if stage == "$default" => return path.into(),
Some(stage) => stage,
};
let prefix = format!("/{stage}/");
if path.starts_with(&prefix) {
path.into()
} else {
format!("/{stage}{path}")
}
}

  1. Can you please indicated which request payload format version are you using 1.0 or 2.0? -> it is 2.0 based on your terraform.
  2. Also, can you confirm if you have tried setting the env variable AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH in your Lambda Function configuration (through terraform) and it still doesn't work?

@calavera
Copy link
Contributor

I think there is a misconception on what this variable is for.

If the variable is not set, and your lambda event has a stage in the JSON payload, the runtime will add the stage to the path, so the function code receives the full path that APIGW received, in your case v1 + staged.

If the variable is set, the runtime will not add the stage to the path.

However, if your path includes the stage directly, like /v1/not-staged, the runtime, will not remove the stage from the path.

I hope that makes sense.

@artemudovyk
Copy link
Author

artemudovyk commented Aug 29, 2024

Thank you.

2. Also, can you confirm if you have tried setting the env variable AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH in your Lambda Function configuration (through terraform) and it still doesn't work?

Yes, I've also tried to apply an env variable with Terraform — it behaved the same.

However, if your path includes the stage directly, like /v1/not-staged, the runtime, will not remove the stage from the path.

Yes, I understand. I don't expect it to remove the stage from the specified route. I've used both prefixed and unprefixed routes for demonstration purposes. In my example, the /not-staged you mentioned is specified without a /v1/ prefix, which doesn't work with the env variable in question is set.

I would expect that the configuration:

  std::env::set_var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH", "true");

  let app = Router::new()
      .route("/route1", get(handler_sample))
      .route("/route2", get(handler_sample));

could handle any stage in the API Gateway (/v1/route1, /v2/route1, /route1 etc.), but I've only got 404 responses from Lambda (not from API Gateway) instead. This configuration only works for me on the $default stage (without prefix).

@moranbw
Copy link

moranbw commented Oct 31, 2024

I am seeing the same behavior as @artemudovyk for API Gateway V2. Don't mean to create a +1 comment, but just wanted to see if this was going to be addressed of if there is anything we can do to help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants