이번에는 NHN Cloud의 Object Storage를 사용하는 법을 배워보자.
아직 NHN Cloud를 사용하는 일반 유저들이 많지 않아서인지 검색을 해도 예제가 나오지 않아서 조금 애를 먹었지만 우리의 친절한 NHN Cloud 사이트에서는 친절하게 가이드를 제공해준다.
Object Storage는 컨테이너를 생성해서 권한을 설정 할 수 있고 관리 할 수 있다.
생성을 하면 빈 깡통 컨테이너가 만들어졌을 것이다.
오브젝트 스토리지를 콘솔이 아닌 곳에서 사용하려면 인증 토큰을 발급받아야한다.
그런 인증 토큰은 Tenant ID
, API Endpoint
, ID
, API Password
의 4가지가 있어야 발급 가능하다.
이 4가지 중 3개는 NHN Cloud 콘솔창의 Object Storage탭에서 API 엔드포인트 설정
버튼을 눌러서 모두 확인 가능하다.
ID는 로그인 할때 썼던 자신의 아이디이니 알아둘 것! 그리고 스토리지 계정은 내 오브젝트 스토리지를 가리키는 것이다.
Object Storage를 처음 사용하는 유저는 API 비밀번호가 설정이 안되어있을 것이다. 이 비밀번호는 로그인할때 쓰는 비밀번호랑은 다르다. 같은 오브젝트라도 사용자마다 다른 비밀번호를 입력할 수 있으며 API를 호출할때만 사용되는 비밀번호이다.
POST https://api-identity.infrastructure.cloud.toast.com/v2.0/tokens
Content-Type: application/json
요청을 보낼때에는 다음과 같은 정보를 json형식으로 body에 넣어서 post로 보내야한다.
{
"auth": {
"tenantId": "{Tenant ID}",
"passwordCredentials": {
"username": "{NHN Cloud ID}",
"password": "{API Password}"
}
}
}
제대로 된 tenantID와 ID, Password를 입력했다면 아래와 같은 형식의 응답이 올 것이다.
{
"access": {
"token": {
"expires": "{Expires Time}",
"id": "{token-id}",
"tenant": {
"description": "",
"enabled": true,
"id": "{Tenant ID}",
"name": "{NHN Cloud ID}",
"groupId": "{NHN Cloud Project ID}",
"project_domain": "NORMAL",
"swift": true
},
"issued_at": "{Token Issued Time}"
},
"serviceCatalog": [],
"user": {
"id": "{User UUID}",
"name": "{User Name}"
}
}
}
여기서 중요한 값은 token의 id 값과 expires 값이다.
토큰을 사용할 때 token-id값을 Header에 넣어주면 사용 가능하고 Expires Time이 지나면 토큰이 만료되므로 다시 발급을 받아야한다.
multer
는 expressjs
에서 만든 파일 업로드를 위한 nodejs
미들웨어이다.
한글화된 multer
의 소개 페이지에서는 이렇게 설명하고 있다.
Multer
는 파일 업로드를 위해 사용되는multipart/form-data
를 다루기 위한node.js
의 미들웨어 입니다. 효율성을 최대화 하기 위해busboy
를 기반으로 하고 있습니다.
Multer
는multipart (multipart/form-data)
가 아닌 폼에서는 동작하지 않습니다.
업로드를 위해서는 form-data로 파일을 보내야하며 보낸 키에 맞는 데이터을 받아 저장한다.
여기서는 img라는 키로 img를 전송하는 테스트를 하겠다.
공식문서에 나와 있는 Custom Storage Engine을 참고하여 만들었다.
https://github.com/expressjs/multer/blob/master/StorageEngine.md
우선 전체 소스코드를 한번 보자.
// ObjectStorage.js
const axios = require("axios");
const OS_ENDPOINT = process.env.OS_ENDPOINT;
const OS_TENANTID = process.env.OS_TENANTID;
const OS_USERNAME = process.env.OS_USERNAME;
const OS_PASSWORD = process.env.OS_PASSWORD;
const containerName = "/tutor";
var token;
var tokenExpiresTime;
const getAcessToken = async () => {
// 만료되지않았다면 기존에 있던 토큰 사용
if (tokenExpiresTime > new Date().toISOString()) {
return token, tokenExpiresTime;
}
// 발급되지않았거나 만료되면 토큰 발급
let tokenURL =
"https://api-identity.infrastructure.cloud.toast.com/v2.0/tokens";
let tokenHeader = {
headers: {
"Content-Type": "application/json",
},
};
let body = {
auth: {
tenantId: OS_TENANTID,
passwordCredentials: {
username: OS_USERNAME,
password: OS_PASSWORD,
},
},
};
let result = await axios.post(tokenURL, body, tokenHeader);
token = result.data.access.token.id;
tokenExpiresTime = result.data.access.token.expires;
logger.info("Get Token Success", token, tokenExpiresTime)
return token, tokenExpiresTime;
};
const putHeader = (token, file) => {
return {
headers: {
"X-Auth-Token": `${token}`,
"Content-type": `${file.mimetype}`,
},
};
};
function ObjectStorage(opts) {
this.getDestination = opts.destination;
}
ObjectStorage.prototype._handleFile = function _handleFile(req, file, cb) {
this.getDestination(req, file, async function (err, container) {
if (err) {
return cb(err);
}
// 토큰 발급
await getAcessToken();
let filename = encodeURI(Date.now() + "_" + file.originalname);
let url = OS_ENDPOINT + containerName + container + filename;
// 파일 전송
axios
.put(url, file.stream, putHeader(token, file))
.then((response) => {
logger.info("Put File Successed");
logger.info(filename);
// req.file에서 쓸 인자 추가
cb(null, {
uri: url,
filename: filename,
});
})
.catch((err) => {
cb(err);
});
});
};
ObjectStorage.prototype._removeFile = function _removeFile(req, file, cb) {};
module.exports = function (opts) {
return new ObjectStorage(opts);
};
입력해야하는 연결 정보들은 모두 env
파일로 뺐다.
그리고 변수로 token
및 expiresTime
을 관리하였다.
발급된 적이 없었다면 token
및 TokenExpiresTime
이 없을 것이고 다시 발급 받게 된다.
발급된 적이 있다면 둘다 존재할 것이고 TokenExpiresTime
이 지금보다 크다면 (시간이 남았다면) 다시 발급받지 않는다.
지금보니 토큰이 invaild
할때는 expire
되기전까지 계속 오류가 날 것이라 다시 발급 받을 수 있게 해야하는 코드를 추가해야될것같다.
handleFile
의 getDestination
안의 axios
안의 then
의 cb(콜백)함수
에서 multer를 쓰면 쓸 수 있는 req.file
에서 쓸 인자들을 추가 할 수 있다.
// multerOS.js
const multer = require("multer");
const ObjectStorage = require("./ObjectStorage");
const multerOS = multer({
storage: ObjectStorage({
destination(req, file, cb) {
cb(null, `/files/`);
},
}),
});
module.exports = multerOS;
사용을 위해서는 아래와 같이 만든 Custom Storage Engine을 적용시켜주면 된다.
const multerOS = require("../module/multerOS");
// ...
router.all("/api/uploadImage", multerOS.single("img") ,candyCtrl.process.setCandyImage);
위와 같이 미들웨어로 적용시키면 사용가능하다. OS로 직접 업로드가 되고 받은 정보들은 req.file에 있으니 요리조리 잘 사용하길 바란다.