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.
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.
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 NULL
s 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 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¶meter1=value2¶meter2=value”. In this
case however the queryStringParameters
(if present) will be
easier to work with:
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¶meter1=value2¶meter2=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
}