--- title: "API Gateway Invocations" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{API Gateway Invocations} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- 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 \code{\link[jsonlite]{toJSON}} 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: ```r ## 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. ```json { "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¶meter1=value2¶meter2=value". In this case however the `queryStringParameters` (if present) will be easier to work with: ```json "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. ```json { "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 } ```