by

How to send response headers for AWS Lambda function exceptions in API Gateway

In a previous post on error handling in API Gateway I discussed various ways to map errors from your Lambda function to appropriate API status codes and how to build the response body appropriately for different types of errors.

One common pattern we’ve seen come up is the requirement to return a specific response header value depending on the type of error from the Lambda function. While this is most easily achieved with proxy integrations, some prefer to use the explicitly-mapped “AWS” integration type. This allows their Lambda function implementation to use native error types and decouples it from their API Gateway configuration.

In this example I will show how to manipulate the HTTP status code, the response body, as well as a response header value based on the Lambda error outcome.

Note, this example is specific to the NodeJS runtime, and will only allow to set a single header – please post in the comments if you have similar solutions for other runtimes.

This technique makes use of the fact that Lambda serializes the exception type in the errorType field, which can be mapped to a header value in API Gateway. This is a workaround solution until API Gateway supports JSON-parsing in parameter mapping expressions.

Lambda function

exports.handler = (event, context, callback) => {
   function MyError(message) {
      Error.captureStackTrace(this, this.constructor); // mapped to $.stackTrace
      this.customMessage = message; // custom field
      this.name = 'test header value' // mapped to $.errorType
      this.message = JSON.stringify(this); // mapped to $.errorMessage
    }
    require('util').inherits(MyError, Error);
    callback(new MyError("BadRequest - my error message"));
};

Observe that this Lambda function sets the error.name field to the value desired in the response header. When the error is serialized by Lambda, this becomes the “errorType” field in the Lambda response. You can also set custom properties in the Lambda error which can be used when rendering the API response body.

This Lambda function outputs the following response:

{
"errorMessage": "{\"customMessage\":\"BadRequest - my error message\",\"name\":\"test header value\"}",
"errorType": "test header value",
"stackTrace": [
"exports.handler.message (/var/task/index.js:12:14)"]
}

API definition

---
swagger: "2.0"
info:
  version: "2017-01-25T19:26:48Z"
  title: "API Gateway Test API"
host: "79rptjwqbk.execute-api.us-east-1.amazonaws.com"
basePath: "/test"
schemes:
- "https"
paths:
  /{proxy+}:
    x-amazon-apigateway-any-method:
      produces:
      - "application/json"
      parameters:
      - name: "proxy"
        in: "path"
        required: true
        type: "string"
      responses:
        200:
          description: "200 response"
        400:
          description: "400 response"
          headers:
            test-header:
              type: "string"
        500:
          description: "500 response"
      x-amazon-apigateway-integration:
        responses:
          default:
            statusCode: "200"
          .*BadRequest.*:
            statusCode: "400"
            responseParameters:
              method.response.header.test-header: "integration.response.body.errorType"
            responseTemplates:
              application/json: "#set ($errorMessageObj = $util.parseJson($input.path('$.errorMessage')))\n\
                \n{\n\"my-message\" : \"$errorMessageObj.customMessage\"\n}"
        uri: "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:XXXXXXXX:function:errors/invocations"
        passthroughBehavior: "when_no_match"
        httpMethod: "POST"
        contentHandling: "CONVERT_TO_TEXT"
        type: "aws"

Note that the method response header is set to the value of the “errorType” field in the Lambda error response for 400 responses.

Zooming in on the mapping template for the 400 response:

#set ($errorMessageObj = $util.parseJson($input.path('$.errorMessage')))
{
   "my-message" : "$errorMessageObj.customMessage"
}

The “stringified” errorMessage is parsed by the mapping template so that all properties (including custom properties) of the error object can be accessed in the mapping template to build the response body.

Invoking this method produces the following results, appropriately setting the trifecta of status code, response body, and response header.

Request: GET /test
Status: 400
Response Body
{
  "my-message": "BadRequest - my error message"
}
Response Headers
{"test-header":"test header value","X-Amzn-Trace-Id":"Root=1-5888fee9-79b51571774c74ebeeb3eb62","Content-Type":"application/json"}

Comments, questions, and improvements are welcome!

-Ryan

Write a Comment

Comment