Skip to content

Instantly share code, notes, and snippets.

@marcogrcr
Last active November 28, 2024 02:10
Show Gist options
  • Save marcogrcr/1cce0db789ead5ae6a2d812936ba4b2e to your computer and use it in GitHub Desktop.
Save marcogrcr/1cce0db789ead5ae6a2d812936ba4b2e to your computer and use it in GitHub Desktop.
AWS Lambda HTTP event comparison

AWS Lambda HTTP event comparison

AWS Lambda allows to create HTTP APIs fronted by API Gateway (REST and HTTP) and ALB (Application Load Balancer) endpoints. However, the input events vary depending on the endpoint. In order to write code that is compatible with all endpoint, certain differences need to be accounted for.

Methodology

The following endpoint configuration was applied to each endpoint type:

  • ALBEvent(single): ALB, default Internet-visible HTTP endpoint, no auth, single-value query/headers.
  • ALBEvent(multi): ALB, default Internet-visible HTTP endpoint, no auth, multi-value query/headers.
  • APIGatewayProxyEvent(rest): REST API Gateway, default HTTPS endpoint, no auth.
  • APIGatewayProxyEvent(1.0): HTTP API Gateway, default HTTPS endpoint, no auth, 1.0 payload format.
  • APIGatewayProxyEventV2: HTTP API Gateway, default HTTPS endpoint, no auth, 2.0 payload format.

The following curl basic request was sent to each endpoint:

curl -H 'no-value-header;' -H 'multi-value-header: 1' -H 'multi-value-header;' -H 'multi-value-header: 2' -H 'multi-value-header: 3,4' -H 'Mixed-Case: header' "http://${endpoint}/?multi-value-query=1&multi-value-query&multi-value-query=&multi-value-query=2&multi-value-query=3,4&no-equal-query&no-value-query=&enco%2Fded=qu%2Fery"

which translates to:

GET /?multi-value-query=1&multi-value-query&multi-value-query=&multi-value-query=2&multi-value-query=3,4&no-equal-query&no-value-query=&enco%2Fded=qu%2Fery HTTP/1.1
no-value-header:
multi-value-header: 1
multi-value-header:
multi-value-header: 2
multi-value-header: 3,4
Mixed-Case: header
host: {endpoint}

The request was sent five times, each time with the following additional arguments:

# a: uppercase Cookie, single header with two cookies, space separated, trailing semicolon
-H 'Cookie: cookie1=1; cookie2=a;'

# b: lowercase cookie, single header with two cookies, no spaces, no trailing semicolon
-H 'cookie: cookie1=1;cookie2=b'

# c: lowercase cookie, single header with two cookies, no spaces, trailing semicolon
-H 'cookie: cookie1=1;cookie2=c;'

# d: lowercase cookie, two headers, no trailing semicolon
-H 'cookie: cookie1=1' -H 'cookie: cookie2=d'

# e: lowercase cookie, two headers, trailing semicolon
-H 'cookie: cookie1=1;' -H 'cookie: cookie2=e;'

As of 2024-11-27, the results are as follows.

ALBEvent(single)

Notable observations:

  • Event:
    • HTTP method is in httpMethod field.
    • HTTP path is in path field.
    • HTTP query is in queryStringParameters field, multiValueQueryStringParameters is missing.
    • HTTP headers is in headers field, multiValueHeaders is missing.
    • requestContext field only has an elb key.
  • Headers:
    • no-value-header is removed.
    • multi-value-header only has the last header value (3,4).
    • Mixed-Case header is normalized to lowercase.
    • Cookies are normalized in a cookie lowercase header. Subtle differences exist between requests (spaces, extra/trailing semicolon).
  • Query:
    • URL-encoded query parameters are sent "as is".
    • multi-value-query only has the last query string value (3,4).
    • no-equal-query is retained with a value of "".
    • no-value-query is retained with a value of "".
    • enco%2Fded and its value is kept "as is" (no URL-decoding) with a { "enco%2Fded": "qu%2Fery" } value.
const event = {
  requestContext: {
    elb: {
      targetGroupArn:
        "arn:aws:elasticloadbalancing:us-east-1:(redacted):targetgroup/(redacted)/(redacted)",
    },
  },
  httpMethod: "GET",
  path: "/",
  queryStringParameters: {
    "enco%2Fded": "qu%2Fery",
    "multi-value-query": "3,4",
    "no-equal-query": "",
    "no-value-query": "",
  },
  headers: {
    accept: "*/*",
    cookie:
      "cookie1=1; cookie2=a;" |
      "cookie1=1;cookie2=b" |
      "cookie1=1;cookie2=c;" |
      "cookie1=1; cookie2=d" |
      "cookie1=1;; cookie2=e;",
    host: "(redacted).us-east-1.elb.amazonaws.com",
    "mixed-case": "header",
    "multi-value-header": "3,4",
    "user-agent": "curl/8.7.1",
    "x-amzn-trace-id": "Root=(redacted)",
    "x-forwarded-for": "(redacted)",
    "x-forwarded-port": "80",
    "x-forwarded-proto": "http",
  },
  body: "",
  isBase64Encoded: false,
};

ALBEvent(multi)

Notable observations:

  • Event:
    • HTTP method is in httpMethod field.
    • HTTP path is in path field.
    • HTTP query is in multiValueQueryStringParameters field, queryStringParameters is missing.
    • HTTP headers is in multiValueHeaders field, headers is missing
    • requestContext field only has an elb key.
  • Headers:
    • no-value-header is removed.
    • multi-value-header retains all non-empty headers (["1", "2", "3,4"]).
    • Mixed-Case header is normalized to lowercase.
    • Cookies are normalized in a cookie lowercase header. Subtle differences exist between requests (spaces, extra/trailing semicolon).
  • Query:
    • URL-encoded query parameters are sent "as is".
    • multi-value-query retains all values.
    • no-equal-query is retained with a value of [""].
    • no-value-query is retained with a value of [""].
    • enco%2Fded and its value is kept "as is" (no URL-decoding) with a { "enco%2Fded": ["qu%2Fery"] } value.
const request = {
  requestContext: {
    elb: {
      targetGroupArn:
        "arn:aws:elasticloadbalancing:us-east-1:(redacted):targetgroup/(redacted)/(redacted)",
    },
  },
  httpMethod: "GET",
  path: "/",
  multiValueQueryStringParameters: {
    "enco%2Fded": ["qu%2Fery"],
    "multi-value-query": ["1", "", "", "2", "3,4"],
    "no-equal-query": [""],
    "no-value-query": [""],
  },
  multiValueHeaders: {
    accept: ["*/*"],
    cookie: [
      "cookie1=1; cookie2=a;" |
        "cookie1=1;cookie2=b" |
        "cookie1=1;cookie2=c;" |
        "cookie1=1; cookie2=d" |
        "cookie1=1;; cookie2=e;",
    ],
    host: ["(redacted).us-east-1.elb.amazonaws.com"],
    "mixed-case": ["header"],
    "multi-value-header": ["1", "2", "3,4"],
    "user-agent": ["curl/8.7.1"],
    "x-amzn-trace-id": ["Root=(redacted)"],
    "x-forwarded-for": ["(redacted)"],
    "x-forwarded-port": ["80"],
    "x-forwarded-proto": ["http"],
  },
  body: "",
  isBase64Encoded: false,
};

APIGatewayProxyEvent(rest)

Notable observations:

  • Event:
    • HTTP method is in httpMethod field.
    • HTTP path is in path field.
    • HTTP query are in queryStringParameters and multiValueQueryStringParameters fields.
    • HTTP headers are in headers and multiValueHeaders fields.
    • requestContext field has a resourceId key.
  • Headers:
    • no-value-header is retained with a value of "" in headers and [""] in multiValueHeaders.
    • multi-value-header only has the last header value (3,4) in headers.
    • multi-value-header retains all values in multiValueHeaders.
    • Mixed-Case header is normalized to lowercase.
    • Cookies are normalized in a cookie lowercase header. Subtle differences exist between requests (spaces, extra/trailing semicolon).
  • Query:
    • multi-value-query only has the last header value (3,4) in queryStringParameters.
    • multi-value-query retains all values in multiValueQueryStringParameters.
    • no-equal-query is retained with a value of "" in queryStringParameters and [""] in multiValueQueryStringParameters.
    • no-value-query is retained with a value of "" in queryStringParameters and [""] in multiValueQueryStringParameters.
    • enco%2Fded and its value is URL-decoded to { "enco/ded" : "qu/ery" } in queryStringParameters and { "enco/ded" : ["qu/ery"] } in multiValueQueryStringParameters.
const event = {
  resource: "/",
  path: "/",
  httpMethod: "GET",
  headers: {
    accept: "_/_",
    cookie:
      "cookie1=1; cookie2=a;" |
      "cookie1=1;cookie2=b" |
      "cookie1=1;cookie2=c;" |
      "cookie1=1; cookie2=d" |
      "cookie1=1;; cookie2=e;",
    Host: "(redacted).execute-api.us-east-1.amazonaws.com",
    "mixed-case": "header",
    "multi-value-header": "3,4",
    "no-value-header": "",
    "User-Agent": "curl/8.7.1",
    "X-Amzn-Trace-Id": "Root=(redacted)",
    "X-Forwarded-For": "(redacted)",
    "X-Forwarded-Port": "443",
    "X-Forwarded-Proto": "https",
  },
  multiValueHeaders: {
    accept: ["*/*"],
    cookie: [
      "cookie1=1; cookie2=a;" |
        "cookie1=1;cookie2=b" |
        "cookie1=1;cookie2=c;" |
        "cookie1=1; cookie2=d" |
        "cookie1=1;; cookie2=e;",
    ],
    Host: ["(redacted).execute-api.us-east-1.amazonaws.com"],
    "mixed-case": ["header"],
    "multi-value-header": ["1", "", "2", "3,4"],
    "no-value-header": [""],
    "User-Agent": ["curl/8.7.1"],
    "X-Amzn-Trace-Id": ["Root=(redacted)"],
    "X-Forwarded-For": ["(redacted)"],
    "X-Forwarded-Port": ["443"],
    "X-Forwarded-Proto": ["https"],
  },
  queryStringParameters: {
    "enco/ded": "qu/ery",
    "multi-value-query": "3,4",
    "no-equal-query": "",
    "no-value-query": "",
  },
  multiValueQueryStringParameters: {
    "enco/ded": ["qu/ery"],
    "multi-value-query": ["1", "", "", "2", "3,4"],
    "no-equal-query": [""],
    "no-value-query": [""],
  },
  pathParameters: null,
  stageVariables: null,
  requestContext: {
    resourceId: "(redacted)",
    resourcePath: "/",
    httpMethod: "GET",
    extendedRequestId: "(redacted)",
    requestTime: "27/Nov/2024:22:08:17 +0000",
    path: "/{stage}/",
    accountId: "(redacted)",
    protocol: "HTTP/1.1",
    stage: "(redacted)",
    domainPrefix: "(redacted)",
    requestTimeEpoch: 1732745297421,
    requestId: "(redacted)",
    identity: {
      cognitoIdentityPoolId: null,
      accountId: null,
      cognitoIdentityId: null,
      caller: null,
      sourceIp: "(redacted)",
      principalOrgId: null,
      accessKey: null,
      cognitoAuthenticationType: null,
      cognitoAuthenticationProvider: null,
      userArn: null,
      userAgent: "curl/8.7.1",
      user: null,
    },
    domainName: "(redacted).execute-api.us-east-1.amazonaws.com",
    deploymentId: "(redacted)",
    apiId: "(redacted)",
  },
  body: null,
  isBase64Encoded: false,
};

APIGatewayProxyEvent(1.0)

Notable observations:

  • Event:
    • HTTP method is in httpMethod field.
    • HTTP path is in path field.
    • HTTP query are in queryStringParameters and multiValueQueryStringParameters fields.
    • HTTP headers are in headers and multiValueHeaders fields.
    • requestContext field has a resourceId key.
  • Headers:
    • no-value-header is retained with a value of "" in headers and [""] in multiValueHeaders.
    • multi-value-header only has the last header value (3,4) in headers.
    • multi-value-header retains all values in multiValueHeaders.
    • Mixed-Case header is normalized to lowercase.
    • Cookies are normalized in a Cookie capitalized header. Subtle differences exist between requests (spaces, extra/trailing semicolon).
  • Query:
    • multi-value-query only has the last header value (3,4) in queryStringParameters.
    • multi-value-query retains all values in multiValueQueryStringParameters.
    • no-equal-query is retained with a value of "" in queryStringParameters and [""] in multiValueQueryStringParameters.
    • no-value-query is retained with a value of "" in queryStringParameters and [""] in multiValueQueryStringParameters.
    • enco%2Fded and its value is URL-decoded to { "enco/ded" : "qu/ery" } in queryStringParameters and { "enco/ded" : ["qu/ery"] } in multiValueQueryStringParameters.
const event = {
  version: "1.0",
  resource: "/",
  path: "/",
  httpMethod: "GET",
  headers: {
    "Content-Length": "0",
    Cookie:
      "cookie1=1; cookie2=a" |
      "cookie1=1; cookie2=b" |
      "cookie1=1; cookie2=c" |
      "cookie1=1; cookie2=d" |
      "cookie1=1; cookie2=e",
    Host: "(redacted).execute-api.us-east-1.amazonaws.com",
    "User-Agent": "curl/8.7.1",
    "X-Amzn-Trace-Id": "Root=(redacted)",
    "X-Forwarded-For": "(redacted)",
    "X-Forwarded-Port": "443",
    "X-Forwarded-Proto": "https",
    accept: "_/_",
    "mixed-case": "header",
    "multi-value-header": "3,4",
    "no-value-header": "",
  },
  multiValueHeaders: {
    "Content-Length": ["0"],
    Cookie: [
      "cookie1=1; cookie2=a" |
        "cookie1=1; cookie2=b" |
        "cookie1=1; cookie2=c" |
        "cookie1=1; cookie2=d" |
        "cookie1=1; cookie2=e",
    ],
    Host: ["(redacted).execute-api.us-east-1.amazonaws.com"],
    "User-Agent": ["curl/8.7.1"],
    "X-Amzn-Trace-Id": ["Root=(redacted)"],
    "X-Forwarded-For": ["(redacted)"],
    "X-Forwarded-Port": ["443"],
    "X-Forwarded-Proto": ["https"],
    accept: ["*/*"],
    "mixed-case": ["header"],
    "multi-value-header": ["1", "", "2", "3,4"],
    "no-value-header": [""],
  },
  queryStringParameters: {
    "enco/ded": "qu/ery",
    "multi-value-query": "3,4",
    "no-equal-query": "",
    "no-value-query": "",
  },
  multiValueQueryStringParameters: {
    "enco/ded": ["qu/ery"],
    "multi-value-query": ["1", "", "", "2", "3,4"],
    "no-equal-query": [""],
    "no-value-query": [""],
  },
  requestContext: {
    accountId: "(redacted)",
    apiId: "(redacted)",
    domainName: "(redacted).execute-api.us-east-1.amazonaws.com",
    domainPrefix: "(redacted)",
    extendedRequestId: "(redacted)",
    httpMethod: "GET",
    identity: {
      accessKey: null,
      accountId: null,
      caller: null,
      cognitoAmr: null,
      cognitoAuthenticationProvider: null,
      cognitoAuthenticationType: null,
      cognitoIdentityId: null,
      cognitoIdentityPoolId: null,
      principalOrgId: null,
      sourceIp: "(redacted)",
      user: null,
      userAgent: "curl/8.7.1",
      userArn: null,
    },
    path: "/",
    protocol: "HTTP/1.1",
    requestId: "(redacted)",
    requestTime: "27/Nov/2024:22:18:50 +0000",
    requestTimeEpoch: 1732745930138,
    resourceId: "ANY /",
    resourcePath: "/",
    stage: "$default",
  },
  pathParameters: null,
  stageVariables: null,
  body: null,
  isBase64Encoded: false,
};

APIGatewayProxyEventV2

Notable observations:

  • Event:
    • HTTP method is in requestContext.http.method field.
    • HTTP path is in rawPath field.
    • HTTP query is in queryStringParameters and rawQueryString fields, multiValueQueryStringParameters is missing.
    • HTTP headers is in headers field, multiValueHeaders is missing.
    • requestContext field has a resourceId key.
  • Headers:
    • no-value-header is retained with a value of "".
    • multi-value-header retains all values in a comma-separated format (1,,2,3,4) in headers. This introduces ambiguity if a header value has a comma.
    • Mixed-Case header is normalized to lowercase.
    • Cookies are normalized cookies event field with the value ["cookie1=1", "cookie2={x}"].
  • Query:
    • URL-encoded query parameters are URL-decoded.
    • multi-value-query retains all values in a comma-separated format (1,,,2,3,4) in queryStringParameters. This introduces ambiguity if a query value has a comma.
    • no-equal-query is retained with a value of "" in queryStringParameters and "as is" in rawQueryString.
    • no-value-query is retained with a value of "" in queryStringParameters and "as is" in rawQueryString.
    • enco%2Fded and its value is URL-decoded to { "enco/ded" : "qu/ery" } in queryStringParameters and "as is" in rawQueryString.
const event = {
  version: "2.0",
  routeKey: "ANY /",
  rawPath: "/",
  rawQueryString:
    "multi-value-query=1&multi-value-query&multi-value-query=&multi-value-query=2&multi-value-query=3,4&no-equal-query&no-value-query=&enco%2Fded=qu%2Fery",
  cookies:
    ["cookie1=1", "cookie2=a"] |
    ["cookie1=1", "cookie2=b"] |
    ["cookie1=1", "cookie2=c"] |
    ["cookie1=1", "cookie2=d"] |
    ["cookie1=1", "cookie2=e"],
  ,
  headers: {
    accept: "_/_",
    "content-length": "0",
    host: "(redacted).execute-api.us-east-1.amazonaws.com",
    "mixed-case": "header",
    "multi-value-header": "1,,2,3,4",
    "no-value-header": "",
    "user-agent": "curl/8.7.1",
    "x-amzn-trace-id": "Root=(redacted)",
    "x-forwarded-for": "(redacted)",
    "x-forwarded-port": "443",
    "x-forwarded-proto": "https",
  },
  queryStringParameters: {
    "enco/ded": "qu/ery",
    "multi-value-query": "1,,,2,3,4",
    "no-equal-query": "",
    "no-value-query": "",
  },
  requestContext: {
    accountId: "(redacted)",
    apiId: "(redacted)",
    domainName: "(redacted).execute-api.us-east-1.amazonaws.com",
    domainPrefix: "(redacted)",
    http: {
      method: "GET",
      path: "/",
      protocol: "HTTP/1.1",
      sourceIp: "(redacted)",
      userAgent: "curl/8.7.1",
    },
    requestId: "(redacted)",
    routeKey: "ANY /",
    stage: "$default",
    time: "27/Nov/2024:22:21:56 +0000",
    timeEpoch: 1732746116434,
  },
  isBase64Encoded: false,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment