๋ชฉ์ฐจ
์ด ๊ฒ์๊ธ์ ์๋ฆฌ์ฆ๋ฌผ์ ๋๋ค!
์๋ ๋ชฉ์ฐจ๋ฅผ ๋จผ์ ํ์ธํด์ฃผ์ธ์
1. Lambda@Edge๋?
2. S3, CloudFront ์ ํ
3. CloudFront ์ฟผ๋ฆฌ์คํธ๋ง ์บ์ ์ ํ
4. IAM ์ญํ ์์ฑ
5. Lambda@Edge ๋ฐฐํฌ ์ ํ ๋ฐ ๋ก๊ทธ ํ์ธ
6. ๋ฆฌ์ฌ์ด์ง ๋ก์ง ์์ฑ ๋ฐ ํ ์คํธ
๋๋ค ๋ก์ปฌ์์ ์คํํ๊ธฐ
serverless framework๋ฅผ ์ด์ฉํ๋ฉด ๋๋ค๋ฅผ ๋ก์ปฌ์์ ์คํํ ์ ์๋๋ฐ,
ํจ์์ ์ ๋ฌ๋๋ event ๊ฐ์ฒด๊ฐ ๋น์ด์๋ ์ํ์์ ์คํ๋์ด ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค.
์๋ฒ๋ฆฌ์ค์ --path ์ต์ ์ ์ด์ฉํ์ฌ ๋ก์ปฌ์ jsonํ์ผ์ ์ฝ์ด event ๊ฐ์ฒด์ ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ด์ค์๋ค.
1. ์ด์ ๊ฒ์๊ธ์์ ํ์ธํ ๋ก๊ทธ๋ฐ์ดํฐ์์ event๊ฐ์ฒด๋ฅผ ๊ทธ๋๋ก ๋ณต์ฌํ์ฌ OriginResponseEvent.json ํ์ผ๋ก ๋ง๋ญ๋๋ค.
2. ์๋ ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํฉ๋๋ค
#serverless invoke local --function ํจ์๋ช
--path event.jsonํ์ผ๊ฒฝ๋ก
$ serverless invoke local --function imageResize --path OriginResponseEvent.json
์ด์ ๋ฒ๊ฑฐ๋กญ๊ฒ ๋๋ค๋ฅผ ๋ฐฐํฌํ์ง ์์๋ ๋ก์ปฌ์์ ๋๋ค๋ฅผ ์คํํ ์ ์๊ฒ ๋์์ต๋๋ค.
๋ฆฌ์ฌ์ด์ง ๋ก์ง ์์ฑ ์ ์...
Sharp ์ค์น
์ด๋ฏธ์ง ๋ฆฌ์ฌ์ด์ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค ๊ฐ์ฅ ์ ๋ช ํ Sharp ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด๋ด ์๋ค.
Sharp๋ ์คํํ๊ฒฝ์ ๋ฐ๋ผ ์ค์น ๋ช ๋ น์ด๊ฐ ๋ฌ๋ผ์ง๋๋ค.
#linux ํ๊ฒฝ
npm install sharp --platform=linux
#window ํ๊ฒฝ
npm install sharp --platform=win32
package.json script ์์
๋๋ค๊ฐ ์คํ๋ linuxํ๊ฒฝ๊ณผ ๋ก์ปฌ(window) ์คํํ๊ฒฝ์ด ์๋ก ๋ค๋ฅด๊ธฐ๋๋ฌธ์
๋ก์ปฌ์์๋ --platform=win32๋ก ์ค์นํด์ ํ ์คํธ๋ฅผ ์งํํ๋ค๊ฐ
๋ฐฐํฌํด์ผํ๋ฉด --platform=linux๋ก ์ค์นํด์ ๋ฐฐํฌํด์ผํ๋ ๋ฒ๊ฑฐ๋ก์์ด ๋ฐ์ํฉ๋๋ค.
package.json ํ์ผ์ script๋ฅผ ์์ ํ์ฌ ๋ก์ปฌ ์คํ ๋ช ๋ น, ๋ฐฐํฌ ์คํ ๋ช ๋ น์ ๊ฐํธํ๊ฒ ์์ ํด๋ด ์๋ค.
- prestart, predeploy : ์คํํ๊ฒฝ์ ๋ง๋ sharp ์ค์น ๋ช ๋ น์ด ์ ๋ ฅ
- start, deploy : ๋ก์ปฌ/๋ฐฐํฌ ์คํ ๋ช ๋ น์ด ์ ๋ ฅ
"scripts": {
"prestart": "npm uninstall sharp && npm install sharp --platform=win32",
"start": "serverless invoke local --function imageResize --path OriginResponseEvent.json",
"predeploy": "npm uninstall sharp && npm install sharp --platform=linux --arch=x64",
"deploy": "serverless deploy"
},
์ด์ ๋ก์ปฌ ์คํ์ npm run start, ์ด์ ๋ฐฐํฌ๋ npm run deploy๋ฅผ ์ ๋ ฅํ๋ฉด ๋ฉ๋๋ค.
์ด๋ฏธ์ง ํ์ผ ๋ฐ์ดํฐ S3์์ ๊ฐ์ ธ์ค๊ธฐ
์ค๋ฆฌ์ง์ผ๋ก๋ถํฐ ์๋ต์ ์์ ํ ์ํ์ด๊ธฐ ๋๋ฌธ์ ๋๋ค event.json์ S3 ์ ๋ณด๊ฐ ์กด์ฌํ์ง๋ง,
์ด๋ฏธ์ง ํ์ผ ๋ฐ์ดํฐ๋ ์๊ธฐ ๋๋ฌธ์ S3์ ๋ค์ ์ ๊ทผํด์ ์๋ณธ ์ด๋ฏธ์ง ํ์ผ์ ๊ฐ์ง๊ณ ์์ผํฉ๋๋ค.
AWS SDK for JavaScript v3 ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ฌ @aws-sdk/client-s3 ์ค์นํด์ค๋๋ค.
npm install @aws-sdk/client-s3
ResponseBody ํฌ๊ธฐ ํ ๋น๋
Lambda@Edge ํจ์์์ Response Body๋ฅผ ๋ง๋๋ ๊ฒฝ์ฐ, ํฌ๊ธฐ ํ ๋น๋์ด ์ ์ฉ๋ฉ๋๋ค. ์ฐธ๊ณ
์ฟผ๋ฆฌ์คํธ๋ง ๊ฐ ํ์ธ
์ฟผ๋ฆฌ์คํธ๋ง์ ์ถ๊ฐํ๋ฉด event๊ฐ์ฒด์ ์ด๋์ ์ฟผ๋ฆฌ์คํธ๋ง๊ฐ์ด ๋ค์ด์ค๋์ง ํ์ธํฉ๋๋ค.
querystring๊ฐ์ ?๋นผ๊ณ ์ ๋ ฅํ ๊ทธ๋๋ก ๋ค์ด์ค๋ค์
OriginResponseEvent.json ํ์ผ์ ์ ๋ ฅํด์ค์๋ค.
๋ฆฌ์ฌ์ด์ง ๋ก์ง ์์ฑ
์์์ ์ ์ฝ์ฌํญ ๋ฑ์ ํ์ธํ์ผ๋ ์ด์ ์ง์ง๋ก ๋ก์ง์ ์์ฑํด๋ด ์๋ค
//index.js
'use strict';
import Sharp from "sharp"
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"
const S3 = new S3Client({
region: 'ap-northeast-2'
});
const getQuerystring = (querystring, key) => {
return new URLSearchParams("?" + querystring).get(key);
}
export const imageResize = async (event, context) => {
console.log("imageResize ์คํ!!");
//===================================================================
//event์์ ์์ฒญ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
const { request, response } = event.Records[0].cf;
//์ฟผ๋ฆฌ์คํธ๋ง ๊ฐ์ ธ์ค๊ธฐ
//์ฟผ๋ฆฌ์คํธ๋ง์ด ์กด์ฌํ์ง ์๋ค๋ฉด ๋ฆฌ์ฌ์ด์ฆ ์ฒ๋ฆฌํ ํ์ ์์ผ๋ ์๋ณธ ๋ฐํ
const querystring = request.querystring;
if(!querystring){
console.log("querystring is empty!! return origin");
return response;
}
//uri ๊ฐ์ ธ์ค๊ธฐ
const uri = decodeURIComponent(request.uri);
//ํ์ผ๋ช
์ด ์์ด์ธ ๊ฒฝ์ฐ : "/samples/chun.jpeg"
//ํ์ผ๋ช
์ด ํ๊ธ์ธ ๊ฒฝ์ฐ : "/samples/%E1%84%86%E1%85%A9%E1%84%85%E1%85%A1%E1%86%AB.png"
//ํ์ผ ํ์ฅ์ ๊ฐ์ ธ์ค๊ธฐ
const extension = uri.match(/(.*)\.(.*)/)[2].toLowerCase();
console.log("extension", extension);
//gif๋ sharp๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ฒด ์ด์ ์กด์ฌ. ๋ฆฌ์ฌ์ด์งํ์ง ์๊ณ ๋ฐ๋ก ์๋ณธ ๋ฐํ
if(extension === 'gif'){
console.log("extension is gif!! return origin");
return response;
}
//์ฟผ๋ฆฌ์คํธ๋ง ํ์ฑ
const width = Number(getQuerystring(querystring, "w")) || null;
const height = Number(getQuerystring(querystring, "h")) || null;
const fit = getQuerystring(querystring, "f");
const quality = Number(getQuerystring(querystring, "q")) || null;
console.log({
width,
height,
fit,
quality
});
//s3 ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
const s3BucketDomainName = request.origin.s3.domainName;
let s3BucketName = s3BucketDomainName.replace(".s3.ap-northeast-2.amazonaws.com", "");
s3BucketName = s3BucketName.replace(".s3.amazonaws.com", "");
console.log("s3BucketName", s3BucketName);
const s3Path = uri.substring(1);
//===================================================================
//S3์์ ์ด๋ฏธ์ง ๊ฐ์ ธ์ค๊ธฐ
let s3Object = null;
try{
s3Object = await S3.send(new GetObjectCommand({
Bucket: s3BucketName,
Key: s3Path
}));
console.log("S3 GetObject Success");
}catch (err){
console.log("S3 GetObject Fail!! \n" +
"Bucket: " + s3BucketName + ", Path: " + s3Path + "\n" +
"err: " + err);
return err;
}
//์ด๋ฏธ์ง ๋ฆฌ์ฌ์ด์ฆ ์ํ
const s3Uint8ArrayData = await s3Object.Body.transformToByteArray();
let resizedImage = null;
try{
resizedImage = await Sharp(s3Uint8ArrayData)
.resize({
width: width,
height: height,
fit: fit
})
.toFormat(extension, {
quality: quality
})
.toBuffer();
console.log("Sharp Resize Success");
}catch (err) {
console.log("Sharp Resize Fail!! \n" +
"Bucket: " + s3BucketName + ", Path: " + s3Path + "\n" +
"err: " + err);
return err;
}
//๋๋ค์ฃ์ง์์ ์๋ต์ ๋ง๋๋ ๊ฒฝ์ฐ, ์๋ตํ ์ ์๋ body์ ํฌ๊ธฐ์ ํ์ด ์๋ค.
//base64 ์ธ์ฝ๋ฉ ํ
์คํธ๋ก ๋ฐํํ๋ ๊ฒฝ์ฐ 1.3MB(1,048,576 byte)๊น์ง ๊ฐ๋ฅ.
const resizedImageByteLength = Buffer.byteLength(resizedImage, 'base64');
console.log('resizedImageByteLength:', resizedImageByteLength);
//๋ฆฌ์ฌ์ด์งํ ์ด๋ฏธ์ง ํฌ๊ธฐ๊ฐ 1MB ์ด์์ธ ๊ฒฝ์ฐ ์๋ณธ ๋ฐํ
if (resizedImageByteLength >= 1048576) {
console.log("resizedImageByteLength >= 1048576!! return origin");
return response;
}
//๋ฆฌ์ฌ์ด์งํ ์ด๋ฏธ์ง ์๋ตํ ์ ์๋๋ก response ์์
response.status = 200;
response.body = resizedImage.toString('base64');
response.bodyEncoding = 'base64';
console.log("imageResize ์ข
๋ฃ!!");
return response;
};
๋ก์ปฌ ํ ์คํธ
npm run start
์ํ์ ์ฑ๊ณตํ๋๋ฐ ์ค์ ๋ก ๋ฆฌ์ฌ์ด์ง ๋ ์ด๋ฏธ์ง ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ ค๋ฉด
body์ ๋ด๊ธด base64์ฝ๋๋ฅผ ๋ค์ ๋ณํํ๊ฑฐ๋, ๋๋ค๋ฅผ ๋ฐฐํฌํด์ ์ํ๋๋์ง๋ฅผ ํ์ธํด์ผํฉ๋๋ค...
๋ก์ปฌ ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ํ์ผ๋ก ๋ณํํ๋ index-local.js ์ถ๊ฐ
index.js์ imageResize ํจ์๋ฅผ importํ์ฌ ์คํํ๊ณ ,
response๋ก ๋ฐ๋ body๋ฐ์ดํฐ๋ฅผ ํ์ผ๋ก ์ ์ฅํ์ฌ ๋ก์ปฌ์์ ๋ฆฌ์ฌ์ด์ง ๊ฒฐ๊ณผ๋ฅผ ํ์ธํด๋ด ์๋ค.
//index-local.js
'use strict';
import { imageResize } from "./index.js";
import eventJson from "./OriginResponseEvent.json" assert{ type: "json"}; //json์ ๊ฐ์ ธ์ค๋ ค๋ฉด assert๋ฅผ ๋ถ์ฌ์ฃผ์ด์ผํ๋ค.
import fs from "fs";
export const imageResizeToFile = async (event, context) => {
console.log("imageResizeToFile start!!");
event = eventJson;
console.log("===============================");
const response = await imageResize(event, context);
console.log("===============================");
const buf = new Buffer(response.body, 'base64');
fs.writeFile('output.jpg', buf ,function(err, result) {
if(err){console.log('error', err);}
});
console.log("imageResizeToFile end!!");
};
run-func ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
index-local.js ํ์ผ์ imageResizeToFile ํจ์๋ฅผ ๋ก์ปฌ์์ ์คํ๊ฐ๋ฅํ๋๋ก run-func ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ค์นํด์ค๋๋ค.
npm install run-func
package.json script ์์
package.json์ start ๋ช ๋ น์ด๋ฅผ ์์ ํด์ค๋๋ค.
"scripts": {
"prestart": "npm uninstall sharp && npm install sharp --platform=win32",
"start": "run-func index-local.js imageResizeToFile",
"predeploy": "npm uninstall sharp && npm install sharp --platform=linux --arch=x64",
"deploy": "serverless deploy"
},
๋ค์ npm run startํ์ฌ ์คํ์ํค๋ฉด output.jpg๋ก ๋ฆฌ์ฌ์ด์ง๋ ์ด๋ฏธ์ง๊ฐ ์ถ๋ ฅ๋ฉ๋๋ค.
์ด์ ๋ฐฐํฌ ๋ฐ ํ ์คํธ
npm run deploy
์๋ณธ (1058 x 1411)
ํฌ๊ธฐ ์ค์ด๊ธฐ (200 x 250)
ํฌ๊ธฐ ๋๋ฆฌ๊ธฐ (2000 x 2500)
์์ฃผ ํฌ๊ฒ ๋๋ฆฌ๊ธฐ (8000 x 8000) → ์ฉ๋์ด ์ปค์ ธ์ ์๋ณธ๋ฐํ!! (1058 x 1411)