[기술공유]Ncloud Object Stroage 학습기 - connect-foundation/2019-07 GitHub Wiki

Ncloud Object Stroage 학습기

NCLOUD 준비 과정

  1. 인증키 준비

    • 계정 관리 -> 인증키 관리 -> 신규 API 인증키 생성
  2. Object Storage 이용 신청

  3. 버킷 생성(버킷은 코딩으로 동적 생성 가능)

코드

환경 변수 설정

  • 필수(ncloud 인증키)

    • Access Key ID
    • Secret Key
  • 선택

    • Object Storage Bucket
  • 저는 Bucket을 동적으로 관리하지 않고 하나의 Bucket만 사용하기 때문에 환경변수 설정을 하였습니다.

    .env
    
      OS_ACCESS_KEY_ID=
      OS_SECRET_ACCESS_KEY=
      OS_BUCKET=
    

필수 패키지 설치

  • multer: 서버로 전송한 파일을 쉽게 처리하는 패키지

    Multer는 파일 업로드를 위해 사용되는 multipart/form-data 를 다루기 위한 node.js 의 미들웨어 입니다. 효율성을 최대화 하기 위해 busboy 를 기반으로 하고 있습니다.

  • aws-sdk: AWS S3 클라우드 서비스를 쉽게 사용할 수 있는 패키지

    SDK를 사용하면 Amazon S3, Amazon EC2, DynamoDB 및 Amazon SWF를 포함하는 AWS 서비스를 위한 JavaScript 객체가 제공되므로 복잡하게 코드를 작성하지 않아도 됩니다

오브젝트 스토리지 파일 기본 설정

  • ncloud 공식예시에서 accesskey와 bucket만 환경변수에서 불러온 정보로 변경

    objectStorage.js;
    
    const AWS = require("aws-sdk");
    require("dotenv").config();
    
    const endpoint = new AWS.Endpoint("https://kr.object.ncloudstorage.com");
    const region = "kr-standard";
    AWS.config.update({
      accessKeyId: process.env.OS_ACCESS_KEY_ID,
      secretAccessKey: process.env.OS_SECRET_ACCESS_KEY,
    });
    
    const S3 = new AWS.S3({
      endpoint,
      region,
    });
    
    const bucket = process.env.OS_BUCKET;
    

파일 업로드

폴더를 만들고 오브젝트를 업로드합니다.

폴더나 오브젝트는 캐싱으로 인하여 덮어쓰기가 되지 않습니다.

예를 들어 보겠습니다.

images/1/ 폴더에 test.jpg 오브젝트를 업로드합니다.

오브젝트 스토리지에 images/1/test.jpg에 이미지 파일이 업로드 됩니다.

images/1/test.jpg에 다른 사진 파일을 같은 key인 images/1/test.jpg에 업로드 한다면 처음에 업로드한 파일을 마주하게 됩니다.

images/1/test.jpg 파일을 삭제하고 다른 파일을 같은 key로 업로드하면 처음에 업로드한 파일을 마주하게 됩니다.

const rootFolder = 'images/';
async function uploadImage(roomId, quizId, filename, buffer) {
  // create folder
  const folder = `${rootFolder}${roomId}/${quizId}/`;
  await S3.putObject({
    Bucket: bucket,
    Key: folder,
  }).promise();

  // upload file
  await S3.putObject({
    Bucket: bucket,
    Key: `${folder}${filename}`,
    Body: buffer,
    ACL: 'public-read',
    // ACL을 지우면 전체공개가 되지 않습니다.
  }).promise();
}

폴더 삭제

폴더 삭제는 안의 오브젝트가 없는 상태, 즉 빈 폴더일때만 삭제가 가능합니다.

따라서, 폴더 안의 오브젝트를 다 지운 후 폴더 삭제를 진행합니다.

  • 폴더안의 오브젝트를 삭제하고 폴더를 삭제하는 method

    async function emptyS3Directory(dir) {
      const listParams = {
        Bucket: bucket,
        Prefix: dir,
      };
    
      const listedObjects = await S3.listObjectsV2(listParams).promise();
    
      if (listedObjects.Contents.length === 0) return;
    
      const deleteParams = {
        Bucket: bucket,
        Delete: {
          Objects: [],
        },
      };
    
      listedObjects.Contents.forEach(({ Key }) => {
        deleteParams.Delete.Objects.push({
          Key,
        });
      });
    
      await S3.deleteObjects(deleteParams).promise();
    
      if (listedObjects.IsTruncated) await emptyS3Directory(bucket, dir);
    }
    
  • 실제로 사용하는 폴더 삭제 method

    const rootFolder = 'images/';
    async function deleteQuizsetFolder(roomId) {
      const folder = `${rootFolder}${roomId}/`;
      emptyS3Directory(folder);
    }
    
    async function deleteQuizFolder(roomId, quizId) {
      const folder = `${rootFolder}${roomId}/${quizId}/`;
      emptyS3Directory(folder);
    }
    

exports

module.exports = {
  uploadImage,
  deleteQuizsetFolder,
  deleteQuizFolder,
};

프론트에서 파일 전송

파일을 전송하기 위해서는 FormData에 파일을 넣어서 보내야합니다.

const formData = new FormData();
formData.append('file', file);

파일뿐만 아니라 다른 정보도 FormData에 넣어서 보낼 수 있습니다.

Object.keys(refinedQuiz).forEach(key => {
  formData.append(key, refinedQuiz[key]);
});

fetch를 할 때 json 형태로 보내거나 header를 설정하면 안보내졌습니다.

async function fetchForm({ data, method }) {
  const url = "/edit/quiz";
  const response = await fetch(url, {
    method,
    body: data,
  });
  const responseJson = await response.json();
  return responseJson;
}

async function createQuiz(formData) {
  const response = await fetchForm({
    data: formData,
    method: "POST",
  });
  return response;
}

async function updateQuiz(formData) {
  const response = await fetchForm({
    data: formData,
    method: "PUT",
  });
  return response;
}

서버에서 req 정보 사용

파일은 req.file 또는 req.files에 저장됩니다.

나머지 정보는 req.body에 저장됩니다.

파일이 있을 때만 오브젝트 스토리지에 저장하였습니다.

const express = require("express");
const multer = require("multer");

const upload = multer();
const router = express.Router();

const dbManager = require("../../models/database/dbManager");
const objectStorage = require("../../objectStorage");

router.put("/quiz", upload.single("file"), async (req, res) => {
  const { roomId, id, title, quizOrder, score, timeLimit } = req.body;
  const { file } = req;
  const { isError } = await dbManager.quiz.updateQuiz({
    id,
    title,
    quizOrder,
    score,
    timeLimit,
  });
  const isSuccess = isError === undefined;
  if (file !== undefined) {
    const { buffer, originalname } = file;
    await objectStorage.uploadImage(roomId, id, originalname, buffer);
    const newImagePath = getImagePath(roomId, id, originalname);
    await dbManager.quiz.updateImagePath(id, newImagePath);
  }
  res.json({
    isSuccess,
  });
});

마치며

오브젝트 스토리지가 막연하게 어려울거 같다고 생각하였습니다. 하지만 막상 구글링을 하고 코딩을 해보니 문서화가 잘 되어있어서 하루만에 끝낼 수 있었습니다.

어려웠던 점은 파일을 전송하기 위해서 헤더의 콘텐트 타입을 multipart/form-data로 설정해줘야된다고 보았습니다. 하지만, 이유는 잘 모르겠지만 오히려 하지 않았을 때 잘 작동하였습니다.

또한, 파일이 아닌 정보를 오브젝트 형태로 보내고 싶었는데 되지 않아서 각 정보들을 key value 형태로 FormData에 append해서 보냈습니다.

좋은 점은 파일을 따로 어렵게 관리할 필요 없이 손쉽게 link 주소만 관리하면 되는 점이 좋았습니다.

이상입니다.