본문 바로가기

Ops/AWS

AWS Lambda Code - 이미지 리사이징 (feat. cloudfront)

반응형

AWS Lambda Code - 이미지 리사이징 (feat. cloudfront)

 

 

 

 

 

const imagemin = require("imagemin");
const imageminGiflossy = require("imagemin-giflossy");
const fsPromise = require("fs").promises;
const querystring = require("querystring");
const Sharp = require("sharp");
const AWS = require("aws-sdk");
const S3 = new AWS.S3({ region: "us-east-2" });
const BUCKET = {
  test1: "s3-ue2-test1",
  test2: "s3-ue2-test2",
  test3: "s3-ue2-test3",
};

const LAMBDA_TIMEOUT_IN_MILLI_SECONDS = 25000;

const allowedTransformExtensions = {
  jpg: ["webp"],
  jpeg: ["webp"],
  png: ["webp"],
  gif: ["webp"],
};

function isNumeric(value) {
  return /^-?\d+$/.test(value);
}

exports.handler = async (event, context, callback) => {
  const request = event.Records[0].cf.request;
  let timeoutId;
  let targetBucketName;

  try {
    for (const bucketName in BUCKET) {
      if (request.uri.startsWith(`/${bucketName}/`)) {
        targetBucketName = BUCKET[bucketName];
        break;
      }
    }
    if (!targetBucketName) {
      callback(null, request);
      console.log(
        "Target bucket not exists, pass to original file : ",
        request.uri
      );
      return;
    }

    const [, originalFileName, originalExtension] = request.uri.match(
      /\/(.*)\.(.*)/
    );
    const params = querystring.parse(request.querystring);

    let targetWidth = params.w;
    let targetExtension = params.f;

    console.log("input target Width / Extension", targetWidth, targetExtension);

    if (!targetWidth && !targetExtension) {
      callback(null, request);
      console.log(
        "Target width & extension not exists, pass to original file : ",
        request.uri
      );
      return;
    }

    if (!allowedTransformExtensions[originalExtension.toLowerCase()]) {
      callback(null, request);
      console.log(
        "Not allowed extension for resizing, pass to original file : " +
          originalExtension.toLowerCase()
      );
      return;
    }

    if (targetWidth && !isNumeric(targetWidth)) {
      console.log("ERROR : Width should be number or not exists");
      return;
    }

    targetWidth = targetWidth ? parseInt(targetWidth) : undefined;
    console.log("Target width in number format : " + targetWidth);

    if (
      targetExtension &&
      !allowedTransformExtensions[originalExtension.toLowerCase()].includes(
        targetExtension
      )
    ) {
      console.log("ERROR : Not allowed target format");
      return;
    }
    /* Input Validation END */

    timeoutId = setTimeout(() => {
      console.log(`${LAMBDA_TIMEOUT_IN_MILLI_SECONDS} ms Timeout for resizing`);
      return callback(null, request);
    }, LAMBDA_TIMEOUT_IN_MILLI_SECONDS);

    targetExtension = targetExtension ?? originalExtension.toLowerCase();
    targetExtension = targetExtension === "jpg" ? "jpeg" : targetExtension;
    console.log("TargetExtension for work " + targetExtension);

    const resizedTargetFileName = `${originalFileName}__resizedByCFLE__${originalExtension}_${
      targetWidth ?? "originalWidth"
    }.${targetExtension}`;

    try {
      const s3HeadObject = await S3.headObject({
        Bucket: targetBucketName,
        Key: resizedTargetFileName,
      }).promise();

      console.log("Resized File Content-Length", s3HeadObject.ContentLength);
      if (s3HeadObject) {
        console.log("Resized file exists in bucket : ", resizedTargetFileName);
        request.uri = "/" + resizedTargetFileName;
        clearTimeout(timeoutId);
        callback(null, request);
        return;
      }
    } catch (error) {
      console.log(
        "Resized file not exists, process goes on : ",
        resizedTargetFileName
      );
    }

    const s3OriginalObject = await S3.getObject({
      Bucket: targetBucketName,
      Key: originalFileName + "." + originalExtension,
    }).promise();

    console.log("Original File", originalFileName + "." + originalExtension);
    console.log("Original File Content-Length", s3OriginalObject.ContentLength);

    let resizedImage = "";
    let resizedImageTmp = "";

    const originalImage = await Sharp(s3OriginalObject.Body);
    const originalFileMetadata = await originalImage.metadata();

    if (targetWidth >= originalFileMetadata.width) {
      console.log(
        "Target width is larger than original width, pass to original file"
      );
      clearTimeout(timeoutId);
      callback(null, request);
      return;
    }

    if (["jpeg", "webp", "png"].includes(targetExtension)) {
      console.log("Start resizing jpeg, webp, png");
      resizedImage = await originalImage
        .resize({
          width: targetWidth,
          withoutEnlargement: true,
        })
        .toFormat(targetExtension)
        .toBuffer();
      console.log("Resize complete");

    } else if (targetExtension === "gif") {
      console.log("Start resizing gif");
      const targetHeight = Number.parseInt(
        (targetWidth * originalFileMetadata.height) / originalFileMetadata.width
      );

      await fsPromise.writeFile("/tmp/resizedImg.gif", s3OriginalObject.Body);

      console.log(
        "TargetWidth / TargetHeight for gif : ",
        targetWidth,
        targetHeight
      );

      resizedImageTmp = await imagemin(["/tmp/resizedImg.gif"], {
        plugins: [
          imageminGiflossy({ resize: `${targetWidth}x${targetHeight}` }),
        ],
      });
      resizedImage = resizedImageTmp[0].data;
    }

    await S3.putObject({
      Body: resizedImage,
      Bucket: targetBucketName,
      ContentType: "image/" + targetExtension,
      Key: resizedTargetFileName,
      StorageClass: "STANDARD",
    }).promise();

    console.log("New file uploaded " + resizedTargetFileName);

    request.uri = "/" + resizedTargetFileName;
    clearTimeout(timeoutId);
    callback(null, request);
    return;

  } catch (error) {
    console.log("Error, pass with original request : " + request.uri, error);
    if (timeoutId) clearTimeout(timeoutId);
    callback(null, request);
    return;
  }
};

 

 

 


by mkdir-chandler


 

 

 

 

 

728x90
반응형