kingkool68
kingkool68

Reputation: 284

Returning Dynamic Content-Type via AWS API Gateway and Lambda Function

I want to use an AWS API Gateway as a proxy for fetching files from an S3 bucket and returning them to the client. I'm using a Lambda function to talk to S3 and send the file to the client via the AWS API Gateway. I've rad that the best way to do this is to use a "Lambda proxy integration" so the entire request gets piped to Lambda without any modification. But if I do that then I can't setup an Integration Response for the resulting response from my Lambda function. So all the client gets is JSON.

It seems there should be a way for the API Gateway to take the JSON and transform the request to the proper response for the client but I can't seem to figure out how to make that happen. There are lots of examples that point to setting a content-type on the response from the API Gateway manually but I need to set the content-type header to whatever the file type is.

Also for images and binary formats my Lambda function is returning a base64 encoded string and the property isBase64Encoded set to true. When I go to the "Binary Support" section and specify something like image/* as a content type that should be returned as binary, it doesn't work. I only have success by setting the Binary Support content type to */* (aka everything) which won't work for non-binary content types.

What am I missing and why does this seem so difficult?

Upvotes: 5

Views: 6156

Answers (1)

kingkool68
kingkool68

Reputation: 284

Turns out API Gateway isn't the problem. My Lambda function wasn't returning proper headers.

For handling binary responses I found you need to set Binary Support content type to */* (aka everything) and then have your Lambda function return the property isBase64Encoded set to true. Responses that are base64 encoded and indicated as such will be decoded and served as binary while other requests will be returned as is.

Here's a simple Gist for a Lambda function that takes a given path and reads the file from S3 and returns it via the API Gateway:

/**
 * This is a simple AWS Lambda function that will look for a given file on S3 and return it
 * passing along all of the headers of the S3 file. To make this available via a URL use
 * API Gateway with an AWS Lambda Proxy Integration.
 * 
 * Set the S3_REGION and S3_BUCKET global parameters in AWS Lambda
 * Make sure the Lambda function is passed an object with `{ pathParameters : { proxy: 'path/to/file.jpg' } }` set
 */

var AWS = require('aws-sdk');

exports.handler = function( event, context, callback ) {
    var region = process.env.S3_REGION;
    var bucket = process.env.S3_BUCKET;
    var key = decodeURI( event.pathParameters.proxy );

    // Basic server response
    /*
    var response = {
        statusCode: 200,
        headers: {
            'Content-Type': 'text/plain',
        },
        body: "Hello world!",
    };
    callback( null, response );
    */

    // Fetch from S3
    var s3 = new AWS.S3( Object.assign({ region: region }) );
    return s3.makeUnauthenticatedRequest(
        'getObject',
        { Bucket: bucket, Key: key },
        function(err, data) {
            if (err) {
                return err;
            }

            var isBase64Encoded = false;
            if ( data.ContentType.indexOf('image/') > -1 ) {
                isBase64Encoded = true;
            }

            var encoding = '';
            if ( isBase64Encoded ) {
                encoding = 'base64'
            }
            var resp = {
                statusCode: 200,
                headers: {
                    'Content-Type': data.ContentType,
                },
                body: new Buffer(data.Body).toString(encoding),
                isBase64Encoded: isBase64Encoded
            };

            callback(null, resp);
        }
    );
};

via https://gist.github.com/kingkool68/26aa7a3641a3851dc70ce7f44f589350

Upvotes: 7

Related Questions