JavaScript/Node JS

node/express 서버에서 Naver cloud 오브젝트 스토리지에 이미지 저장하기

한땀코딩 2020. 11. 8. 10:52

오브젝트 스토리지와 이미지

예전에 이미지 데이터를 관리하는 법과 관련하여 오브젝트 스토리지에 대해 간략하게 알아본 적이 있습니다. 이번 글에서는 이 오브젝트 스토리지에 실제로 이미지 파일을 저장하는 방법에 대해 간단하게 적어보려고 합니다. 선택한 스토리지나 사용하고 있는 프레임워크, 언어에 따라 방법은 조금씩 차이가 있겠지만, 큰 흐름 자체는 동일할 거라고 예상합니다.

사용한 기술 및 환경

  • 서버: node JS / Express JS
  • 오브젝트 스토리지: ncloud 오브젝트 스토리지(S3와 연동)
  • 프론트: 리액트

위의 환경을 기반으로 진행되는 순서는 이렇습니다.

프론트(웹)에서 input을 통해 파일을 업로드
→ 해당 파일을 express 서버로 전송
→ 서버가 파일을 오브젝트 스토리지로 전송

오브젝트 스토리지 설정

설명서

 

설명서

네이버 클라우드 플랫폼의 상품 사용 방법을 보다 상세하게 제공하고, 다양한 API의 활용을 돕기 위해 [설명서]와 [API 참조서]를 구분하여 제공하고 있습니다. Object Storage API 참조서 바로가기 >> Ob

docs.ncloud.com

Naver cloud 문서를 보면 오브젝트 스토리지 생성에 대한 가이드라인이 있습니다. 가이드라인에 따라 API를 신청하고 오브젝트 스토리지를 생성하면 필요한 키를 발급받을 수 있습니다. 이런 계정과 관련된 키는 외부에 노출되는 것을 조심해야 하기 때문에 서버 측에서 .env 등을 통해 관리하게 되는데, 제 경우 아래 4가지를 .env에 넣어두었습니다. (혹 .env의 활용법에 대해 정보가 필요하시다면 이 글을 참고해주세요)

  • Access Key ID - ncloud 포털의 마이페이지→인증키 관리에서 발급 및 확인 가능
  • Secret Key - ncloud 포털의 마이페이지→인증키 관리에서 발급 및 확인 가능
  • endpoint - 모든 오브젝트 스토리지 동일 (cdn 사용 제외). 이미지 업로드 api 설명서에서 확인 가능
  • 버킷명 - 오브젝트 스토리지 생성 시 지정한 이름으로 콘솔에서 확인 가능

프론트

리액트를 활용하여 프론트를 생성했습니다만, 바닐라로 할 때와 크게 다른 점은 없습니다. 이미지가 업로드되었음을 감지하고 만약 업로드된 파일이 있다면 서버로 POST 요청을 보내시면 됩니다. input 태그가 이벤트가 발생한 DOM 객체라고 생각하면 대략적인 코드는 이렇습니다.

const onUploadImage = async (e) => { // e: input, type=file인 DOM 객체
    if (e.target.files && e.target.files[0]) { // 업로드된 파일이 존재한다면
      const image = e.target.files[0];
      const datas = new FormData();
      datas.append('image', image, image.name);
      try {
        const result = await axios({
          method: 'post',
          url: 'api', // post 통신을 위한 api 주소
          data: datas,
          headers: { 'Content-Type': 'multipart/form-data' },
        });
      } catch (err) {
        setImageError(true);
      }
    }
  };

여기서 신경 써주어야 하는 부분은 이미지 데이터기 때문에 FormData 객체에 넣어서 보내주는 부분입니다. 단순 텍스트 데이터나 JSON이 아니기 때문에, 서버에서 처리하기 위한 작업입니다. 이 FormData 객체 안에 image라는 키 값에 업로드한 이미지 파일을 value로 지정해 서버로 보내게 됩니다.

서버

설명서

 

설명서

Javascript SDK for S3 API AWS S3에서 제공하는 Javascript SDK를 이용하여 네이버 클라우드 플랫폼 Object Storage를 사용하는 방법을 설명합니다. 이 문서는 AWS Javascript SDK 2.348.0 버전을 기준으로 작성되었습니

docs.ncloud.com

node 기반의 서버이기 때문에 Javascript sdk 설명서를 참고하면서 작업을 진행하게 됩니다. 여기서 오브젝트 업로드 부분을 참고하시면 됩니다. ncloud 오브젝트 스토리지는 S3를 사용하고 있기 때문에 aws-sdk 모듈을 설치하는 걸 확인하실 수 있습니다. 제 경우, 이미지 파일로 특정하여 업로드하고 싶었기 때문에 이미지의 타입을 특정하는 부분이 추가되어 있습니다. 또, 이미지를 향후 사용하기 위해 넣어준 위치의 주소 값을 반환해야 해서 그 부분은 수동으로 문자열을 합쳐서 생성하게 됩니다. 대략적인 코드는 이렇습니다.

require('dotenv').config();
const router = require('express').Router();
const multer = require('multer');
const AWS = require('aws-sdk');
const { v4: uuidv4 } = require('uuid');

const upload = multer();

router.post('', upload.single('image'), async (req, res) => {
  const S3 = new AWS.S3({
    endpoint: new AWS.Endpoint(process.env.IMAGE_ENDPOINT),
    region: 'kr-standard',
    credentials: {
      accessKeyId: process.env.IMAGE_ACCESSKEY,
      secretAccessKey: process.env.IMAGE_SECRETACCESSKEY,
    },
  });

  const imageName = uuidv4(); // 랜덤 이미지 생성
  await S3.putObject({
    Bucket: process.env.IMAGE_BUCKET,
    Key: `${imageName}.PNG`,
    ACL: 'public-read',
    // ACL을 지우면 전체공개가 되지 않습니다.
    Body: req.file.buffer,
    ContentType: 'image/png', // 파일 타입을 png로 지정
  }).promise();

  res.json({
    imageLink: `${process.env.IMAGE_ENDPOINT}/${process.env.IMAGE_BUCKET}/${imageName}.PNG`,
  });
});

module.exports = router;

multer 모듈의 경우, 위에서 프론트가 전달한 FormData 같은 데이터를 서버가 처리하기 위해 필요합니다. upload.single('image') 부분을 통해 post 요청에서 'image'라는 key에 하나의 파일이 전달된다는 걸 명시하게 됩니다. 이렇게 하면 req.file에 이미지 파일의 정보, 데이터가 담겨있습니다. uuid 모듈의 경우 이미지를 저장할 때 이미지 파일명을 랜덤하게 지정하기 위함입니다. 만약 파일명이 그대로 유지되어야 한다면 받아온 파일의 이름에 접근하여 해당 부분을 넣어주면 됩니다.

주의할 점은, req.file을 바로 전달하는 것이 아니라, 실질적으로 이미지 자체에 대한 정보는 이미지 파일의 buffer에 저장되어 있다는 것입니다. 그래서 putObject의 Body에 req.file.buffer를 지정하고 밑에서 해당 데이터가 png 파일로 저장될 수 있도록 ContentType을 지정해주고 있습니다.

이후, 오브젝트 스토리지의 엔드포인트 + 버킷명 + 이미지명을 합쳐서 이를 반환해주어 프론트 쪽에서 재사용할 수 있도록 해주었습니다.

마치며

S3를 직접적으로 사용한 것은 아니어서 생략되거나 다른 부분도 많을 수는 있습니다만, ncloud 설명서로 해결되지 않는 문제는 s3를 키워드로 검색하면 해결법을 몇 가지 찾으실 수 있습니다. 이미지 파일을 전송할 때 어떤 데이터를 보내야 하는가에 대해서도 명확하게 딱 정리해둔 글이 없는 것 같아서 우선 제가 성공한 방법을 소개해보는 차원에서 작성하게 되었습니다. 프론트→서버→오브젝트 스토리지라는 흐름으로 이미지 데이터 전송이 필요하신 분께 많이 참고가 되면 좋겠습니다 :)