I have set up my architecture as such:
GET request for /products/:id/100x100/image.jpeg is being sent to CloudFront.
CloudFront will send the 100x100 dimension image.jpeg back to the client if it is cached, else it will call the Origin, which will then call the Lambda function to resize the image. The resized image from the Lambda function will then be put in the S3 Bucket and sent back as the response.
I have removed the ViewerRequest Lambda function.
This is my NodeJS code:
const AWS = require('aws-sdk');
const S3 = new AWS.S3({
signatureVersion: 'v4',
});
const Sharp = require('sharp');
const BUCKET = 'xBucket';
exports.handler = (event, context, callback) => {
let response = event.Records[0].cf.response;
//check if image is not present
if (response.status == 404) {
let request = event.Records[0].cf.request;
let path = request.uri;
let key = path.substring(1);
console.log('key path:', key);
// parse the prefix, width, height and image name
// Ex: key=images/200x200/webp/image.jpg
let prefix, originalKey, match, width, height, requiredFormat, imageName, productId;
// key=products/12/100x100/image.jpg
try {
match = key.match(/(.*)\/(\d+)\/(\d+)x(\d+)\/(.*)/);
prefix = match[1]; //products
productId = match[2];
width = parseInt(match[3], 10); //100
height = parseInt(match[4], 10);//100
console.log(`match: ${match} | prefix: ${prefix} | productId: ${productId} | width: ${width} | height: ${height}`);
// correction for jpg required for 'Sharp'
requiredFormat = match[5].split('.')[1] === "jpg" ? "jpeg" : match[4].split('.')[1];
console.log('requiredFormat: ', requiredFormat);
imageName = match[5];
originalKey = `${prefix}/${productId}/${imageName}`; // products/12/fjords.jpg
console.log('Original Key:', originalKey);
// get the source image file
S3.getObject({ Bucket: BUCKET, Key: originalKey }).promise()
.then(data => {
console.log('data:', data);
return Sharp(data.Body)
.resize(width, height)
.toFormat(requiredFormat)
.toBuffer()
})
.then(buffer => {
console.log('image resized');
// save the resized object to S3 bucket with appropriate object key.
S3.putObject({
Body: buffer,
Bucket: BUCKET,
ContentType: 'image/' + requiredFormat,
CacheControl: 'max-age=31536000',
Key: key,
StorageClass: 'STANDARD'
}).promise()
// even if there is exception in saving the object we send back the generated
// image back to viewer below
.catch((err) => { console.log(`Exception while writing resized image to bucket: ${err}`)});
console.log('resized imaged updated');
// generate a binary response with resized image
response.status = 200;
response.body = buffer.toString('base64');
response.bodyEncoding = 'base64';
response.headers['content-type'] = [{ key: 'Content-Type', value: 'image/' + requiredFormat }];
console.log('response event:', event);
console.log('FINAL RESPONSE: ', response);
callback(null, response);
})
.catch(err => { console.log("Exception while reading source image :%j",err) })
} catch (e) {
console.log(e);
}
} // end of if block checking response statusCode
else {
// allow the response to pass through
callback(null, response);
}
};
Based on AWS docs, the 502 Error could be due to Lambda Validation Error where the response returned from OriginResponseLamda does not conform to the structure of Lambda@Edge event structure. https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/http-502-lambda-validation-error.html
The error is: 502 ERROR The request could not be satisfied. The Lambda function returned invalid json: The json output must be an object type.
Not sure why the Lambda function logs is not found in CloudWatch also..