Skip to content
This repository has been archived by the owner on Jun 16, 2022. It is now read-only.

upgrade sharp and deploy with serverless #2

Merged
merged 1 commit into from Jul 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
@@ -1,3 +1,4 @@
lambda/node_modules

dist/function.zip
.serverless
5 changes: 4 additions & 1 deletion Makefile
Expand Up @@ -5,9 +5,12 @@ all: package
image:
docker build --tag amazonlinux:nodejs .

package: image
package_old: image
docker run --rm --volume ${PWD}/lambda:/build amazonlinux:nodejs npm install --production

package:
docker run --rm --volume ${PWD}/lambda:/var/task lambci/lambda:build-nodejs12.x npm install --production

dist: package
cd lambda && zip -FS -q -r ../dist/function.zip *

Expand Down
60 changes: 35 additions & 25 deletions README.md
Expand Up @@ -6,6 +6,18 @@ Resizes images on the fly using Amazon S3, AWS Lambda, and Amazon API Gateway. U

## Usage

```
# build sharp package with nodejs lambda docker image
make package

# deploy
sls deploy # for staging
sls deploy --stage prod # for prod

```

## Usage(deprecated)

1. Build the Lambda function

The Lambda function uses [sharp][sharp] for image resizing which requires
Expand All @@ -22,40 +34,38 @@ Resizes images on the fly using Amazon S3, AWS Lambda, and Amazon API Gateway. U

2. Deploy the CloudFormation stack

Run `bin/deploy [prod|sandbox]` to deploy the CloudFormation stack. It will create a temporary Amazon S3 bucket, package and upload the function, and create the Lambda function, Amazon API Gateway RestApi, and an S3 bucket for images via CloudFormation.
Run `bin/deploy [prod|sandbox]` to deploy the CloudFormation stack. It will create a temporary Amazon S3 bucket, package and upload the function, and create the Lambda function, Amazon API Gateway RestApi, and an S3 bucket for images via CloudFormation.

The deployment script requires the [AWS CLI][cli] version 1.11.19 or newer to be installed.
The deployment script requires the [AWS CLI][cli] version 1.11.19 or newer to be installed.

3. Test the function

Upload an image to the S3 bucket and try to resize it via your web browser to different sizes, e.g. with an image uploaded in the bucket called image.png:
Upload an image to the S3 bucket and try to resize it via your web browser to different sizes, e.g. with an image uploaded in the bucket called image.png:

- http://[BucketWebsiteHost]/300x300/image.png
- http://[BucketWebsiteHost]/90x90/image.png
- http://[BucketWebsiteHost]/40x40/image.png
- http://[BucketWebsiteHost]/300x300/image.png
- http://[BucketWebsiteHost]/90x90/image.png
- http://[BucketWebsiteHost]/40x40/image.png

You can find the BucketWebsiteUrl in the table of outputs displayed on a successful invocation of the deploy script.
You can find the BucketWebsiteUrl in the table of outputs displayed on a successful invocation of the deploy script.

Using CloudFront to serve files in SSL

Step 1. Create CloudFront Distribution
Go to CloudFront Distributions and Create Distribution
Select Web
For Original Domain Name, do not use S3 autocomplete, use the S3 static site URL
Click "Create Distribution"

Step 2. Create Behaviors
In Behaviors tab, click "Create Behavior"
Enter "*/*.jpg" for the Path Pattern
Set Default TTL to 0
Click "Create"
Repeat for "*/*.png", "*/*.jpeg", "*/*.PNG", "*/*.JPG", "*/*.JPEG"

Step 3. Update Lambda Environment
Copy the Domain Name in CloudFront
Go to Lambda/Functions, select the lambda function, in the Environment variables section, set URL to the CLoudFront domain


Step 1. Create CloudFront Distribution
Go to CloudFront Distributions and Create Distribution
Select Web
For Original Domain Name, do not use S3 autocomplete, use the S3 static site URL
Click "Create Distribution"

Step 2. Create Behaviors
In Behaviors tab, click "Create Behavior"
Enter "_/_.jpg" for the Path Pattern
Set Default TTL to 0
Click "Create"
Repeat for "_/_.png", "_/_.jpeg", "_/_.PNG", "_/_.JPG", "_/_.JPEG"

Step 3. Update Lambda Environment
Copy the Domain Name in CloudFront
Go to Lambda/Functions, select the lambda function, in the Environment variables section, set URL to the CLoudFront domain

**Note:** If you create the Lambda function yourself, make sure to select Node.js version 8.10.

Expand Down
2 changes: 2 additions & 0 deletions lambda/config/prod.yml
@@ -0,0 +1,2 @@
BUCKET_NAME: attachments-prod-filebucket-abhk7syvoz1k
CDN_URL: https://cdn1.fireworktv.com
2 changes: 2 additions & 0 deletions lambda/config/staging.yml
@@ -0,0 +1,2 @@
BUCKET_NAME: attachments-staging-filebucket-1k8ixw9ll2qt
CDN_URL: https://cdn1-staging.fireworktv.com
88 changes: 45 additions & 43 deletions lambda/index.js
@@ -1,34 +1,32 @@
'use strict';
"use strict";

const AWS = require('aws-sdk');
const AWS = require("aws-sdk");
const S3 = new AWS.S3({
signatureVersion: 'v4',
signatureVersion: "v4",
});
const Sharp = require('sharp');
const Sharp = require("sharp");

const BUCKET = process.env.BUCKET;
const URL = process.env.URL;

function resizeImage(data, width, height, format) {
return Sharp(data.Body)
.rotate()
.resize(width, height)
.max()
.toFormat(format)
.toBuffer()
.rotate()
.resize(width, height, { fit: "inside" })
.toFormat(format)
.toBuffer();
}

function resizeThumbnail(data, width, height, format) {
return Sharp(data.Body)
.rotate()
.resize(width, height)
.crop()
.toFormat(format)
.toBuffer()
.rotate()
.resize(width, height, { fit: "cover", position: "attention" })
.toFormat(format)
.toBuffer();
}

function format(extension) {
switch(extension.toLowerCase()) {
switch (extension.toLowerCase()) {
case "png":
return "png";
case "jpeg":
Expand All @@ -46,43 +44,47 @@ function parseQuery(key) {
width: parseInt(match[2], 10),
height: parseInt(match[4], 10),
format: format(match[6]),
crop: match[3] === '_',
originalKey: `${match[1]}/original/${match[5]}.${match[6]}`
crop: match[3] === "_",
originalKey: `${match[1]}/original/${match[5]}.${match[6]}`,
};
} else {
return null;
}
}

exports.handler = function(event, context, callback) {
exports.handler = function (event, context, callback) {
const q = parseQuery(event.queryStringParameters.key);

if (q) {
S3.getObject({Bucket: BUCKET, Key: q.originalKey}).promise()
.then(data => q.crop ?
resizeThumbnail(data, q.width, q.height, q.format) :
resizeImage(data, q.width, q.height, q.format)
)
.then(buffer => S3.putObject({
Body: buffer,
Bucket: BUCKET,
ContentType: `image/${q.format}`,
CacheControl: 'max-age=12312312',
Key: q.key,
}).promise()
)
.then(() => callback(null, {
statusCode: '301',
headers: {'location': `${URL}/${q.key}`},
body: '',
})
)
.catch(err => callback(err))
console.log(`processing ${q.originalKey}`);
S3.getObject({ Bucket: BUCKET, Key: q.originalKey })
.promise()
.then((data) =>
q.crop
? resizeThumbnail(data, q.width, q.height, q.format)
: resizeImage(data, q.width, q.height, q.format)
)
.then((buffer) =>
S3.putObject({
Body: buffer,
Bucket: BUCKET,
ContentType: `image/${q.format}`,
CacheControl: "max-age=12312312",
Key: q.key,
}).promise()
)
.then(() =>
callback(null, {
statusCode: "301",
headers: { location: `${URL}/${q.key}` },
body: "",
})
)
.catch((err) => callback(err));
} else {
callback(null, {
statusCode: '404',
body: ''
})
statusCode: "404",
body: "",
});
}

}
};