Juni_Dev_log

(node.js) [Part.5] 웹 서버 만들기 - 파일 업로드 기능 만들기 본문

Theorem (정리)/node.js

(node.js) [Part.5] 웹 서버 만들기 - 파일 업로드 기능 만들기

Juni_K 2020. 12. 12. 16:03

웹 서버는 기본적으로 서버에 저장된 문서를 조회하거나 데이터를 받아 저장할 수 있지만 파일 자체를 업로드하거나 다운로드하는 경우도 자주 있다.

특히, 모바일 단말로 찍은 사진을 업로드하거나 웹이나 모바일에서 사진을 다운로드하여 보는 일이 많아지면서 이미지 파일을 다루는 경우도 많다.

외장 모듈을 사용하면 익스프레스에서 파일을 업로드할 수 있다.

파일을 업로드할 때멀티 파트 포맷으로 된 파일 업로드 기능을 사용하며, 파일 업로드 상태 등을 확인할 수 있다.

 

멀티 파트 포맷은 웹 서버에서 파일을 업로드하기 위해서 사용한다.

multipart 포맷은 음악이나 이미지 파일 등을 일반 데이터와 함께 웹 서버로 보내려고 만든 표준이다.
따라서 일반적으로 웹 서버에서 파일을 업로드할 때는 multipart 포맷을 사용한다.

multer 미들웨어 설치해서 파일 업로드하기

여기에서는 multer 미들웨어로 파일을 업로드하는 방법에 대해서 알아보자.

먼저, 명령 프롬프트에서 multer 미들웨어를 설치한다.

 

npm install multer --save

 

미들웨어를 사용하는 순서가 바뀌면 오류가 날 수 있다.

파일 업로드 기능을 구현할 때 body-parser , multer 등의 미들웨어를 사용한다.
그런데 이렇게 여러 개의 미들웨어를 사용할 때 사용 순서가 바뀌면 제대로 동작하지 않을 수 있다. 따라서, 미들웨어의 사용 순서가 바뀌지 않도록 주의하자.

app12.js 파일을 복사해서 app13.js 를 만들고 모듈을 불러들이는 부분에서 다음과 같이 require() 메소드를 호출한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 
// Express 기본 모듈 불러오기
var express = require('express')
  , http = require('http')
  , path = require('path');
 
// Express의 미들웨어 불러오기
var bodyParser = require('body-parser')
  , cookieParser = require('cookie-parser')
  , static = require('serve-static')
  , errorHandler = require('errorhandler');
 
// 에러 핸들러 모듈 사용
var expressErrorHandler = require('express-error-handler');
 
// Session 미들웨어 불러오기
var expressSession = require('express-session');
 
// 파일 업로드용 미들웨어
var multer = require('multer');
var fs = require('fs');
 
// 클라이언트에서 ajax로 요청했을 때 CORS(다중 서버 접속) 지원
var cors = require('cors');
cs

파일을 업로드한 후에 업로드한 파일을 다루어야 하는 경우가 많으므로 fs 모듈도 함께 불러들였다.

오류 처리를 다루는 errorhandler 와 express-error-handler 모듈도 불러들였는데, 이 모듈들을 명령 프롬프트에서 먼저 설치를 진행해야한다.

 

cors 모듈은 CORS 라고 불리는 다중 서버 접속 지원을 위해 사용된다.

보통 웹 브라우저에 로딩된 웹 페이지는 이 페이지가 로딩된 서버로만 접근할 수 있지만 CORS 기능을 사용하면 페이지가 로딩된 서버 이외의 서버에도 접근할 수 있다. 이 기능이 모듈에 구현되어 있어 cors 모듈을 명령 프롬프트에서 설치한 불러들인다.

body-parser, cookie-parser, express-session 을 미들웨어로 사용할 수 있도록 다음 부분에서 multer 미들웨어를 추가하는 코드를 넣는다. 그다음에는 router 미들웨어를 추가하는 코드를 입력한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
...
 
// 익스프레스 객체 생성
var app = express();
 
// 기본 속성 설정
app.set('port', process.env.PORT || 3000);
 
// body-parser를 이용해 application/x-www-form-urlencoded 파싱
app.use(bodyParser.urlencoded({ extended: false }))
 
// body-parser를 이용해 application/json 파싱
app.use(bodyParser.json())
 
// public 폴더와 upload 폴더 오픈
app.use('/public'static(path.join(__dirname, 'public')));
app.use('/uploads'static(path.join(__dirname, 'uploads')));
 
// cookie-parser 설정
app.use(cookieParser());
 
// 세션 설정
app.use(expressSession({
    secret:'my key',
    resave:true,
    saveUninitialized:true
}));
 
// 클라이언트에서 ajax로 요청했을 때 CORS(다중 서버 접속)지원
app.use(cors());
 
//multer 미들웨어 사용 : 미들웨어 사용 순서 중요 body-parser -> multer -> router
// 파일 제한 : 10개, 1G
var storage = multer.diskStorage({
    destination: function(req, file, callback){
        callback(null'uploads')
    },
    filename: function(req, file, callback){
        callback(null, file.originalname + Date.now())
    }
});
 
var upload = multer({
    storage: storage,
    limits: {
        files: 10,
        fileSize: 1024*1024*1024
    }
});
 
// 라우터 사용하여 라우팅 함수 등록
var router = express.Router();
 
...
cs

multer 미들웨어를 사용하려면 multer 미들웨어 함수를 실행한다.

이때 반환된 객체는 라우팅 함수를 등록할 때 파라미터로 넘겨준다. multer 함수를 호출하면서 파라미터로 전달하는 객체에는 속성이나 콜백 함수를 설정할 수 있다.

우리는 다양한 속성과 콜백 함수를 설정했지만, 간단하게 destination 속성에 파일이 저장될 폴더만 지정해도 모듈이 잘 동작한다. 여기에서는 사용한 주요 속성과 메소드의 의미는 다음과 같다.

 

속성/메소드 이름 설명
destination 업로드한 파일이 저장될 폴더를 지정한다.
filename 업로드한 파일의 이름을 바꿉니다.
limits 파일 크기나 파일 개수 등의 제한 속성을 설정하는 객체이다.

destination 속성으로 지정한 폴더는 프로젝트 폴더 안에 만들어져 있어야 한다.

여기에서는 [uploads] 폴더로 지정했으므로 업로드된 파일은 모두 이 폴더에 저장된다.

 

업로드된 파일은 요청 객체의 files 속성에 들어가며, 각각의 파일 이름은 클라이언트에서 보내 온 name  속성의 값이 된다.

limits 객체는 파일 크기를 제한하는 등 파일을 다룰 때 필요한 제약(Limitaion)을 만들어 자바스클비트 객체로 설정할 수 있다.

여기에서는 한 번에 업로드할 수 있는 파일의 수를 10개로 제한하고, 파일 크기는 1Mbyte 까지로 허용하였다.

 

파일을 업로드하면 업로드한 파일 이름이 중복될 수 있다. 이 때문에 고유한 파일 이름으로 변경하여 저장하는 것이 좋다. filename 속성을 설정하면 파일을 업로드했을 때 그 이름을 변경할 수 있다.

여기에서는 다른 파일과 중복되지 않도록 업로드한 파일 이름에 현재 시각을 붙여 저장했다.

클라이언트의 요청 처리 함수 추가하기

이제 multer 미들웨어를 사용할 수 있게 되었으므로 클라이언트의 요청을 처리할 함수를 추가할 수 있다.

서버 쪽에서 처리할 함수를 추가하기 전에 먼저 사용자가 띄워 볼 웹 문서를 만든다.

[public] 폴더 안에 photo.html 파일을 만들고 다음 코드를 입력한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>파일 업로드 테스트</title>
</head>
<body>
    <h1>파일 업로드</h1>
    <br>
    <form method="post" enctype="multipart/form-data" action="/process/photo">
        <table>
            <tr>
                <td><label>파일</label></td>
                <td><input type="file" name="photo"></td>
            </tr>
        </table>
        <input type="submit" value="업로드" name="submit">
    </form>
</body>
</html>
cs

<form> 태그 안에 들어가는 <input> 태그의 type 속성 값으로 file을 입력하면 파일을 선택하여 멀티파트 포맷으로 서버에 업로드를 요청할 수 있다. <form>태그의 method 속성 값은 post 로 설정하고 enctype 속성 값은 multipart/form-data 로 설정한다. 요청할 URL 정보인 /process/photo 는 action 속성의 값으로 넣어준다.

 

이제 사용자가 업로드한 사진 파일을 받아서 처리할 수 있도록 다음과 같이 /process/photo 패스를 라우팅하는 함수를 만들어준다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 사진 업로드하는 기능 라우팅 함수
router.route('/process/photo').post(upload.array('photo',1),function(req,res){
    
    try{
        var files = req.files;
        
        console.dir('#==== 업로드된 첫번째 파일 정보 ====#');
        console.dir(req.files[0]);
        console.dir('#=====#');
        
        // 현재의 파일 정보를 저장할 변수 선언
        var originalname = '',
            filename = '',
            mimetype= '',
            size = 0;
        
            if(Array.isArray(files)){
                console.log("배열에 들어있는 파일의 갯수 : %d",files.length);
                for(var index = 0; index < files.length; index++){
                    originalname = files[index].originalname;
                    filename = files[index].filename;
                    mimetype = files[index].mimetype;
                    size = files[index].size;
                }
            }else{
                // 배열에 들어가 있지 않은 경우 (현재 설정에서는 해당 없음)
                console.log('파일 갯수 : 1');
                
                originalname = files[index].originalname;
                filename = files[index].filename;
                mimetype = files[index].mimetype;
                size = files[index].size;
                
            }
        console.log('현재 파일 정보 : ' + originalname + ', ' + filename + ', ' + mimetype + ', ' + size);
        
        //클라이언트에 응답 전송
        res.writeHead('200', {'Content-Type':'text/html;charset=utf8'});
        res.write('<h3>파일 업로드 성공</h3>');
        res.write('<hr/>');
        res.write('<p>원본 파일 이름 : ' + originalname + '-> 저장 파일명 ' + filename + '</p>');
        res.write('<p>MIME TYPE : ' + mimetype + '</p>');
        res.write('<p>파일 크기 : ' + size + '</p>');
        res.end();
        
    }
    catch(err){
        console.dir(err.stack);
    }
});
cs

코드가 제법 길지만 실제로는 파일의 정보를 확인하고 클라이언트에 응답을 보낸느 것이 전부이다.

업로드한 파일의 정보를 확인할 때는 req.files 배열에 들어있는 원소들을 참조한다.

 

파일을 업로드했을 때 업로드한 파일의 정보는 배열 객체로 저장된다. 여기서는 for문으로 배열 객체의 요소들인 파일 이름이나 크기를 하나씩 확인한다.

originalname 속성은 클라이언트에서 보낼 때의 원본 파일이며, filename은 서버에 저장될 파일 이름이다.

업로드된 파일이 어떤 MIME TYPE 으로 전달된 것인지 mimetype 속성으로 확인할 수 있으며, 파일 크기는 size 속성으로 확인할 수 있다.

 

이제 서버를 실행한 후 브라우저를 열어서 해당 주소를 요청한다.

그러면 파일을 업로드하기 위한 웹 문서가 표시된다.

 

파일 업로드를 위한 웹 문서
업로드한 후 보이는 파일 정보

서버에서는 업로드가 성공했을 때 파일 정보를 확인하고 응답을 보내도록 설정되어있다.

따라서, 응답 웹 문서에는 업로드한 파일의 정보가 함께 표시되어있다.

 

서버의 콘솔 창을 보면 클라이언트에서 파일을 업로드 했을 때의 과정과 함께 업로드한 파일이 서버의 [uploads] 폴더에 어떤 이름으로 저장되어있는지 알 수 있는 로그가 출력된다.

 

업로드한 후 서버 쪽에 보이는 콘솔 창의 로그 정보

 

이미지 파일을 업로드하는 과정을 정리하면 다음과같다.

 

이미지 파일을 업로드하는 과정

웹 브라우저에서 이미지 파일을 선택하고 [업로드] 버튼을 눌러 웹 서버로 요청하면 업로드 기능을 담당하는 multer 미들웨어를 거쳐 라우팅 함수 쪽으로 전달된다.

이 함수에서는 파일 이름을 변경하여 [uploads] 폴더에 저장된다. 이 과정에서 확인된 정보는 응답으로 보내지므로 웹 브라우저에서 조회하는 응답 웹 문서에서 업로드 결과를 볼 수 있다.

 

이제 이미지 파일을 웹 서버에 업로드할 수 있게 되었다.

만약, 쇼핑몰 사이트를 운영하는 판매자라고 가정해본다면, 판매자인 우리가 상품 정보를 올렸을 때, 일반 사용자가 업로드된 상품 정보를 조회할 수 있는 기능을 만들고 싶다면 지금까지 익힌 업로드 예제를 활용하면 충분히 해결할 수 있다.

 

 

Comments