์๋ฒ๋ฆฌ์ค, ์์ด๋ก๋ Server(์๋ฒ) + less(์๋)์ด๋ค. ์๋ฒ๋ฅผ ํด๋ผ์ฐ๋๊ฐ ๋์ ๊ด๋ฆฌํด ์ฃผ๋ ๊ฒ.
์๋ฒ๋ฅผ ํด๋ผ์ฐ๋ ์๋น์ค๊ฐ ๋์ ๊ด๋ฆฌํด ์ฃผ๊ธฐ ๋๋ฌธ์, ๊ฐ๋ฐ์ ๋๋ ์ด์์๊ฐ ์๋ฒ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐ์ ๋ํ ๋ถ๋ด์ด ์ค๋ค.
๋ํ์ ์ผ๋ก๋ AWS์ GCP์์ ๋ค์ํ ์๋ฒ๋ฆฌ์ค ์๋น์ค๋ฅผ ์ ๊ณตํ๋ค.
AWS์ ๋๋ค(Lambda), API ๊ฒ์ดํธ์จ์ด (API Gateway), S3์ด ์๋ค.
Google Cloud์ ์ฑ ์์ง (App Engine), ํ์ด์ด๋ฒ ์ด์ค (Firebase), ํด๋ผ์ฐ๋ ํ์ ์ค (Cloud Functions), ํด๋ผ์ฐ๋ ์คํ ๋ฆฌ์ง (Cloud Storage) ๋ฑ์ด ์๋ค.
โ FaaS
๋๋ค์ ํด๋ผ์ฐ๋ ํ์ ์ค๋ ํน์ ํ ๋์์ ์ํํ๋ ๋ก์ง์ ์ ์ฅํ๊ณ , ์์ฒญ์ด ๋ค์ด์ฌ ๋ ๋ก์ง์ ์คํํ๋ ์๋น์ค์ด๋ค. ํจ์์ฒ๋ผ ํธ์ถํ ๋ ์คํ๋๊ธฐ์ FaaS, Fuction as a Service๋ก ๋ถ๋ฆฌ๊ธฐ๋ ํ๋ค.
์ฌ์ฉํ ๋งํผ๋ง ์๊ธ์ ์ง๋ถํ๋ฉด ๋๋ค.
โ ํด๋ผ์ฐ๋ ๋ฐ์ดํฐ ์ ์ฅ์
S3์ ํด๋ผ์ฐ๋ ์คํ ๋ฆฌ์ง๋ ํด๋ผ์ฐ๋ ๋ฐ์ดํฐ ์ ์ฅ์๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค. ์ ์ ํ์ผ์ ์ ๊ณตํ๋๋ก ๋ ธ๋ ์๋ฒ์์ ์์๋ฐ๊ธฐ๋ ํ๋ค.
{
"Version": "2022-01-24",
"Statement" : [
{
"Sid" : "AddPerm",
"Effect" : "Allow",
"Principal" : "*",
"Action" : [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::๋น์ ์๋ฒํท๋ช
/*"
}
]
}
7. AWS ์ก์ธ์ค ํค ๋ฐ๊ธ
8. ํ๋ก์ ํธ์ multer-s3 ํจํค์ง, aws-sdk ํจํค์ง ์ค์น
9. .envํ์ผ์ ๋ฐ๊ธ ๋ฐ์ ํค ํ์ผ ์์ ์ ํ ์ก์ธ์ค ํค ID์ ๋ณด์ ์ก์ธ์ค ํค๋ฅผ ๋ณต์ฌ
ํจํค์ง ์ค์น๋ ๋ค์๊ณผ ๊ฐ์ ๋ช ๋ น์ด๋ก ์งํํ๋ค.
npm i multer-s3 aws-sdk
.env ํ์ผ์ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ฑํ๋ค.
...
S3_ACCESS_KEY_ID = ์ก์ธ์คํคID
S3_SECRET_ACCESS_KEY = ์ก์ธ์คํค
S3์ ์ ๋ก๋ํ ์ด๋ฏธ์ง๋ฅผ ๋ฆฌ์ฌ์ด์งํ ํ, ์ค์ด๋ ์ด๋ฏธ์ง๋ฅผ ๋ค์ S3์ ์ ์ฅํ๋ ์์ ์ด๋ค.
์์ ์ ์งํ ๊ณผ์ ์ ๋ค์๊ณผ ๊ฐ๋ค.
1. nodebird ํด๋ ์ธ๋ถ์ aws-upload ํด๋๋ฅผ ๋ง๋ค๊ณ package.json์ ์์ฑํ๋ค.
{
"name": "aws-upload",
"version": "1.0.0",
"description": "Lambda ์ด๋ฏธ์ง ๋ฆฌ์ฌ์ด์ง",
"main": "index.js",
"author": "ZeroCho",
"license": "ISC",
"dependencies": {
"aws-sdk": "^2.634.0",
"sharp": "^0.25.1"
}
}
2. aws-upload์ .gitignore์ ์ถ๊ฐํ ํ, ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ๋ค. ๊นํ๋ธ์ ์ ๋ก๋๋๋ ๊ฒ์ ๋ง๋๋ค.
node_modules
3. aws-upload์ ๋๋ค๊ฐ ์คํํ index.js๋ฅผ ์์ฑํ๋ค.
const AWS = require('aws-sdk');
const sharp = require('sharp');
const s3 = new AWS.S3();
//handler ํจ์๊ฐ ๋๋ค ํธ์ถ ์ ์คํ๋๋ ํจ์
// event : ํธ์ถ ์ํฉ์ ๋ํ ์ ๋ณด๊ฐ ๋ด๊ฒจ ์์
// context : ์คํ๋๋ ํจ์ ํ๊ฒฝ์ ๋ํ ์ ๋ณด๊ฐ ๋ด๊ฒจ ์์
// callback : ํจ์๊ฐ ์๋ฃ๋์๋์ง ๋๋ค์๊ฒ ์๋ฆผ
// callback์ ์ฒซ๋ฒ์งธ ์ธ์ error ์ฌ๋ถ / ๋๋ฒ์งธ ์ธ์ ๋ฐํ๊ฐ
exports.handler = async (event, context, callback) => {
// event ๊ฐ์ฒด๋ก๋ถํฐ ๋ฒํท ์ด๋ฆ(Bucket)๊ณผ ํ์ผ ๊ฒฝ๋ก(Key)๋ฅผ ๋ฐ์์ด
// ์ด๋ฅผ ํตํด ํ์ผ๋ช
๊ณผ ํ์ฅ์๋ ์ป์
const Bucket = event.Records[0].s3.bucket.name;
const Key = event.Records[0].s3.object.key;
const filename = Key.split('/')[Key.split('/').length - 1];
const ext = Key.split('.')[Key.split('.').length - 1];
const requiredFormat = ext === 'jpg' ? 'jpeg' : ext; // sharp์์๋ jpg ๋์ jpeg ์ฌ์ฉํฉ๋๋ค.
console.log('name', filename, 'ext', ext);
// s3.getObject ๋ฉ์๋๋ก ๋ฒํท์ผ๋ก๋ถํฐ ํ์ผ์ ๋ถ๋ฌ์ด
// s3Object.Body์ ํ์ผ ๋ฒํผ๊ฐ ๋ด๊ฒจ ์์
try {
const s3Object = await s3.getObject({ Bucket, Key }).promise(); // ๋ฒํผ๋ก ๊ฐ์ ธ์ค๊ธฐ
console.log('original', s3Object.Body.length);
// sharp ํจ์๋ฅผ ํ์ผ ๋ฒํผ์ ๋ฃ๊ณ resize ๋ฉ์๋๋ก ํฌ๊ธฐ ์ง์
// resize(๊ฐ๋ก, ์ธ๋ก, ์กฐ์ ๊ธฐ์ค)
// fit:inside -> ๊ฐ๋ก ์ธ๋ก ์ฌ์ด์ฆ ์์ ๋ฑ ๋ง๊ฒ ์ด๋ฏธ์ง ์กฐ์
// toBuffer ๋ฉ์๋๋ ๋ฆฌ์ฌ์ด์ง๋ ์ด๋ฏธ์ง ๊ฒฐ๊ณผ๋ฅผ ๋ฒํผ๋ก ์ถ๋ ฅ
const resizedImage = await sharp(s3Object.Body) // ๋ฆฌ์ฌ์ด์ง
.resize(200, 200, { fit: 'inside' })
.toFormat(requiredFormat)
.toBuffer();
// s3.putObject ๋ฉ์๋๋ก ๋ฆฌ์ฌ์ด์ง๋ ์ด๋ฏธ์ง๋ฅผ thumb ํด๋์ ์ ์ฅ
await s3.putObject({
Bucket,
Key: `thumb/${filename}`,
Body: resizedImage,
}).promise();
console.log('put', resizedImage.length);
return callback(null, `thumb/${filename}`);
} catch (error) {
console.error(error);
return callback(error);
}
};
4. ๋๋ค์ ๋ฐฐํฌ๋ฅผ ์งํํ๋ค.
Lightsail์์ ๋น๋ํ ํ S3๋ก ๋ฐฐํฌํ๊ณ , ๋๋ค๋ S3์์ ๋ฐฐํฌ๋ ํ์ผ์ ๊ฐ์ ธ์ ํจ์๋ก ๋ง๋ ๋ค.
sharp๊ฐ ์๋์ฐ์ฉ, ๋งฅ์ฉ, ๋ฆฌ๋ ์ค์ฉ์ผ๋ก ๊ตฌ๋ถ๋๋ค. ํ์ฌ๋ ์๋์ฐ ํ๊ฒฝ์์ ์ค์ต ์ค์ด๋ฏ๋ก ๋น๋ ์ sharp๊ฐ ์๋์ฐ์ฉ์ผ๋ก ์ค์น๊ฐ ๋๋๋ฐ, ๋๋ค๋ ๋ฆฌ๋ ์ค์ด๋ฏ๋ก ํธํ์ด ๋์ง ์๊ธฐ์ Lightsail์์ ๋ฐฐํฌ๋ฅผ ์งํํ๋ค.
1. ๊นํ๋ธ์ aws-upload ๋ ํฌ์งํฐ๋ฆฌ ์์ฑ ํ push
2. Lightsail ์ธ์คํด์ค SSH์ ์ ์ํ์ฌ ๊นํ๋ธ ๋ ํฌ์งํฐ๋ฆฌ clone ๋ฐ์
3. npm i ํ aws-upload ํด๋ ์๋์ ๋ชจ๋ ํ์ผ์ ์์ถํ์ฌ aws-upload.zip ํ์ผ ๋ง๋ฆ
4. Lightsail์์ S3๋ก ํ์ผ ์ ๋ก๋ (aws-cli ์ค์นํ๊ณ aws configure ๋ช ๋ น์ ์ ๋ ฅ ํ ๋ฐ๊ธ๋ฐ์๋ ํค ํ์ผ ์ ๋ณด ์ ๋ ฅ)
5. aws-cli๋ฅผ ์ฌ์ฉํด aws-upload.zip ์ ๋ก๋
5. ๋๋ค ์๋น์ค ์ค์
1. AWS ํํ์ด์ง - ์ ํ - ์ฃผ์ ์๋น์ค - AWS Lambda๋ฅผ ํด๋ฆญํ๋ค.
2. ํจ์ ์์ฑ ๋ฒํผ์ ํด๋ฆญํ๋ค.
1) ํจ์ ์ด๋ฆ : node-deploy
2) Runtime : Node.js 12.x
3. Amazon S3์์ ํ์ผ์ ์ ๋ก๋ํ๋ค.
1) Amazon S3 ๋งํฌ URL : https://๋ฒํท๋ช .s3.์ง์ญ๋ช .amazonaws.com/ํ์ผ๋ช
4. ๊ธฐ๋ณธ ์ค์ - ํธ์ง ๋ฒํผ์ ํด๋ฆญํ๋ค.
5. ํธ๋ฆฌ๊ฑฐ ์ถ๊ฐ : S3์ ์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ ํ ๋๋ง๋ค ๋๋ค ํจ์๊ฐ ๋์ํ๋๋ก ํ๋ค.
6. nodebird/routes/post.js
๊ธฐ์กด ์ฃผ์์์ original ํด๋๋ฅผ thumb ํด๋๋ก ๊ต์ฒดํ๋ค.
...
router.post('/img', isLoggedIn, upload.single('img'), (req, res) => {
console.log(req.file);
const originalUrl = req.file.location;
const url = originalUrl.replace(/\/original\//, '/thumb/');
res.json({ url, originalUrl });
});
...
7. nodebird/views/main.html
img ํ๊ทธ์ onerror ์์ฑ์ ๋ถ์ฌ ๋ฆฌ์ฌ์ด์ง๋ ์ด๋ฏธ์ง๋ฅผ ๋ก๋ฉํ๋ ๋ฐ ์คํจํ๋ฉด ์๋ณธ ์ด๋ฏธ์ง๋ฅผ ์ฌ์ฉํ๋๋ก ํ๋ค.
...
{% if twit.img %}
// ์๋ div ํ๊ทธ ์ถ๊ฐ
<div class="twit-img">
<img
src="{{twit.img}}"
onerror="this.src = this.src.replace(/\/thumb\//, '/original/');"
alt="์ฌ๋ค์ผ"
/>
</div>
...
{% block script %}
<script>
if (document.getElementById('img')) {
document.getElementById('img').addEventListener('change', function(e) {
const formData = new FormData();
formData.append('img', this.files[0]);
axios.post('/post/img', formData)
.then((res) => {
document.getElementById('img-url').value = res.data.url;
document.getElementById('img-preview').src = res.data.originalUrl; // ์ถ๊ฐ
document.getElementById('img-preview').style.display = 'inline';
})
์ด๋ฏธ์ง ๋ฏธ๋ฆฌ๋ณด๊ธฐ(img-preview) ์์๋ ์๋ณธ ์ด๋ฏธ์ง๋ฅผ ๋ณด์ฌ์ฃผ๊ณ , ์ด๋ฏธ์ง๋ฅผ ์ ์ฅํ ํ์๋ ๋ฆฌ์ฌ์ด์ง๋ ์ด๋ฏธ์ง๋ฅผ ๋ณด์ฌ์ฃผ๋๋ก ํ๋ค.
์ด๋ฏธ์ง ์ ๋ก๋์ ๋ฆฌ์ฌ์ด์ง ๊ฐ์ ์๊ฐ์ฐจ๋๋ฌธ์ ์ด๋ฏธ์ง๊ฐ ๋ณด์ด์ง ์๋ ํ์์ ํด๊ฒฐํ๊ธฐ ์ํด ์ฌ์ฉํ๋ค.
๊ตฌ๊ธ ํด๋ผ์ฐ๋ ์คํ ๋ฆฌ์ง์ ์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ํ๋ ๋ฐฉ๋ฒ์ด๋ค.
1. ๊ตฌ๊ธ ํด๋ผ์ฐ๋ ํ๋ซํผ - Storage ์ ํ - ๋ฒํท ์์ฑ ๋ฒํผ
2. ํด๋ผ์ฐ๋ ์คํ ๋ฆฌ์ง ์ ๊ทผ ํค ๋ฐ๊ธ๋ฐ๊ธฐ
3. multer-google-storage ํจํค์ง๋ฅผ ์ค์นํ๋ค.
4. nodebird/routes/post.js๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ๋ค.
const express = require('express');
const multer = require('multer');
const fs = require('fs');
const multerGoogleStorage = require('multer-google-storage');
const upload = multer({
storage: multerGoogleStorage.storageEngine({
bucket: 'nodebird',
projectId: 'node-deploy-270114',
keyFilename: 'node-deploy-270114-b024dbed754a.json',
}),
limits: { fileSize: 5 * 1024 * 1024 },
});
storage ์์ฑ ์์ multerGoogleStorage๋ฅผ ์ฌ์ฉํ์ฌ multer-google-storage๋ฅผ ์ฌ์ฉํ๋ค.
bucket, projectId, keyFilename : ๋ฒํท๋ช , ํ๋ก์ ํธ ID, ํค ํ์ผ๋ช
npm i multer-google-storage
๊ตฌ๊ธ ํด๋ผ์ฐ๋ ํ์ ์ค๋ ๋ค์๊ณผ ๊ฐ์ ๋ก์ง์ผ๋ก ๋์ํ๋ค. ์์ ๋ณด์๋ AWS ๋๋ค์ ์ ์ฌํ ๊ตฌ์กฐ๋ฅผ ๋๊ณ ์๋ค.
gcp-upload/package.json
nodebird ํด๋ ์ธ๋ถ์ gcp-upload ํด๋๋ฅผ ์์ฑํ ํ package.json ํ์ผ์ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ๋ค.
{
"name": "gcp-upload",
"version": "1.0.0",
"description": "Cloud Functions ์ด๋ฏธ์ง ๋ฆฌ์ฌ์ด์ง",
"main": "index.js",
"author": "ZeroCho",
"license": "ISC",
"dependencies": {
"@google-cloud/storage": "^1.6.0",
"gm": "^1.23.1",
"sharp": "^0.25.1"
}
}
gcp-upload/.gitignore
node_modules
gcp-upload/index.js
const storage = require('@google-cloud/storage')();
const sharp = require('sharp');
exports.resizeAndUpload = (data,context) => {
const { bucket, name } = data;
const ext = name.split('.')[Key.split('.').length -1];
const requiredFormat = ext === 'jpg' ? 'jpeg' : ext;
console.log('name', name, 'ext', ext);
const file = storage.bucket(bucket).file(name);
const readStream = file.createReadStream();
const newFile = storage.bucket(bucket).file(`thumb/${name}`);
const writeStream = newFile.createWriteStream();
sharp(readStream)
.resize(200, 200, { fit : 'inside' })
.toFormat(requiredFormat)
.pipe(writeStream);
return new Promise((resolve, reject) => {
writeStream.on('finish', () => {
resolve(`thumb/${name}`);
});
writeStream.on('error', reject);
});
};
1. GCP - Cloud Functions - ํจ์ ๋ง๋ค๊ธฐ ๋ฒํผ ํด๋ฆญ
- ํจ์ ์ด๋ฆ : gcp-upload
- ํธ๋ฆฌ๊ฑฐ : Cloud Storage
- ์ด๋ฒคํธ ์ ํ : ์๋ฃ/์์ฑ
- ๋ฒํท : ๋ณธ์ธ์ด ๋ง๋ ๋ฒํท ์ ํ
- ์์ค์ฝ๋ : Cloud Storage์ ZIP์ ํด๋ฆญํด ๋ฐฉ๊ธ ์ ๋ก๋ํ ํ์ผ ์ ๋ก๋
- ์คํํ ํจ์ : resizeAndUpload (*index.js์ exports.resizeAndUpload ์ด ๋ถ๋ถ๊ณผ ๋์ผํด์ผ ํจ)
2. NodeBird ์ฝ๋ ์์ ํ๊ธฐ(16.3๊ณผ ๋์ผ)
nodebird/routes/post.js
...
router.post('/img', isLoggedIn, upload.single('img'), (req, res) => {
console.log(req.file);
const filePath = req.file.path.split('/').splice(0, 3).join('/');
const originalUrl = `${filePath}/${req.file.filename}`;
const url = originalUrl.replace(/\/original\//, '/thumb/');
res.json({ url, originalUrl });
});
...
nodebird/views/main.html
...
{% if twit.img %}
<div class="twit-img">
<img
src="{{twit.img}}"
onerror="this.src = this.src.replace(/\/thumb\//, '/original/');"
alt="์ฌ๋ค์ผ"
/>
</div>
{% endif %}
...
...
document.getElementById('img-preview').src = res.data.originalUrl;
...
[Node.js] 15์ฅ AWS์ GCP๋ก ๋ฐฐํฌํ๊ธฐ (0) | 2023.01.05 |
---|---|
[Node.js] 14์ฅ CLI ํ๋ก๊ทธ๋จ ๋ง๋ค๊ธฐ (0) | 2023.01.05 |
[Node.js] 12์ฅ ์น ์์ผ์ผ๋ก ์ค์๊ฐ ๋ฐ์ดํฐ ์ ์กํ๊ธฐ (0) | 2022.12.22 |
[Node.js] 11์ฅ ๋ ธ๋ ์๋น์ค ํ ์คํธํ๊ธฐ (0) | 2022.12.01 |
[Node.js] 10์ฅ ์น API ์๋ฒ ๋ง๋ค๊ธฐ (0) | 2022.11.24 |