본문으로 바로가기
 

sharp - High performance Node.js image processing

 

sharp.pixelplumbing.com

 

 

 

이번에 ZzalZzalee를 개발하면서 이미지 리사이징 문제를 피할 수 없게 되었다.

 

프론트 단에서 이미지 리사이징을 처리할 수도 있고, 백단에처 처리할 수도 있다. 각자 장단점이 있다.

 

이미지를 서버단에 전송한 이후에 리사이징하는 것은 서버 비용을 초래하는 일이기 때문에 프론트 단에서도 해결할 수 있긴 하다. (https://feel5ny.github.io/2018/05/27/JS_12/)

 

그러나 프론트단에서 리사이징을 하는 것은 데이터 전송량이 많아지면서 프로토콜 응답이 느려질 수도 있다고 한다. 여기저기 구글링을 한 결과 리사이즈는 백단에서 처리하는 경우가 많은 것 같다. 당근 마켓도 그렇게 하고 있다고 한다.

 

이미지 리사이징을 할 때 두 가지 방법을 고려할 수 있다. 1) 브라우져에서 크롭하여 다양한 이미지 크기를 생성한 뒤 서버에 보내는 방법. 2) 원본 이미지만 서버로 보내고 서버에서 리사이징을 처리하는 방법이 있다. 전자의 경우 후자에 비에 데이터 전송량이 많아지면서 프로토콜 응답이 느려질 수 있는 성능 상의 이슈가 있다. 
 - http://jeonghwan-kim.github.io/%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%97%85%EB%A1%9C%EB%93%9C-1-multer-%EB%AA%A8%EB%93%88%EB%A1%9C-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C/

 

 

여기서는 sharp 패키지를 통해서 앞단에서 이미지 리사이징을 해보도록 하겠다. 이미지 리사이징과 관련된 패키지는 많지만 sharp가 가장 널리 쓰인다고 한다.

sharp npm : (https://www.npmjs.com/package/sharp)

shap 공식 문서 : (https://sharp.pixelplumbing.com/)

사용법에 대한 정리 : (https://sub0709.tistory.com/1)

튜토리얼 : (https://bezkoder.com/node-js-upload-resize-multiple-images/)

 

 

또, s3에 업로드하기 전에 이미지 리사이징을 먼저 한 후 s3에다 올려보기도 해보자.

 

🚀 작업 플로우

1️⃣ 유저가 이미지를 올린다 -> multer-s3로 이미지를 저장한다 -> multer-s3가 반환한 이미지 주소를 이용하여 해당 이미지를 buffer로 변환 한 후 sharp를 이용해 resize한다 -> 다시 s3에 저장한다. (리사이징한 이미지를 다른 버켓에 저장할 건지 말건지는 선택)

 

2️⃣ 유저가 이미지를 올린다 -> multer-s3-transform와 multer-s3를 이용하여 s3에 저장하기 전 리사이즈 한다.

 

 

 

 

1️⃣ sharp를 이용한 이미지 리사이징

 

🚨 주의 사항

 

여기서 중요한 건 multer-s3로 업로드한 후 받는 req.file에서 s3 주소를 통해 얻게된 url을 통해 받은 이미지를 buffer로 만들어줘야 한다는 것입니다. 공식 문서를 보면 sharp constructor는 buffer나 string을 받는데 url 주소는 sharp가 인식하지 못합니다. (https://sharp.pixelplumbing.com/api-constructor)

 

 

* buffer?

 

Pure JavaScript는 이진 데이터(binary data)를 다룰 수 없다. 그런데 서버단에서 활용하는 node에서는 TCP streams 이나 파일을 읽고 쓸 수 있어야 한다. 그래서 등장한 것이 buffer이다. 쉽게 말해 파일을 읽고 쓰는데 전송되는 이진 데이터를 buffer로 변환해서 활용하는 것이다.

 

아래 코드는 node 내장 모듈인 fs의 readFile을 통해 파일을 읽었고 그 결과를 콜백으로 반환하는데 readFile가 자동으로 buffer로 변환한 것이다. 해당 buffer를 사용하기 위해서는 buffer의 여러 메서드를 사용하면 되는데 여기서는 toString을 썼다.

 

import fs from "fs";

// 파일 읽기
fs.readFile("./memo.txt", (err, data) => {
  if (err) {
    console.warn(err);
  }
  // data는 buffer 입니다. <Buffer 72 65 61 64 20 6d 65 20 62 72 6f 21>
  // 해당 buffer를 사용하기 위해 toString 메서드를 사용했습니다.
  console.log(data.toString());
});

 

buffer 관련 글

 

https://darrengwon.tistory.com/126

https://nodejs.org/en/knowledge/advanced/buffers/how-to-use-buffers/

https://nodejs.org/api/buffer.html

 

 

multer-s3를 통해 받은 url은 'xxx.s3.ap-northeast-x.amazonaws.com/image/xxx.jpg' 이런 형태이므로 axios를 통해서 정보를 받아온 후 해당 정보를 buffer로 변환해보겠습니다.

 

// req.file.location은 xxx.s3.ap-northeast-x.amazonaws.com/image/xxx.jpg 이런 꼴입니다
const s3Image = await axios.get(req.file.location);

// node의 Buffer 클래스를 이용합니다.
const buf = await Buffer.from(s3Image.data, "utf8");

// 출력해보면 <Buffer 47 49 46... 1766947 more bytes> 버퍼가 되었습니다.
console.log(buf);

 

이제 해당 버퍼를 이용해서 sharp를 사용할 수 있게 되었습니다.

 

트러블슈팅 1)

 

axios가 보내준 결과물 중 header 부분을 살펴보면 'content-type': 'application/octet-stream'임을 확인할 수 있습니다. 이는 multer-s3의 구성에서 ContentType을 주지 않아서 발생한 문제입니다. contentType: multerS3.AUTO_CONTENT_TYPE을 줍시다.

 

 

2️⃣ multer-s3-transform을 이용한 이미지 리사이징

 

(https://www.npmjs.com/package/multer-s3-transform)

 

multer-s3-transform+multer-s3를 이용하려면 multer-s3-transform을 따로 설치해야 한다.

또, transform 과정에서 resizing 작업을 위해 sharp도 설치해야 한다.

multer-s3 문서에는 없는 내용이 있는데, 다음과 같다.

 

The optional shouldTransform option tells multer whether it should transform the file before it is uploaded.

By default, it is set to false. If set to true, transforms option must be added, which tells how to transform the file. transforms option should be an Array, containing objects with can have properties id, key and transform.

 

관련 예시는 공식 문서에서 참고하고 곧 바로 활용해보자. 보면 알겠지만 결국 리사이징에 sharp를 이용하고 있다. 이 말인 즉슨 해당 기능을 이용하려면 sharp에 대해서 알고 있어야 한다는 것입니다.

 

multer-s3를 require하면 안된다. multer-s3-trasform을 require해야 한다.

const multer = require("multer");
const multerS3 = require("multer-s3-transform");
const sharp = require("sharp");


const ImageUpload = multer({
  storage: multerS3({
    s3,
    bucket: "zzalzzalimage/image",
    contentType: multerS3.AUTO_CONTENT_TYPE,
    shouldTransform: true,
    transforms: [
      {
        id: "resized",
        key: function (req, file, cb) {
          let extension = path.extname(file.originalname);
          cb(null, Date.now().toString() + extension);
        },
        transform: function (req, file, cb) {
          cb(null, sharp().resize(100, 100));
        },
      },
    ],
    acl: "public-read-write",
  }),
});

const uploadImageMulterMiddleware = ImageUpload.single("file");

 

반환하는 내용도 일반 multer-s3와 조금 다르다.

만약 이런 형태가 아니면 일반 multer-s3가 작동되고 있는 것일 가능성이 높으므로 확인하자

 

{
[1]   fieldname: 'file',
[1]   originalname: 'c9ae3c6a-ca90-5e46-8664-0dc3b9813725.gif',
[1]   encoding: '7bit',
[1]   mimetype: 'image/gif',
[1]   transforms: [
[1]     {
[1]       id: 'resized',
[1]       size: 11834,
[1]       bucket: 'zzalzzalimage/image',
[1]       key: '1593480856167.gif',
[1]       acl: 'public-read-write',
[1]       contentType: 'image/gif',
[1]       metadata: null,
[1]       location: '이건 비밀이죵.gif',
[1]       etag: '"etag어쩌구저쩌구"'
[1]     }
[1]   ]
[1] }

 

이제 남은건 sharp를 이용해서 이미지 리사이징이나 확장 등등을 처리하는 것이다. 이 문제는 각자 목표하는 바에 따라 작성하면 되겠다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 


여담)

 

여기서 인프라에 대해서 조금 더 이야기하자면 현재 이용 중인 AWS에서 S3에 저장하면서 발생하는 이벤트를 트리거로 삼아 serverless 함수를 돌릴 수도 있다.

 

과거 당근 마켓에서는 이런 방식으로 이미지 리사이즈 처리를 했다고 한다. 일단 S3에 저장하고, 람다를 돌려서 리사이징을 거친 후 재저장 하는 방식. (현재는 다른 방식을 사용중 이라고.) 

 

출처 : https://medium.com/daangn/lambda-edge%EB%A1%9C-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-on-the-fly-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A6%AC%EC%82%AC%EC%9D%B4%EC%A7%95-f4e5052d49f3

 

 

이 방식에 대한 글은 아래에서 볼 수 있다.

 

 

https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/with-s3-example-deployment-pkg.html

 

https://medium.com/daangn/aws-lambda%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%8D%B8%EB%84%A4%EC%9D%BC-%EC%83%9D%EC%84%B1-%EA%B0%9C%EB%B0%9C-%ED%9B%84%EA%B8%B0-acc278d49980

 

https://roka88.dev/102

 

 

 

 

 


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체