API Gateway Invocations

Invocations via an API Gateway require extra care when parsing the input event, and when formatting results. For the most part, lambdr will handle this automatically. But for highly custom serialisation/deserialisation or edge cases it may be helpful to understand how content via API Gateways is structured and responded to.

Events via API Gateways

When a Lambda runtime, like that implemented by lambdr, starts listening for inputs it queries the “next invocation” HTTP endpoint. The response to this is what’s called here an event. The content of this event contains the arguments (if any) to the function. After the result has been calculate, the runtime must format the result and submit it to the “result” HTTP endpoint.

With direct invocation the content of the event contains a string which — when interpreted as JSON — gives the arguments for the handler function. When a Lambda is invoked via an API Gateway then the content of the event contains significantly more detail and requires careful parsing.

There are two types of API Gateways to consider: REST and HTML.

REST API Gateway events

An example of REST API Gateway event content is given at the bottom of this section but the two most important sections are body and queryStringParameters.

The queryStringParameters contain standard JSON. So if the request contains a parameter ?number=9 in the query then the event will contain "queryStringParameters": {"number": "9"} (note the string).

The body of the event contains the data passed during a POST operation. It contains stringified JSON. That is, instead of "body": {"number": 9} we would see "body": "{\"number\":9}".

Stringified JSON can be produced with the as_stringified_json helper function which wraps jsonlite’s function. There is a slight difference in behaviour, as this function also automatically unboxes singleton values and also represents NULLs as JSON nulls — a convention that is used by the event body provided by AWS Lambda.

When the result is calculated and ready to be sent back to the AWS Lambda response endpoint it must be formatted in a very specific way in order to be compatible with the API Gateway. The correct format is as below, with the body containing the stringified JSON representation of the result. This is given as an R list which is then converted to stringified JSON. Compare this to the representation of the result for a direct invocation:

## Formatting a result for a direct invocation
as_stringified_json(result)

## Formatting a result for an invocation via an API Gateway
as_stringified_json(
  list(
    isBase64Encoded = FALSE,
    statusCode = 200L,
    body = as_stringified_json(result)
  )
)

Here is an example event content from an invocation that is coming via an API Gateway. The invocation is a call to a parity function with an argument number = 9. Some information has been censored.

{
"resource": "/parity",
"path": "/parity",
"httpMethod": "POST",
"headers": {
  "accept": "*/*",
  "Host": "abcdefghijk.execute-api.ap-southeast-2.amazonaws.com",
  "User-Agent": "curl/7.64.1",
  "X-Amzn-Trace-Id": "Root=1-615e4711-5f239aad2b046b5609e43b1c",
  "X-Forwarded-For": "192.168.1.1",
  "X-Forwarded-Port": "443",
  "X-Forwarded-Proto": "https"
},
"multiValueHeaders": {
  "accept": [
    "*/*"
  ],
  "Host": [
    "abcdefghijk.execute-api.ap-southeast-2.amazonaws.com"
  ],
  "User-Agent": [
    "curl/7.64.1"
  ],
  "X-Amzn-Trace-Id": [
    "Root=1-615e4711-5f239aad2b046b5609e43b1c"
  ],
  "X-Forwarded-For": [
    "192.168.1.1"
  ],
  "X-Forwarded-Port": [
    "443"
  ],
  "X-Forwarded-Proto": [
    "https"
  ]
},
"queryStringParameters": null,
"multiValueQueryStringParameters": null,
"pathParameters": null,
"stageVariables": null,
"requestContext": {
  "resourceId": "abcdef",
  "resourcePath": "/parity",
  "httpMethod": "POST",
  "extendedRequestId": "G0AKsFXISwMFsGA=",
  "requestTime": "07/Oct/2021:01:02:09 +0000",
  "path": "/test/parity",
  "accountId": "1234567890",
  "protocol": "HTTP/1.1",
  "stage": "test",
  "domainPrefix": "abcdefghijk",
  "requestTimeEpoch": 1633568529038,
  "requestId": "59bbb4c9-9d24-4cbb-941b-60dd4969e9c5",
  "identity": {
    "cognitoIdentityPoolId": null,
    "accountId": null,
    "cognitoIdentityId": null,
    "caller": null,
    "sourceIp": "192.168.1.1",
    "principalOrgId": null,
    "accessKey": null,
    "cognitoAuthenticationType": null,
    "cognitoAuthenticationProvider": null,
    "userArn": null,
    "userAgent": "curl/7.64.1",
    "user": null
  },
  "domainName": "abcdefghijk.execute-api.ap-southeast-2.amazonaws.com",
  "apiId": "abcdefghijk"
},
"body": "{\"number\":9}",
"isBase64Encoded": false
}

HTML API Gateway events

HTML API Gateway events are similar to REST API Gateway events, except that the body is more likely to be Base64 encoded, and the query parameters are presented as string values. That is, we might expect to see a rawQueryString of “parameter1=value1&parameter1=value2&parameter2=value”. In this case however the queryStringParameters (if present) will be easier to work with:

"queryStringParameters": {
    "parameter1": "value1,value2",
    "parameter2": "value"
  }

A full example of an event body is shown below. Note that the queryStringParameters will not appear if rawQueryString is an empty string.

{
    "version": "2.0",
    "routeKey": "ANY /parity",
    "rawPath": "/default/parity",
    "rawQueryString": "parameter1=value1&parameter1=value2&parameter2=value",
    "queryStringParameters": {
      "parameter1": "value1,value2",
      "parameter2": "value"
    },
    "headers": {
        "accept": "*/*",
        "content-length": "12",
        "content-type": "application/x-www-form-urlencoded",
        "host": "abcdefghi.execute-api.ap-southeast-2.amazonaws.com",
        "user-agent": "curl/7.64.1",
        "x-amzn-trace-id": "Root=1-6167f9fb-1ada874811eaf2bc1464c679",
        "x-forwarded-for": "192.168.1.1",
        "x-forwarded-port": "443",
        "x-forwarded-proto": "https"
    },
    "requestContext": {
        "accountId": "123456789",
        "apiId": "abcdefghi",
        "domainName": "abcdefghi.execute-api.ap-southeast-2.amazonaws.com",
        "domainPrefix": "abcdefghi",
        "http": {
            "method": "POST",
            "path": "/default/parity",
            "protocol": "HTTP/1.1",
            "sourceIp": "192.168.1.1",
            "userAgent": "curl/7.64.1"
        },
        "requestId": "HMP_QiusywMEPEg=",
        "routeKey": "ANY /parity",
        "stage": "default",
        "time": "14/Oct/2021:09:35:55 +0000",
        "timeEpoch": 1634204155055
    },
    "body": "eyJudW1iZXIiOjd9",
    "isBase64Encoded": true
}