How To: HTTP redirects with API Gateway and Lambda

Update (2017-08-15): Recent service updates have removed the need for the workarounds described below, though they may still be useful in some cases, or for historical context.

Achieving HTTP redirects with API Gateway and Lambda is now trivial with the addition of Proxy integrations.

Simply define an API with an ‘aws_proxy’ integration type, and implement your Lambda function to explicitly return the redirect status code and Location header.

Lambda Function

i.e.

'use strict';
 
exports.handler = function(event, context, callback) {
    var response = {
        statusCode: 301,
        headers: {
            "Location" : "http://ryangreen.ca"
        },
        body: null
    };
    callback(null, response);
};

Swagger

---
swagger: "2.0"
info:
  version: "2017-08-15T19:29:52Z"
  title: "redirect test"
basePath: "/prod"
schemes:
- "https"
paths:
  /redirect2:
    x-amazon-apigateway-any-method:
      x-amazon-apigateway-integration:
        uri: "arn:aws:apigateway:[REGION]:lambda:path/2015-03-31/functions/arn:aws:lambda:[REGION]:[ACCOUNT_ID]:function:[FUNCTION_NAME]/invocations"
        passthroughBehavior: "when_no_match"
        httpMethod: "POST"
        type: "aws_proxy"

——

API Gateway recently released support for mapping response bodies to response headers. One application of this feature is to enable conditional HTTP redirects in your API Gateway/Lambda API.

There are a couple of ways to achieve 30x redirects in your API Gateway/Lambda API.

Option 1: 30X as “default” response

This option is preferred if your API method always redirects (i.e. never returns a normal 2XX response).

1) Define a method response with status 302, and a “Location” header defined
2) Define a “default” integration response mapping with blank regex, mapping to 302.
3) For this response, define a “Location” header mapping from the redirect URL returned in your Lambda function. i.e. “integration.response.body.location”
3) Configure your lambda function to return the redirect location in the body, i.e.

context.succeed({location : "http://example.com"})

Swagger example

/lambdaredirect-default:
    get:
      produces:
      - "application/json"
      parameters: []
      responses:
        200:
          description: "200 response"
          schema:
            $ref: "#/definitions/Empty"
          headers: {}
        302:
          description: "302 response"
          headers:
            Location:
              type: "string"
      x-amazon-apigateway-integration:
        responses:
          default:
            statusCode: "302"
            responseParameters:
              method.response.header.Location: "integration.response.body.location"
        uri: "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:[ACCOUNT_ID]:function:redirect-default/invocations"
        httpMethod: "POST"
        type: "aws"

Lambda function

exports.handler = function(event, context) {
    context.succeed({
        location : "https://example.com"
    });
};

Option 2: 30X as an error response

This option allows your method to return both “successful” (2XX) and redirect outcomes, but requires you to model redirects in your lambda function as errors.

1) Define a method response with status 302, and a “Location” header defined. Leave the “default” integration 2XX response with blank regex, mapped to your 2XX method response.
2) Define the redirect integration response mapping with regex “http.*”, mapped to your 30X response.
3) For this response, map the redirect URL returned in the error message of your lambda function to your “Location” header: “integration.response.body.errorMessage”
3) Configure your lambda function to return the redirect location as the error message, i.e.

context.fail("http://example.com")

or

throw new RuntimeException("http://example.com")

4) Optional: If you don’t want to expose the lambda error response body to the client, define a mapping template on the redirect response to nullify the response body. You can use a template with a comment to render an empty response.

Swagger example

 /lambdaredirect-error:
    get:
      produces:
      - "application/json"
      parameters: []
      responses:
        200:
          description: "200 response"
          schema:
            $ref: "#/definitions/Empty"
          headers: {}
        302:
          description: "302 response"
          headers:
            Location:
              type: "string"
      x-amazon-apigateway-integration:
        responses:
          default:
            statusCode: "200"
          https://.*:
            statusCode: "302"
            responseParameters:
              method.response.header.Location: "integration.response.body.errorMessage"
            responseTemplates:
              application/json: "## intentionally blank"
        uri: "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:[ACCOUNT_ID]]:function:redirect-error/invocations"
        httpMethod: "POST"
        type: "aws"

Lambda function

exports.handler = function(event, context) {
    context.fail("https://example.com");
};

Here’s a full Gist with the example.

Cheers,
Ryan