블로그 프로젝트 - 2-3 : 주요 개발 내용 - Multer & Amazon S3

HK_Jang·2022년 4월 6일
0

블로그 프로젝트

목록 보기
6/10
post-thumbnail

1. Multer

1. 무엇인가?

Multer란 Node.js 미들웨어이다. HTML Form에서 multipart/form-data로 넘겨주는 데이터를 처리하기 위한 패키지이다.

2. 왜 사용했는가?

처음에 Amazon S3 업로드를 구현했을 때, 파일스트림을 이용해 구현하였는데, 다음과 같은 문제점이 있었다.

  • 파일 경로를 읽어올 수 없는 문제
    HTML form 태그의 디폴트 타입(application/x-www-form-urlencoded)을 사용하게 되면, input 태그의 파일을 사용하더라도, 파일명만 반환하고 파일 경로를 반환하지 않았다. 혹은, 파일 자체를 받을 수도 없었다.
    파일 경로를 받는 것은, 중대한 보안 위반이기 때문에 불가능하다. 또, 파일 자체를 받는 것은 텍스트와 이미지는 데이터 종류가 달라 동시에 받을 수 없었다. 따라서 실질적으로 파일에 접근할 수 없었다.

따라서 multipart/form-data로 데이터를 넘겨, 일반 텍스트와 사진 데이터를 구분해 받을 수 있었고, 이를 처리하기 위해 Multer패키지를 이용하게 되었다.

3. 어떻게 사용했는가?

Amazon S3와 묶어 사용했기 때문에, Amazon S3에 대해 설명한 후 같이 기술하겠다.

2. Amazon S3

1. 무엇인가?

Amazon S3는 아마존에서 제공하는 클라우드 서비스이다.

2. 왜 사용했는가?

앱 배포 시 Amazon EC2를 활용한 배포를 처음부터 생각중이었다. 그런데 Amazon EC2의 프리티어 사용시 스토리지의 용량은 불과 30GB밖에 되지 않았다. 그렇다고 내 PC를 서버로 이용해 배포하기엔 보안 측면에서도 유지 비용 측면에서도 크게 원하지 않는 방법이었다. 따라서 이미지 저장 공간을 분리해, 저장된 이미지의 URL을 불러온 후 그 이미지를 img src태그를 이용해 띄우는 방법을 생각해 냈다.

3. 어떻게 사용했는가?

  1. AWS는 친절하게 S3를 이미지 스토리지로 활용해 HTML페이지에서 사진을 불러오고 싶으면, 어떻게 구성해야 하는지 친절하게 알려준다. 링크의 문서를 참고해 버킷(저장소)를 구성했다. 그 후, Admin이 아닌 일반 유저라 할 수 있는 IAM 유저를 생성하였다.
  2. IAM계정을 활용, aws s3 객체를 생성해주었다.

aws.js

const aws = require('aws-sdk');
const id = ''; // IAM ID
const secret = ''; // IAM 비밀번호
const s3 = new aws.S3({ accessKeyId:id, secretAccessKey:secret });

module.exports = s3;
  1. 생성된 aws s3객체를 불러와, multer의 storage 설정을 통해, multer를 통해 받은 multipart data를 저장할 장소 (이번 경우 Amazon S3 Bucket)을 설정해 주었다.
    참고로, multerfilter를 통해 content type이 이미지인 파일만 업로드하도록 허용하였고, 또한 업로드시 content type이 이미지인 것을 명시해 주어야 한다.
    그렇지 않으면 이미지가 정상적으로 표시되지 않고, '새 탭에서 이미지 열기' 등으로 이미지를 보려고 하면 이미지가 아닌 일반 파일로 인식되어 로컬 저장되어 버린다.

multer.js

const multer = require('multer'); // multipart-data 처리를 통한 파일 업로드를 위한 multer
const multerS3 = require('multer-s3'); // multer를 활용, s3로 파일 업로드
s3 = require('./aws'); // aws config

// Multer를 위한 필터. 이미지 파일만 Multer를 통해 업로드하고 아닌 경우 에러 발생.
const multerfilter = (req, file, cb) => {
    if (file.mimetype.startsWith('image')){
        cb(null, true);
    } else {
        cb(console.log('Not image file upload tried'), false);
    }
}

// multer 저장소 및 필터 설정
const upload = multer({
    storage: multerS3({
        s3: s3,
        bucket: 'blogprojectbucket',
        contentType: multerS3.AUTO_CONTENT_TYPE, // content type 들어오는대로 설정
        key: function(req, file, cb){
                if(file.originalname === undefined || file.originalname === null){ }
                else {
                    cb(null, Date.now() + '.' + file.originalname.split('.').pop()); // 파일 이름 설정을 위한 callback function
                }
        }
    }),
    fileFilter: multerfilter,
},'NONE');

module.exports = upload;
  1. HTML단에서 multipart/form-data형태로 post하여 전달한다.

index_admin.ejs

<form action="/api/insertAboutme" enctype="multipart/form-data" method="post" id="changeAboutmeForm">
...
 <div class="row d-flex col-md-12 justify-content-center">
          <div class="mb-1 col-md-8">
              <div class="col-md-12">
                  <label for="postPhoto" class="form-label">사진 선택</label>
                  <input class="form-control form-control" id="postPhoto" type="file" name="postPhoto" accept="image/png, image/gif, image/jpeg">
              </div>
          </div>
      </div>
...
  1. 라우터를 통해 multer 업로드를 필요로 하는 페이지에 multer가 설치되어, input file 태그의 name요소로 업로드를 필요로 하는 multipart data를 찾아, Amazon S3 스토리지에 업로드한다.

routes/api.js

// /api

const express = require('express');
const { getAboutme, getAboutmeById, insertAboutme, updateAboutme, deleteAboutme, insertImage, insertPost, deletePost, updatePost } = require('../controllers/apiController');
const upload = require('../lib/multer');

const router = express.Router();
...
router.post('/insertAboutme', upload.single('postPhoto'), insertAboutme);
router.post('/updateAboutme/:postId',upload.single('postPhoto'), updateAboutme);
router.post('/deleteAboutme/:postId',upload.single(), deleteAboutme);
router.post('/insertImage', upload.single('img'), insertImage);
// upload.single()은 파일 한 개의 업로드를 지원하는 함수로서, 인자로 input file 태그의 name 요소를 전달한다.
...
  1. 그 후, req.file.location을 통해 multer가 업로드한 AWS S3 Object의 주소를 받을 수 있다. 그 주소를 DB에 저장한다.
// @post
// /api/insertAboutme
const insertAboutme = async(req, res) => {
    ...
            if(req.file !== undefined) {
                var imgurl = req.file.location; // router에서 붙인 multer가 반환한 url (aws s3 object url)
            }
            
    ...
};
  1. 결과로, 이런 형태의 이미지URL이 저장되는 것을 볼 수 있다. WYSIWIG 에디터를 통해 글을 저장할때도 유사한 방식을 택했는데, 이는 추후 WYSIWIG 에디터를 설명하면서 기재하도록 하겠다.

MariaDB에 저장된 이미지 URL들

AWS S3 Bucket에 저장된 이미지들

정상적으로 표시되는 이미지들.

HTML 소스 보기를 통해 본 이미지를 표시하는 모습

profile
살아남는 종은 강한 종이나 똑똑한 종이 아닌, 변화에 적응하는 종이다. - 찰스 다윈

0개의 댓글