awslabs / aws-lambda-rust-runtime

A Rust runtime for AWS Lambda
Apache License 2.0
3.29k stars 335 forks source link

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

Closed npellegrin closed 2 months ago

npellegrin commented 2 months ago

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:

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 commented 2 months ago

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

github-actions[bot] commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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.