Monday, 15 August 2016

AWS Lambda - Image Generator via API Gateway

Following on from the AWS Lambda - Getting Started post I wanted to continue exploring some of the starting blocks. In this article I'm going to look at continuing using AWS Lambda for working with images but instead stepping away from the well-trodden step of initiating the Lambda by dropping a file into an S3 bucket.

What I'd like to look at is calling a Lambda service via REST API and getting an image back. Now, you can think of any number of applications here, we're just going to look at some of the technology bits.

First step in moving away from initiating the Lambda from S3 is that we need some way to trigger the Lambda from a web-service call. AWS provides this via their API Gateway product and there is an accompanying tutorial in the reference Lambda documentation. API Gateway is pretty neat and relatively easy to configure if you want to send some JSON and return the result also in JSON. We're not going to explore that any further here. Now, say we want to create a service that just calls a Lambda and returns an image. Should be just the kind of thing for Lambda, right?

Now, this is where things get interesting. There are a number of blogs and articles talking about this with varying approaches trying to get API Gateway to somehow return an image/xxx MIME type. None of them seemed to work for me at the current time. This one is instructive on some failed approaches and a stackoverflow discussion worth reading as well.

So, it depends how you might want to use this. In my simple example case, which we'll step onto next I wanted to call from a basic web-page and get an image back from a Lambda rather than an S3 hosted file. It's probably fighting with all AWS is designed to do, but let's go with it for now.

For this example, the alternative seems to be to return the image (binary data) as Base64 encoded data in the JSON response. While a little more inefficient as we need to encode, decode and transport using more bytes it should be friendly with API Gateway and as we'll see in the next step also works pretty easily with simple HTML as well.

Getting started, this is how things turn out. I'll explain what's going on below:


var im = require("imagemagick");
var fs = require("fs");
var os = require('os');

var AWS = require('aws-sdk');
AWS.config.region = 'eu-west-1';

exports.handler = function(event, context) {
    
        var params = {Bucket: 'mybucket', Key: 'myimage.png'};
        var s3 = new AWS.S3();
        
        console.log("bucket", params);

        s3.getObject(params, function(err, data) {
            if (err) 
                console.log("ERROR", err, err.stack); // an error occurred
            else     
                console.log("SUCCESS", data);         // successful response
            
                var resizedFile = os.tmpdir() + '/' + Math.round(Date.now() * Math.random() * 10000);
            
                im.resize({
                    srcData: data.Body,
                    dstPath: resizedFile,
                    width: 100,
                    height: 100,
                    progressive: false
                }, function(err, stdout, stderr){
                    
                    console.log("resised", resizedFile); 
                    
                    if (err) {
                        context.fail(err);
                    }
                    else {
                        var resultImgBase64 = new Buffer(fs.readFileSync(resizedFile)).toString('base64');
                        context.succeed({ "img" : resultImgBase64 });
                    }
                });
                
        });

        // allow callback to complete 
};

This simple Lambda is using the AWS API in node and setting up the parameters to be able to read a file in a bucket. First thing to remember is that this file needs to be visible to Lambda for it to work, so either make this a publically available file (you need to set this for each file on S3) or set the roles and permissions via IAM for S3 and your Lambda appropriately.

The function then uses ImageMagick, which is kindly provided by AWS as an available package to do the resize. We're using the 'os' to enable the creation of a local file within the Lambda context. This file is then encoded in a buffer to Base64 and returned. None of this is hugely efficient, but it does the job.

This can all be dropped in to AWS Lambda as inline code and simply tested by calling the resulting URL given in the API Gateway setup via a browser something like this:


https://xxxxxxx.execute-api.eu-west-1.amazonaws.com/prod/getImage

Which if everything is working ok, should give a response something like this:


{"img":"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"}

There's a useful github repo for someone trying the same idea with more involved code.

Next step we'll get this called from a basic web page.

2 comments:

  1. IT's very informative blog and useful article thank you for sharing with us , keep posting learn more about aws
    AWS Online Training

    ReplyDelete
  2. Nice Blog very use full inormation.AWS-certified experts help you develop a cloud strategy which aligns with your business transformation road map and implement cloud management solutions to harness the AWS Cloud Managed Services

    ReplyDelete