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

Cannot deserialize API Gateway v2 HTTP Request when only apigw_http feature is specified in Cargo.toml #888

Closed
npellegrin opened this issue Jun 2, 2024 · 4 comments

Comments

@npellegrin
Copy link

I have deployed an HTTP API (Amazon Api Gateway v2) with a Rust lambda backend, but I am facing some problems with request deserialization. I am not sure if it is a bug or a mistake on my side, but it seems the lambda-http module only recognize an Amazon API Gateway v2 HTTP Lambda Proxy Integration Request format when both apigw_rest and apigw_http features are enabled. I expected to enable only apigw_http feature when working with HTTP APIs (apigateway v2).

The behavior is:

  • When specifying features = ["apigw_http"] in Cargo.toml, any request thows the error: this function expects a JSON payload from Amazon API Gateway, Amazon Elastic Load Balancer, or AWS Lambda Function URLs, but the data doesn't match any of those services' events"
  • When specifying features = ["apigw_rest", "apigw_http"] in Cargo.toml: it works

Below are the resources to reproduce the issue.

Lambda source code

use lambda_http::{run, http::{StatusCode, Response}, service_fn, Error, IntoResponse, Request, RequestPayloadExt};
use serde::{Deserialize, Serialize};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt()
        .without_time()
        .with_max_level(tracing::Level::INFO)
        .init();
    run(service_fn(function_handler)).await
}

pub async fn function_handler(event: Request) -> Result<impl IntoResponse, Error> {
    let body = event.payload::<MyPayload>()?;

    let response = Response::builder()
        .status(StatusCode::OK)
        .header("Content-Type", "application/json")
        .body(json!({
            "message": "Hello World",
            "payload": body,
          }).to_string())
        .map_err(Box::new)?;

    Ok(response)
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct MyPayload {
    pub prop1: String,
    pub prop2: String,
}

Invalid Cargo.toml

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

[dependencies]
serde = "1.0.203"
serde_json = "1.0.117"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
tokio = { version = "1", features = ["full"] }

[dependencies.lambda_http]
version = "0.11.1"
default-features = false
features = ["apigw_http"]

[dev-dependencies]
tokio-test = "0.4.2"

Working Cargo.toml configuration

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

[dependencies]
serde = "1.0.203"
serde_json = "1.0.117"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
tokio = { version = "1", features = ["full"] }

[dependencies.lambda_http]
version = "0.11.1"
default-features = false
features = ["apigw_rest", "apigw_http"]

[dev-dependencies]
tokio-test = "0.4.2"

JSON Event from API integration reproducing the problem

{
    "version": "1.0",
    "resource": "/{proxy+}",
    "path": "/",
    "httpMethod": "GET",
    "headers": {
        "Content-Length": "0",
        "Host": "myapi.example.com",
        "Postman-Token": "xxx",
        "User-Agent": "PostmanRuntime/7.37.3",
        "X-Amz-Date": "20240602T152145Z",
        "X-Amz-Security-Token": "...",
        "X-Amzn-Trace-Id": "...",
        "X-Forwarded-For": "...",
        "X-Forwarded-Port": "443",
        "X-Forwarded-Proto": "https",
        "accept": "*/*",
        "accept-encoding": "gzip, deflate, br",
        "authorization": "...",
        "cache-control": "no-cache"
    },
    "multiValueHeaders": {
        "Content-Length": [
            "0"
        ],
        "Host": [
            "myapi.example.com"
        ],
        "Postman-Token": [
            "xxx"
        ],
        "User-Agent": [
            "PostmanRuntime/7.37.3"
        ],
        "X-Amz-Date": [
            "20240602T152145Z"
        ],
        "X-Amz-Security-Token": [
            "..."
        ],
        "X-Amzn-Trace-Id": [
            "..."
        ],
        "X-Forwarded-For": [
            "..."
        ],
        "X-Forwarded-Port": [
            "443"
        ],
        "X-Forwarded-Proto": [
            "https"
        ],
        "accept": [
            "*/*"
        ],
        "accept-encoding": [
            "gzip, deflate, br"
        ],
        "authorization": [
            "..."
        ],
        "cache-control": [
            "no-cache"
        ]
    },
    "queryStringParameters": null,
    "multiValueQueryStringParameters": null,
    "requestContext": {
        "accountId": "--REDACTED--",
        "apiId": "...",
        "domainName": "myapi.example.com",
        "domainPrefix": "api",
        "extendedRequestId": "",
        "httpMethod": "GET",
        "identity": {
            "accessKey": "...",
            "accountId": "--REDACTED--",
            "caller": "...",
            "cognitoAmr": null,
            "cognitoAuthenticationProvider": null,
            "cognitoAuthenticationType": null,
            "cognitoIdentityId": null,
            "cognitoIdentityPoolId": null,
            "principalOrgId": "aws:PrincipalOrgID",
            "sourceIp": "...",
            "user": "...",
            "userAgent": "PostmanRuntime/7.37.3",
            "userArn": "..."
        },
        "path": "/",
        "protocol": "HTTP/1.1",
        "requestId": "...",
        "requestTime": "02/Jun/2024:15:21:45 +0000",
        "requestTimeEpoch": 1717341705848,
        "resourceId": "ANY /{proxy+}",
        "resourcePath": "/{proxy+}",
        "stage": "$default"
    },
    "pathParameters": {
        "proxy": ""
    },
    "stageVariables": null,
    "body": null,
    "isBase64Encoded": false
}
@calavera
Copy link
Contributor

calavera commented Jun 2, 2024

Your payload example shows that you're not receiving payloads from an APIGW HTTP endpoint, but an APIGW Rest endpoint. The version field is the indicator:

{
    "version": "1.0",

If your integrating send events from APIGW HTTP, the version would be 2.0. See this payload as an example: https://github.com/awslabs/aws-lambda-rust-runtime/blob/main/lambda-events/src/fixtures/example-apigw-v2-request-no-authorizer.json#L2

@calavera calavera closed this as completed Jun 2, 2024
Copy link

github-actions bot commented Jun 2, 2024

This issue is now closed. Comments on closed issues are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.

@npellegrin
Copy link
Author

Thank you @calavera for your quick answer.

The payload actually came from an authentic HTTP API Gateway v2. But you are correct, the 1.0 payload compatibility was activated on it.

As I understand, apigw_http feature flag will currently only works with 2.0 payloads, even if 1.0 payloads are available in HTTP APIs. Is it a design choice in the module to not support 1.0 payloads with apigw_http ? Can we add a note about it in the README to prevent further mistakes when using this feature flag ?

@calavera
Copy link
Contributor

calavera commented Jun 2, 2024

This is the first time I see version 1 being used with an HTTP endpoint, tbh. The feature name is just to indicate how we part the event payloads, but it really doesn't matter where they come from. I'm ok documenting this in the readme.

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

2 participants