Skip to content

Instantly share code, notes, and snippets.

@zzerjae
Last active March 9, 2021 11:51
Show Gist options
  • Save zzerjae/11c1b684eb671e0d2ca439070a5de472 to your computer and use it in GitHub Desktop.
Save zzerjae/11c1b684eb671e0d2ca439070a5de472 to your computer and use it in GitHub Desktop.
sample code - Resizing Images with Amazon CloudFront & Lambda@Edge
'use strict';
const querystring = require('querystring');
const aws = require('aws-sdk');
const s3 = new aws.S3({
region: 'ap-northeast-2',
signatureVersion: 'v4'
});
const sharp = require('sharp');
// Image types that can be handled by Sharp
const supportImageTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'tiff'];
exports.handler = async (event, context, callback) => {
const { request, response } = event.Records[0].cf;
const bucket = 'YOUR_BUCKET';
// check if image is present and not cached.
if (response.status == 200) {
const params = querystring.parse(request.querystring);
// If none of the s, t, or q variables is present, just pass the request
if (!params.s || !params.t || !params.q) {
callback(null, response);
return;
}
// read the S3 key from the path variable.
// assets/images/sample.jpeg
let key = decodeURIComponent(request.uri).substring(1);
let width, height, type, quality, requiredFormat;
// s=100x100&t=crop&q=100(&f=webp)
const sizeMatch = params.s.split('x');
const typeMatch = params.t;
const qualityMatch = params.q;
const formatMatch = params.f;
let originalFormat = key.match(/(.*)\.(.*)/)[2].toLowerCase();
if (!supportImageTypes.some((type) => { return type == originalFormat })) {
responseUpdate(
403,
'Forbidden',
'Unsupported image type',
[{ key: 'Content-Type', value: 'text/plain' }],
);
callback(null, response);
}
width = parseInt(sizeMatch[0], 10);
height = parseInt(sizeMatch[1], 10);
type = typeMatch == 'crop' ? 'cover' : typeMatch;
quality = parseInt(qualityMatch, 10)
// correction for jpg required for 'Sharp'
originalFormat = originalFormat == 'jpg' ? 'jpeg' : originalFormat;
requiredFormat = formatMatch == 'webp' ? 'webp' : originalFormat;
try {
const s3Object = await s3.getObject({
Bucket: bucket,
Key: key
}).promise();
if (s3Object.ContentLength == 0) {
responseUpdate(
404,
'Not Found',
'The image does not exist.',
[{ key: 'Content-Type', value: 'text/plain' }],
);
callback(null, response);
}
let metaData, resizedImage, byteLength = 0;
if (requiredFormat != 'jpeg' && requiredFormat != 'webp' && requiredFormat != 'png' && requiredFormat != 'tiff') {
requiredFormat = 'jpeg';
}
while (1) {
resizedImage = await sharp(s3Object.Body).rotate();
metaData = await resizedImage.metadata();
if (metaData.width > width || metaData.height > height) {
resizedImage
.resize(width, height, { fit: type });
}
if (byteLength >= 1046528 || originalFormat != requiredFormat) {
resizedImage
.toFormat(requiredFormat, { quality: quality });
}
resizedImage = await resizedImage.toBuffer();
byteLength = Buffer.byteLength(resizedImage, 'base64');
if (byteLength >= 1046528) {
quality -= 10;
}
else {
break;
}
}
responseUpdate(
200,
'OK',
resizedImage.toString('base64'),
[{ key: 'Content-Type', value: 'image/' + requiredFormat }],
'base64'
);
response.headers['cache-control'] = [{ key: 'cache-control', value: 'max-age=31536000' }];
return callback(null, response);
}
catch (err) {
console.error(err);
return callback(err);
}
}
else {
// allow the response to pass through
callback(null, response);
}
function responseUpdate(status, statusDescription, body, contentHeader, bodyEncoding = undefined) {
response.status = status;
response.statusDescription = statusDescription;
response.body = body;
response.headers['content-type'] = contentHeader;
if (bodyEncoding) {
response.bodyEncoding = bodyEncoding;
}
}
};
@jybaek
Copy link

jybaek commented Jan 24, 2019

블로그 잘 보고 왔습니다. 모바일이라 힘들지만 몇 가지 제안 드리자면..

  • callback 후 return 처리 안되어 있는 부분.
  • 버킷이나 상수는 람다 환경변수로 빼면 어떨까요.
  • while(1) 이 보다는.. quality 사이즈 비교로 하는게 그나마 덜 무섭지 않을는지,
  • 혹여라도 다른 로깅 없이 CW에 남는 console.log 에 의존적이라면 로그가 조금 더 추가되고 다듬어져야 할 듯,

다시한번 좋은 글 너무 잘 봤습니다. 감사합니다. :)

@zzerjae
Copy link
Author

zzerjae commented Feb 8, 2019

댓글 감사합니다 :)

환경 변수 같은 부분은 실제로는 저희 서비스에 맞게 따로 가져오도록 되어 있습니다. 예제로 코드를 올리다 보니, 불가피하게 위와 같이 작성하였습니다.

그 외 지적해주신 부분은 저희 서비스에도 도움이 될 것 같습니다. 감사합니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment