Monday 15 August 2016

AWS Lambda - Using Image function in a basic web-page

Following on from the previous posting, we're now going to try to use our basic Lambda function which provides a simple HTTP GET method with no parameters and returns JSON with an image in base64 encoding. We're going to make a simple web-page to call the function and display the image.

I'm going to use AngularJS as it just means we don't need to fight with basic HTML and scripting. If you're not familiar, then this or this should give a quick Hello World intro just for what is needed in this example.

First of all we need to think how to get a Base64 chunk of data to show as an image. Thankfully most browsers now support inline images with data URLs. More details can be found here. The format looks like this:

<img src="data:image/png;base64,iVBORw0KGgoAAAANS..." />


Which all seems quite easy. There are some discussions on stackoverflow of where to go from here to link up to JavaScript.

Taking our basic Angular app it's quite a simple matter to add in a button that pushes the base64 data we had previously into a text box and also into the image data:

<!DOCTYPE html>
<html ng-app="TestApp">
<head>
 <script type="text/javascript"
  src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

</head>
<body>
 <div ng-controller="TestController">
        
        <h1>base64: {{data}}</h1>
    
        <img height="100" width="100" data-ng-src="data:image/png;base64,{{data}}" data-err-src="empty.png"/>

        <button ng-click="click()">GetImage</button>
 </div>
    
 <script>
        angular.module('TestApp', [])
            .controller('TestController', function($scope) {
            $scope.data = "empty";
            
            $scope.click = function() {
                $scope.data = "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";
            };
        });
     </script>
</body>
</html>

Now to get it calling the Lambda service, the $scope.click function can simply be changed to make an http request and put the data into the image. Hey presto!


$scope.click = function() {
    
    var response = $http.get('https://xxxxxxx.execute-api.eu-west-1.amazonaws.com/prod/getImage2');

    response.success(function(data, status, headers, config) {

        $scope.data = data.img;
    });

    response.error(function(data, status, headers, config) {
        alert("Error.");
    });
};

Now, if this isn't working for you, and almost part of the point of this post in itself it means that you have some API Gateway config to get sorted. I must admit first time I went through this there was a bit of head bashing and using fiddler to check that everything was ok.

If everything worked ok up to the previous post and you're getting the base64 data coming back from a browser call then it's sure to be some fun with JavaScript. This is most likely that you have not got CORS setup for your API Gateway binding. Biggest clue here is that you only have a GET method defined in API Gateway and there is no OPTIONS method. AWS makes this pretty easy, so follow the online instructions to configure for CORS.

If you're still having problems and I was bashing my head at this for a while it seems (for me at least) that the default CORS settings from AWS were not working and I needed to manually add in X-Requested-With to the Access-Control-All-Headers  configuration. Remember you'll need to deploy the API again for the changes to take effect. Thanks to this stackoverflow discussion and this one as well for the help.

The other problem I bumped into on this first time round and you'll not have it on the example posted is that if you use the function context.fail(error); in your Lambda to return an error, this will give an http 403 response, not a 200 response, and this is not handled in the basic API Gateway bindings so looks like an error that the service is unavailable. Very confusing!

1 comment: