node.jsのいろいろなモジュール5 – node-formidableでアップロード

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ファイルアップロード用モジュール

ノンブロッキングIO環境であるnode.jsに向いた機能の1つに、ファイルアップロードがあります。
いままでのWEBアプリケーションではファイルアップロードが完了するまで待たされていたり、負荷が大きかったりすることも
あったと思いますが、node.jsを使用すれば、高パフォーマンスのアップロード処理を簡単に記述することができます。
今回紹介するnode-formidableモジュール ※1は画像や動画ファイルのアップロードやエンコードにフォーカスして開発されました。
このモジュールの作者はTransloadit※2というWebサービスのアップロード処理をまるごと肩代わりするサービスも提供しており、
とくにGB単位の巨大なファイルを扱う際にはこのモジュールが役に立つかもしれません。

node-formidableモジュールの主な特徴は以下のとおりです。

  • 高速(〜500mb/sec)、non-buffering multipartパーサ
  • 自動でディスクへアップロードしたファイルを書き込み
  • 少ないメモリで動作する
  • エラー処理が簡単に記述可能
  • テストカバレッジ率が高い

低コストで高パフォーマンス、シンプルで記述しやすいという特徴ですね。

サンプルを動かしてみる

ではモジュールをインストールしてサンプルを動かしてみましょう。
今回使用した環境は下記のとおりです。

  • OS: Mac OS X 10.7.1
  • Node.js: v0.4.11
    • まずはnode-formidableモジュールをインストールします。

      % npm install formidable@latest
      

      つぎに公開されているサンプルを元に、jsファイルを作成します。
      サンプルではアップロードするファイルを指定し(複数可能)、指定したディレクトリにアップロードします。
      これがサンプルプログラムの全文です.

      //app.js
      var http = require('http'),
          util = require('util'),
          formidable = require('formidable'),
          server;
      
      //ファイル保存場所
      var TEST_TMP="/tmp";
      //ポート番号
      var TEST_PORT=3000;
      
      
      server = http.createServer(function(req, res) {
        if (req.url == '/') {
          res.writeHead(200, {'content-type': 'text/html'});
          res.end(
            '<form action="/upload" enctype="multipart/form-data" method="post">'+
            '<input type="text" name="title"><br>'+
            '<input type="file" name="upload" multiple="multiple"><br>'+
            '<input type="submit" value="Upload">'+
            '</form>'
          );
        } else if (req.url == '/upload') {
          var form = new formidable.IncomingForm(),
              files = [],
              fields = [];
          
          form.uploadDir = TEST_TMP;
      
          form
            .on('field', function(field, value) {
              console.log(field, value);
              fields.push([field, value]);
            })
            .on('file', function(field, file) {
              console.log(field, file);
              files.push([field, file]);
            })
            .on('end', function() {
              console.log('-> upload done');
              res.writeHead(200, {'content-type': 'text/plain'});
              res.write('received fields:\n\n '+util.inspect(fields));
              res.write('\n\n');
              res.end('received files:\n\n '+util.inspect(files));
            });
          form.parse(req);
        } else {
          res.writeHead(404, {'content-type': 'text/plain'});
          res.end('404');
        }
      });
      server.listen(TEST_PORT);
      
      console.log('listening on http://localhost:'+TEST_PORT+'/');
      
      

      ではそれぞれ解説をしていきます。

      最初に必要モジュールのrequireしています。
      TEST_TMPにアップロードされるファイルのパス、TEST_PORTに起動するポート番号を指定しています。

      //app.js
      var http = require('http'),
          util = require('util'),
          formidable = require('formidable'),
          server;
      
      //ファイル保存場所
      var TEST_TMP="/tmp";
      //ポート番号
      var TEST_PORT=3000;
      

      次にリクエストをうけた際のレスポンスを作成しています。
      ここでは単にhtmlとしてアップロード用のフォームを返しています。

      server = http.createServer(function(req, res) {
        if (req.url == '/') {
          res.writeHead(200, {'content-type': 'text/html'});
          res.end(
            '<form action="/upload" enctype="multipart/form-data" method="post">'+
            '<input type="text" name="title"><br>'+
            '<input type="file" name="upload" multiple="multiple"><br>'+
            '<input type="submit" value="Upload">'+
            '</form>'
          );
      

      multiple="multiple"を指定して、複数ファイル指定できるようにしています。

      /uploadにリクエストがくると、アップロード処理をしています。

         } else if (req.url == '/upload') {
          var form = new formidable.IncomingForm(),
              files = [],
              fields = [];
          
          form.uploadDir = TEST_TMP;
      
          form
            .on('field', function(field, value) {
              console.log(field, value);
              fields.push([field, value]);
            })
            .on('file', function(field, file) {
              console.log(field, file);
              files.push([field, file]);
            })
            .on('end', function() {
              console.log('-> upload done');
              res.writeHead(200, {'content-type': 'text/plain'});
              res.write('received fields:\n\n '+util.inspect(fields));
              res.write('\n\n');
              res.end('received files:\n\n '+util.inspect(files));
            });
          form.parse(req);
      

      new formidable.IncomingForm()で作成したオブジェクトに対して、「file」「field」「end」イベントを定義しています。
      ファイルを受信した時、フィールドと値のペアを受信した時、アップロードしたファイルがディスクへコピーし終わった時に実行されます。

      最後に指定外のリクエスト時のエラー処理を記述し、TEST_PORTで指定したポート番号でサーバーを起動しています。

        } else {
          res.writeHead(404, {'content-type': 'text/plain'});
          res.end('404');
        }
      });
      server.listen(TEST_PORT);
      console.log('listening on http://localhost:'+TEST_PORT+'/');
      

      では実際に動かしてみましょう。

      % node app.js
      listening on http://localhost:3000/
      

      http://localhost:3000にアクセスし、ファイルを指定してアップロードしてみます。
      私の環境でファイルを2つ指定してアップロードすると、コンソールに下記のような表示がされ、 /tmpディレクトリにファイルがアップロードされました。

      title 
      upload { size: 510,
        path: '/tmp/00ebe21416af40048a30106963712c63',
        name: 'test.txt',
        type: 'application/octet-stream',
        lastModifiedDate: Tue, 20 Sep 2011 04:40:29 GMT,
        ・・・・
        mime: [Getter] }
      upload { size: 252,
        path: '/tmp/77aa1bbb5d657463172131514b0ba769',
        name: 'test.doc',
        type: 'application/octet-stream',
        lastModifiedDate: Tue, 20 Sep 2011 04:40:29 GMT,
        ・・・・
        mime: [Getter] }
      -> upload done
      

      それぞれ、pathにアップロードされた場所とその名前、nameにアップロード時のファイル名が出力されています。
      実際にはこれらファイル名とパス名をどこかで紐付けておく必要があるでしょう。

      まとめ

      今回はnode-formidableモジュールをご紹介しました。
      アップロードするプログラムがとても簡単に記述できたと思います。
      また、progressイベントを使用すれば、アップロードの進捗状況を得ることもできます。
      このモジュールを使用すれば使いやすいアップローダーが手軽に作れそうですね。

      参考サイトなど