by

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

Write a Comment

Comment

  1. Thanks for writing this up! I’m afraid I spent the last 2+ hours reading the conflicting and not very helpful AWS Lambda/APIGateway docs, trying to do just this. It’s amazing how many steps and models they require to just do a redirect, which is one of the most common and most trivial of HTTP tasks.

  2. Hi,

    The post describes how to do it with node js lambda. Do you know whether it’s possible to do something similar with a Python lambda? It looks like the context object has a different purpose there, and the only possible response is json.

    Thanks,
    Benny

  3. Here’s option 3 – which I think is the best one

    The API Gateway status codes are triggered by ErrorMessage. So I’d use it for the error code. Then I’d pass the body or in the case of 302, the location in the errorType. It’s a little odd to read. E.g. { errorType: “Sorry can’t find the resource”, errorMessage: “404” }. But this way I can then map the response.header.location to errorType and still have full control for the other status codes.

    See it working here: https://github.com/ronaldwidha/index.lambda (click on the demo/test-harness/302 redirect)