[기술공유]Ncloud Object Stroage 학습기 - connect-foundation/2019-07 GitHub Wiki
Ncloud Object Stroage 학습기
NCLOUD 준비 과정
-
인증키 준비
- 계정 관리 -> 인증키 관리 -> 신규 API 인증키 생성
-
Object Storage 이용 신청
-
버킷 생성(버킷은 코딩으로 동적 생성 가능)
코드
환경 변수 설정
-
필수(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 주소만 관리하면 되는 점이 좋았습니다.
이상입니다.