Integrating API Gateway — Lambda Responses

Daniel Ilie
The Startup
Published in
7 min readOct 21, 2020

--

Probably the most used, versatile and oldest combination between AWS cloud services is REST APIs (public) with Lambda integration on the backend [1]. While RESTful API development is not at all new, if you are relatively new to developing in the cloud, working with these two services may be somewhat challenging. This article requires a basic understanding of these two services. Its focus is beyond the point where you are able to invoke a Lambda function and receive status code 200 from the API. Given the availability of SNS and CloudWatch Logs, why would one bother integrating the API Method & Integration Responses past returning code 200? The answer is that would be a half implemented feature, which lets users speculating about what the result of the invocation was. This article shows how to program an API to return status codes that make sense to your application.

My Use Case

I shall focus on a Lambda function which may throw unexpected exceptions, errors and return a response. To make this more challenging, I am going to use Python. This language is a big hit with developers transitioning from traditional scientific and engineering roles, who have not used any “JS” languages. The intention is to create a foundation on which further API development can be done or at least leave the API in a minimum viable state.

My Solution

I started creating the Lambda function, which I called DanielHTTP. The function uses the latest Python runtime and the IAM policy AWSLambdaBasicExecutionRole which allows CloudWatch logs to be created. I created a role called the same and attached the policy. Alternatively, when this function is created, I could have selected Create New Role to have this done automatically. The function code is available in my GitHub repository [2]. The code is designed to throw an exception by performing an illegal operation, to raise exceptions and errors and to return an Ok response. All cases are individually handled in the API.

It is a good idea to have a common design for the responses that I can easily control. Generally, I favor a two tier dictionary:

  1. "code", "type", "message" and "context” refer to: a programmable HTTP status code [3], severity type (Exception, Error, Info, etc.), the actual message to be communicated and the context surrounding the response
  2. "context" can contain anything I need to help out my users, including contextual data regarding the function invocation [4], in this case for Python

The RESTful API, also called DanielHTTP, uses a guid resource with a POST method. Next, I will show how this is assembled step-by-step.

A GUID query string is defined in the method request in order to be able to select the various execution path in the Lambda function:

The Lambda function and region are specified in the integration request. I was prompted that the API has been given permissions to invoke the Lambda function. At this stage, API Gateway should show as a trigger on the Lambda function page.

A mapping template is defined in the integration request as shown below. This allows the Lambda function to receive parameter data according to the code requirements.

The method response contains a single definition for HTTP status code 200. This code will always be returned regardless of the code path in Lambda, which it not helpful. I set the response body as indicated below.

Next, I defined code 400 for Errors and code 502 for Exceptions, returned by the function. The response body is identical in both cases.

The last step is to define the integration response [5]. During this phase of the API invocation the response from the Lambda is compared against the selectionPattern defined as a regex, then mapped to the defined method response status. The default mapping to status 200 is processed last, because such a selection pattern will match all error messages, including null, i.e., any unspecified error message.

Both the integration response mapped to code 400 and the one mapped to code 502, have the same mapping template definition as shown below. This allows the API to extract the value of "errorMessage" key in the returned dictionary from Lambda. I needed to save both the mapping template and the actual mapping.

The mapping template definition for code 200, allows the API to override the 200 code. Code 500 is returned instead, if the function response contains the word stackTrace. Again, I needed to save save twice.

I could have skipped all these steps by importing the API from my repository [2]. Instructions are provided.

I finalized API development by deploying it from the Actions Button > Deploy API. I named my stage prod.

My API Invocation

I retrieved the invoke URL by selecting the prod Stage from the Stages Section. The first invocation selects the path where illegal code is found. As the illegal code is executed an unhandled exception is raised.

At runtime, API Gateway matches the Lambda error’s errorMessage against the pattern of the regular expression on the selectionPattern property. If there is a match, API Gateway returns the Lambda error as an HTTP response of the corresponding HTTP status code. If there is no match, API Gateway returns the error as a default response or throws an invalid configuration exception if no default response is configured [6].

In the unhandled exception case above, neither Error or Exception were found in the value corresponding to the errorMessage key. Therefore, this is matched against the 200 code. This is not an Ok answer but one can always count on a stack trace being part of an exception, which is the case here. Therefore, code 500 is returned instead because of the overriding caused by finding stackTrace in the response [7].

The second and third invocations select the code paths where exceptions are raised by the developer. It is not possible to mimic the errorMessage response to get an error. The Lambda function must fail according to the runtime used. In Python, the raise Exception()does the job. The mapping template is used to discard the errorType and stackTrace keys, which are created when an exception is raised. Only the actual coded response is passed as serialized JSON string and assigned to the errorMessage key. The selectionPattern identifies Error and Exception words and returns the appropriate HTTP code.

The forth and final invocation covers the code path which returns a value when everything is Ok . The returned value is passed as a JSON object. Nothing is matched and code 200 is the method response.

AWS documentation provides detailed information on request/response parameter, status codes [8] and mapping templates [9]. If you are interested to do this in Node.js, Java or JavaScript, this article on the AWS Compute Blog is an excellent start [10].

Finally, I hope you enjoyed reading this and widened your understanding of integrating API Gateway — Lambda responses.

Further Reading

[1] API Gateway Earlier Updates

[2] Short-Examples Repository

[3] HTTP Status Codes

[4] AWS Lambda context object in Python

[5] Set up an integration response in API Gateway

[6] Handle Lambda errors in API Gateway

[7] Use a mapping template to override an API’s request and response parameters and status codes

[8] Amazon API Gateway API request and response data mapping reference

[9] API Gateway mapping template and access logging variable reference

[10] Routing Lambda function errors to API Gateway HTTP responses

--

--

Daniel Ilie
The Startup

Cloud solution architect at Wood PLC. Provided clarity, employed creativity and managed complexity of systems.