2022. 5. 6. 17:26ㆍ책/Node.js 교과서
multer부분을 따로 뺀 이유는 사용 자체가 어려운 편에 속하고, 짚고 넘어가야 할 개념들이 많기 때문이다. 그렇다고 모른 채 지나간다면 파일 업로드를 처리할 수 없으므로 차근차근 알아보기로 하자.
1. 파일업로드의 본질
1.1 HTTP 메시지
이전에도 설명했지만 HTTP 요청/응답은 Header와 Body로 구성되어 있다. Header를 보고 '어떤 형태'의 데이터인지 확인하고 Body를 보고 구체적인 '내용'을 확인할 수 있는 것이다. 즉, 모든 HTTP 메시지는 Header와 Body가 적절한 형태로 채워져 있어야 소프트웨어가 동작할 수 있다.
1.2 클라이언트 → 서버 파일 업로드 과정 이해하기
파일 업로드를 구현한다고 해보자. 클라이언트 측에서 파일을 실어서 서버로 보낼 것이고, 서버는 이를 보고 해석한 후 필요한 응답을 해줄 것이다. 그리고 이 모든 과정 또한 HTTP 메시지를 주고받는 일에 속한다. 즉, '어떤 형태'의 데이터를 실을 것인지 Header를 명시해줘야 하고, 파일의 구체적인 '내용'을 Body에 담아줘야 한다.
- 클라이언트 측에선, 그러니까 웹 브라우저의 경우라면 폼 태그를 통해 파일을 등록한 후 서버로 전송한다
- 이 말은 HTTP 메시지를 서버로 보낸다는 뜻이고, 이에 따라 Header와 Body를 적절한 모양으로 채워줘야 한다
- 즉, HTTP 메시지의 Header중 Content-type을 채워줘야 하는데, 이때 multipart/form-data로 지정하게 되는 것. (더 정확하게 표현하자면, Content-type은 HTTP 메시지의 Header 중 엔티티 헤더에 속한다)
# <form> 태그
- name : 서버에 데이터를 보낼 때 데이터 구분을 위해 달아주는 이름
- action : 데이터를 보낼 경로
- method : HTTP 요청 메서드
- enctype : 데이터를 서버로 전송할 때, 해당 데이터가 인코딩되는 방식
# enctype
- application/x-www-form-urlencoded : default 값으로, 모든 문자들을 서버로 보내기 전에 인코딩 됨을 명시한다
- text/plain : 공백 문자는 '+'기호로 변환하되 나머지 문자는 모두 인코딩되지 않음을 명시
- multipart/form-data : 모든 문자를 인코딩하지 않는다. 보통 파일이나 이미지를 서버로 전송할 때 사용한다
2. Multipart 탄생 배경
그런데 왜 굳이 Content-type으로 multipart가 필요했던 걸까? 그 이유는 한 번에 여러 타입을 처리하기 위함이다.
위 사진에서 폼 태그의 <input> 태그를 보자. 현재 하나의 폼 안에서 'file' 타입을 받는 <input>, 'text'타입을 받는 <input> 각각 한 개씩 존재한다. 즉 각 요청의 content-type은 'image/jpeg', 'text/palin'인 것이다.
보통 HTTP Request의 Content-type은 한 종류로 지정되나 위와 같은 상황에선 타입이 다른 두 데이터가 하나의 폼안에 담겨서 서버로 보내진 다는 것이다. 따라서 둘을 적절하게 구분해주면서 한 번에 전송 및 처리될 수 있도록 해주는 것이 multipart 타입인 것이다.
그리고 이 multipart 타입을 처리해주는 미들웨어가 multer인 것.
3. multer
multipart/form-data를 다루기 위한 미들웨어 (Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files. It is written on top of busboy for maximum efficiency)
req.file(혹은 req.files)과 req.body 객체를 생성해 서버로 업로드된 파일을 핸들링할 수 있게 해 준다. 파일을 전달받는 개수에 따라 호출하는 메서드가 약간씩 달라진다. 아래는 멀터객체 생성 및 메서드들.
# multer( options )
멀터 객체를 반환한다. body와 file객체를 (업로드된 파일이 복수개일 경우 files) request 객체에 추가하고, 이를 통해 파일 업로드를 제어한다.
- options : 보통의 웹 앱에선 파일 업로드 위치를 지정하는 dest (혹은 storage) 옵션 정도로도 충분하다
dest(혹은 storage) | 파일이 저장될 위치 |
fileFilter | 어떤 파일을 허용할지 제어하는 함수 |
limits | 업로드 데이터 한도 |
preservePath | 파일의 base name대신 보존할 파일의 전체 경로 |
# multer.single( fieldname )
fieldname 인자에 명시된 한 개의 파일을 전달받는다. 전달받은 파일은 req.file에 저장.
( fieldname : 폼 태그의 <input name="어쩌구" >에서 지정해준 name을 말함)
# multer.array( fieldname [, maxCount] )
fieldname 인자에 명시된 여러 개의 파일을 전달받는다. 전달받은 파일은 req.files에 저장.
# multer.fileds( fields )
fieldname이 서로 다른 파일들을 전달받을 때 사용한다. 전달받고자 하는 필드네임을 배열 형태로 지정해주면 해당 필드네임에 맞는 파일들을 가져올 수 있음.
ex) const fields = [ { name: 'image1' }, { name: 'imageSet', maxCount: 4 } ];
##### storage engines #####
멀터에 storage 옵션을 지정할 때 storage의 설정을 지정해주는 컴포넌트. 파일을 '디스크'에 저장하도록 도와주는 엔진인 diskStorage와 '메모리'에 저장하도록 도와주는 'memoryStorage' 엔진이 있다. 그 외 필요한 것들은 서드파티 라이브러리로 추가할 수 있다.
# multer.diskStorage( [destination] [, filename] )
- destination <string>|(req, file, callback)
어떤 폴더에 파일을 저장할지 결정. 따로 지정해주지 않으면 os에서 임시 파일을 저장하는 기본 디렉토리를 사용한다. 경로를 직접 'string' 형태로 지정해주거나 callback함수를 넣어서 적당히 처리 후 (필요하다면 에러처리도 가능) 경로를 지정해줄 수 있다.
- callback(error :Error, destination :string) : 에러가 있다면 에러를 넣어주고, 나머진 파일을 저장할 경로를 'string' 타입으로 지정해준다 - filename(req, file, callback)
업로드된 파일을 저장할 때 따로 파일명을 지정하는 옵션. 확장자도 함께 명시해줘야 한다.
- callback(error :Error, filename :string)
3.1 단일 파일 업로드
위 그림처럼 하나의 파일을 업로드한다고 치자. 지금은 익숙지 않아 어디서부터 손대야 할지 감도 안 잡히겠지만 아래와 같은 과정을 거친다고 생각하고 코드를 짜면 된다.
- 클라이언트로부터 파일을 전달받을 수 있게 폼 양식을 구성한다
- multer 객체를 생성해 전달받은 파일을 어떻게 요리할지 구성한다
- 라우터에 만들어준 multer 미들웨어 함수를 single 메서드로 감싸서 껴넣는다. 끝.
import multer from 'multer';
const upload = multer({
storage: multer.diskStorage({
destination(req, file, done) {
done(null, 'uploads/');
},
filename(req, file, done) {
const ext = path.extname(file.originalname);
done(null, path.basename(file.originalname, ext) + Date.now() + ext);
}
})
})
app.post('/upload', upload.single('image'), (req, res) => {
console.log(req.file, req.body);
res.send('ok');
})
코드설명
path.extname( '파일명' ) : 파일의 확장자를 추출한다 ('.js'같은)
path.basename('파일명', '확장자') : 파일명에서 확장자를 제거한 나머지 값을 반환
file.originalname : 클라이언트측에서 업로드한 파일명
참고로 multer( ... ) 부분에서 콜백함수로 받는 file은 아래처럼 생겼음
file
{
fieldname: 'image',
originalname: '그림1.png',
encoding: '7bit',
mimetype: 'image/png'
}
콘솔창
// req.file
{
fieldname: 'image',
originalname: '그림1.png',
encoding: '7bit',
mimetype: 'image/png',
destination: 'uploads/',
filename: '그림11651848968029.png',
path: 'uploads\\그림11651848968029.png',
size: 97563
}
// req.body
{ title: '그림1 업로드' }
3.2 복수 파일 업로드(array)
업로드 파일의 개수가 늘어났다고 드라마틱하게 뭐가 변하지는 않는다. 다만 multer 객체를 미들웨어에 껴넣을 때 array( ) 메서드로 감싸고 req.files로 읽는다는 정도만 달라질 뿐.

// 이 부분은 달라질 게 없어서 생략했다
const upload = multer( ... )
app.post('/upload/array', upload.array('image'), (req, res) => {
console.log(req.files, req.body);
res.send('ok');
})
3.3 복수 파일 업로드(fields)

app.post('/upload/fields',
upload.fields([ { name: 'image1'}, {name: 'image2'} ]),
(req, res) => {
console.log(req.files, req.body);
res.send('ok');
}
)
4. 정리
결국 멀터로 파일을 업로드할 땐 아래 세 단계를 거친다고 생각하면 된다.
- 클라이언트로부터 파일을 전달받을 수 있게 폼 양식을 구성한다
- multer 객체를 생성해 전달받은 파일을 어떻게 요리할지 구성한다
- 라우터에 만들어준 multer 미들웨어 함수를 업로드 파일 갯수에 맞게 메서드로 잘 감싸서 껴넣는다. 끝.
'책 > Node.js 교과서' 카테고리의 다른 글
[7장] MySQL (0) | 2022.05.07 |
---|---|
[6장] Express / Router (0) | 2022.05.07 |
[6장] Express (0) | 2022.05.05 |
[5장] npm 패키지 매니저 (0) | 2022.05.04 |
[4장] 서버 만들기 (cluster) (0) | 2022.05.03 |