Infra

S3, CloudFront, Lambda@Edge를 이용한 이미지 리사이즈(6) - 리사이징 로직 작성 및 테스트

연_우리 2023. 5. 29. 11:25
반응형

목차

     

     

     

    이 게시글은 시리즈물입니다!
    아래 목차를 먼저 확인해주세요

    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)

     

     

     

     

     

     

    반응형
    • 네이버 블러그 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 구글 플러스 공유하기
    • 카카오톡 공유하기