Skip to content

Node.js 0.6 + express 2.5 를 이용한 파일 업로드 서버

2012/03/16

우선 히스토리 입니다.

히스토리를 밝히는 이유는 현재 파일 업로드를 구현하는 방법을  검색하다보면 여러 방법이 나와 있지만 결국 다 같은 모듈(formidable)을 사용하고 있기 때문입니다.

초기에는 multipart를 지원하는  file upload를 처리하기 위해서는 formidable을 사용했었습니다.

그리고 express 에서 더 쉽게 만들기 위해 connect-form가 생겨났습니다.

하지만 connect-form 은 현재 없어졌고 express의 코어 미들웨어인 connect 2.0에서 formidable을 이용해서 mulipart 를 지원하게 됩니다.

그리고 express 2.5.4로 업데이트 되면서 connect 2.0과 호환이 되어 현재는 express 에서 간단하게 구현을 할수 있습니다.

그래서 만약 node.js를 이용한 파일 업로드 구현을 검색한 소스에서 require(‘formidable’) / require(‘connect-form) 또는 req.form.complete 를 소스는 express 2.5이상에서는 맞지 않습니다.

혹시 formidable을 직접 사용하고 싶다면 delete express.bodyParser.parse[‘multipart/form-data’]; 를 한 후에 사용해야 합니다.

/**
 * Module dependencies.
 */

var express = require('express')
    , routes = require('./routes');

var app = module.exports = express.createServer();

// Configuration

app.configure(function(){
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  //jade에서 extends와 block을 사용하기 위해 layout을 false로 설정해 줍니다.
  app.set('view options', { layout: false });
  //올라오는 파일 용량을 제한합니다.
  app.use(express.limit('5mb'));
  //기본 temp 폴더를 지정하기 위해 uploadDir 을 지정합니다.
  app.use(express.bodyParser({uploadDir: __dirname + '/tmp'}));  
  app.use(express.methodOverride());
  //기본 로그를 찍기위해 사용합니다.(아파치 로그처럼 요청/응답에 대한 로그를 보여줍니다.)
  app.use(express.logger({ buffer: 5000}));
  //로그에 클라이언트의 favicon 요청을 무시하도록 해줍니다.
  app.use(express.favicon());
  app.use(app.router);
  //express에서 이미지 등 static 파일들을 제공해주기 위한 폴더 설정
  app.use(express.static(__dirname + '/public'));
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
  app.use(express.errorHandler());
});

// Routes

app.get('/', routes.index);
//파일을 올리기 위한 form 페이지
app.get('/test', routes.test);
//업로드된 파일을 받고 결과를 보여주기 위한 페이지
app.post('/upload', routes.upload);

app.listen(process.env.C9_PORT);
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);

일부 express 를 사용하지 않는 예제에서는 업로드된 파일을 다시 보여주기 위해서는 업로드된 파일을 fs를 이용해 파일을 읽어서 respose에 전달해서 보여주는 복잡한 과정이 있습니다.
하지만 express 를 이용하면 static 파일을 저장할 폴더를 지정해 주고 파일들을 옮겨 놓으면 클라이언트에서 요청시 알아서 전달해줍니다.

//views/test.jade
extends layout //layout.jade를 extends합니다.

block content //layout.jade의 block content를 아래의 내용으로 대체합니다.
    form(action='/upload', method='post', enctype='multipart/form-data')
        input(type='file', name='imgs', multiple="") //여러파일을 한번에 올릴수 있도록 multiple을 추가합니다.
        input(type='submit', value='Upload')
//routes/index.js
exports.test = function(req, res){
    res.render('test', { title: 'Upload Test'});
};

exports.upload = function(req, res){    
    console.log('-> upload was called\n\n');
    console.log('-> ' +  util.inspect(req.files));        
    var images = [];
    var isImage = false;
    //express.bodyParser 는 multipart 를 위해 req.files를 만들어 줍니다.
    //클라이언트에서 mutiple로 요청시 여러파일이 올라오기 때문에 array인지 확인해 줍니다.
    if (Array.isArray(req.files.imgs)){
        req.files.imgs.forEach(function(image){
            var kb = image.size / 1024 | 0;
            //파일의 타입을 확인합니다.
            isImage = checkType(image);
            images.push({name: image.name, size: kb, isImage: isImage});
            //임시명으로 만들어진 파일의 이름을 바꿉니다.
            renameImg(image);
            console.log('->> isImage: ' + isImage );
        });  
    }else{
        var image = req.files.imgs;
        var kb = image.size / 1024 | 0;
        isImage = checkType(image);
        images.push({name: image.name, size: kb, isImage: isImage});
        renameImg(image);
        console.log('->> isImage: ' + isImage );
    }
    
    console.log('->> render');
    res.render('show', { title: 'Show'
                            ,images: images
    });
};

function checkType(image){
    var isImage = false;
    console.log('->> image.type.indexOf : ' + image.type.indexOf('image'));
    //파일의 타입 비교
    if(image.type.indexOf('image') > -1){
        console.log('->>> req.files.img is img');
        isImage = true;
    }else{
        console.log('->>> req.files.img is not img');
        isImage = false;
    }
    return isImage;
}

function renameImg(image){
    var tmp_path = image.path;
    var target_path = './public/upload/' + image.name;
    console.log('->> tmp_path: ' + tmp_path );
    console.log('->> target_path: ' + target_path );
    //tmp_path -> target_path로 이동하면서 파일명을 바꿉니다.        
    fs.rename(tmp_path, target_path, function(err){
        if(err) throw err;
        /* 어떤 예제에서 아래와 같이 tmp_path를 다시 unlink해주지만 이미 rename으로 이동시켰기 때문에 tmp_path가 없다는 오류가 나게 됩니다.
        fs.unlink(tmp_path, function() {
            if (err) throw err;
            res.send('File uploaded to: ' + target_path + ' - ' + req.files.thumbnail.size + ' bytes');
        });*/
        console.log('->> upload done');
    });
}
extends layout

block content
    //jade에서 '-'로 시작하면 code로 인식되며 images.length로 유무를 체크합니다.
    - if(images.length)
    ul
        - images.forEach(function(image){
            li
                img(src='/images/'+image.name)
                p= image.size
        - })

https://github.com/teragoon/node-fileupload 를 통해 확인할수 있습니다.

No comments yet

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중

%d 블로거가 이것을 좋아합니다: