Akca Blog

Home

Blog

akcatech
Performance

Dynamic Image Resizing using Lambda@Edge and CloudFront

A
AkcaTech Team
24/10/2024
Dynamic Image Resizing using Lambda@Edge and CloudFront
Let's say you have an S3 bucket filled with images and serve your images with CloudFront. Some day you decided to use your distribution for your mobile application. Learn how to automatically resize images on-demand using Lambda@Edge functions.

Intro

Let's say you have an S3 bucket filled with images and serve your images with CloudFront. Some day you decided to use your distribution for your mobile application. Congratulation you have a small problem. You need to resize your images for your mobile applications. In this article, we'll solve your problem together.

Requirements

  • aws cli

S3 & CloudFront Setup

In order to test our scenario, we'll set up a new bucket and distribution.

Let's create a sample s3 bucket.

aws s3api create-bucket \ --bucket my-bucket \ --region eu-central-1

Upload a sample image into it

aws s3 cp test.jpeg s3://my-bucket/images/test.jpeg

Now create CloudFront distribution

aws cloudfront create-distribution \ --origin-domain-name my-bucket.s3.amazonaws.com

After the distribution creation operation, let's continue with the AWS Console.

In order to configure our distribution correctly, we need to make some changes.

First of all, choose your distribution and check the following items

  • Correct origin domain.
  • Origin access control settings (recommended)
  • Origin access control (If you previously created one, you may use it)
  • Click Copy policy button and paste it to "S3 Bucket Policy".

Let's jump to behaviour settings and make sure following settings below

  • Viewer Protocol policy HTTP to HTTPS
  • Allowed HTTP methods GET, HEAD, OPTIONS
  • Restrict viewer access No.

Function Deployment

We'll be going to use a simple lambda function to resize our images. This function is using sharp npm package. Create a new directory and add the following code:

"use strict"; const AWS = require("aws-sdk"); const S3 = new AWS.S3({ signatureVersion: "v4", }); const Sharp = require("sharp"); // set your S3 bucket name here const BUCKET = "my-bucket"; const FIT_TYPE = "contain"; exports.handler = (event, context, callback) => { let response = event.Records[0].cf.response; console.log("Response status code: " + response.status); if (response.status == 404) { let request = event.Records[0].cf.request; let path = request.uri; let key = path.substring(1); let prefix, originalKey, match, width, height, imageFormat, imageName; let startIndex; match = key.match(/(.*)\/(\d+)x(\d+)\/(.*)/); prefix = match[1]; width = parseInt(match[2], 10); height = parseInt(match[3], 10); imageName = match[4]; originalKey = prefix + "/" + imageName; imageFormat = imageName.split(".")[1]; if (imageFormat == "jpg") { imageFormat = "jpeg"; } S3.getObject({ Bucket: BUCKET, Key: originalKey, }) .promise() .then((data) => Sharp(data.Body) .resize(width, height, { fit: FIT_TYPE, }) .toBuffer() ) .then((buffer) => { // save the resized object to S3 bucket S3.putObject({ Body: buffer, Bucket: BUCKET, ContentType: "image/" + imageFormat, CacheControl: "max-age=31536000", Key: key, StorageClass: "STANDARD", }) .promise() // even if failing to save the object it will send the generated // image back to viewer below .catch(() => { console.log("Exception while writing resized image to bucket"); }); response.status = 200; response.body = buffer.toString("base64"); response.bodyEncoding = "base64"; response.headers["content-type"] = [ { key: "Content-Type", value: "image/" + imageFormat, }, ]; callback(null, response); }); } else { // bypass when the status code is not 404 callback(null, response); } };

Make sure to update the BUCKET variable with your actual S3 bucket name.

Okay, let's build the project and zip it.

💡 The following build procedure is required by MacOS. You may need to change --platform for your operating system.

npm i rm -rf node_modules/sharp SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux --libc=glibc sharp zip -r function.zip .

Create a role with the following properties

  • Trust relationship must include "lambda.amazonaws.com" and "edgelambda.amazonaws.com" services.
  • Permissions must include s3 bucket permission. (If you want to use Cloud Watch, you should also include it)
aws lambda create-function \ --region us-east-1\ --function-name my-function \ --runtime nodejs14.x \ --zip-file fileb://function.zip \ --handler index.handler \ --role arn:aws:iam::123456789012:role/service-role/previously-created-role

💡 @Edge functions can be deployed only us-east-1 region at the time I wrote this article.

Lambda@Edge Deployment

Moreover, we need to deploy our function to the edge. Open your function page on AWS Console. Click Action -> click Deploy to Lambda@Edge.

  • Choose your distribution
  • Select cache behaviour.
  • Select CloudFront event (Viewer Response)
  • Confirm deployment to Lambda@Edge.

And deploy your function.

What we have done?

If CloudFront cannot find the requested image in S3 (404 response), our function will run and resize the requested image and return the resized image.

Test

Let's call our image with a custom dimension

https://abcdefgh.cloudfront.net/images/500x500/test.jpeg

This request will create a new folder 500x500 in our S3 bucket and return the resized image.

Result

In this article, we have done automatic image resizing using many AWS services. Thanks to the function we used, we automatically resized the pictures and got rid of the manual operation burden.

Need Expert Support?

Need professional support on this topic and more? Don't hesitate to reach out to us. Our expert team is happy to help you achieve your goals.

Back to Blog
Published on 24/10/2024