今回は久しぶりのNodejs関連です。Koa(WAF)でmultipart/form-dataのファイルを保存する方法について。
co-busboyを使ってmultipart/form-dataのハンドリングを行い, Uploadされた画像を保存します。
Environment
ES6で追加された同期処理っぽい処理ができる yield を使うためには, v0.11.xx以降が必要です。
Koa + co-busboy をインストールします。busboyはHTMLフォームデータのための StreamingPasrser で, co-busboyは ES6 の yield 対応版です。
# OS X 10.9.4 using node v0.11.12
$ node -e 'console.log(process.versions.v8)'
3.22.24.19
$ npm install koa koa-route co-busboy
v0.11.12のインストールは以下。
$ git clone git://github.com/creationix/nvm.git ~/.nvm
$ source ~/.nvm/nvm.sh
$ nvm install 0.11.12
$ nvm use 0.11.12
$ node -v
Client
クライアント側のコードです。formで取得した画像データをAjaxでPOSTします。
$.ajax('/upload', {method: 'POST',contentType: false, processData: false,
data: formData , dataType: 'json',
error: function() {
console.log('error')
},
success: function(data) {
alert(data.message)
console.log('success')
}
});
FormDataに画像とメッセージ(Key-Value)をappendして一緒にPOSTします。Bodyは以下のようになります。
-----------------------------9840774291617783656799451725 Content-Disposition: form-data; name="image_0"; filename="sample_pic.jpg" Content-Type: image/jpeg ÿØÿà� JFIF� �� � ��ÿþ�;CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 90 ÿÛ�C� ŸzBÞ• jilÒ¸...
.
.
.
-----------------------------9840774291617783656799451725 Content-Disposition: form-data; name="message" test_msg
Server
ほぼco-busboyのチュートリアルにある通りです。
クライアントから送信されたデータはpart.lengthで0以外の場合,つまりKey-Value形式はkeyがpart[0],valueはpart[1]で受け取れます。
それ以外のStreamはPipeで繋いで,tmp/img に保存しています。
var koa = require('koa');
var parse = require('co-busboy');
var fs = require('fs');
var app = koa();
// POST /upload
app.use(route.post('/upload', function*() {
var parts = parse(this);
var part;
while (part = yield parts) {
if (part.length) {
// arrays are busboy fields
console.log('key: ' + part[0] + " val: " +[part[1]])
} else {
// handle stream
part.pipe(fs.createWriteStream('tmp/img'))
}
}
this.body = {"message": "upload done!"};
}));
簡単な応用例として,DBに直接保存してGETリクエストで直接データを返す方法です。
受け取った画像をMongoDBなりDBにBase64エンコードして保存しておきます。
* Base64はそのアルゴリズム上,ファイルファイズが約30%増えてしまいます
# mongodb
{ "_id" : ObjectId("53e8c3e97151636c2f946f83"), "message" : "test_msg",
"created_at" : ISODate("2014-08-11T13:23:53.124Z"), "img_0" : { "data" : BinData(0,
"/9j/4AAQSkZJRgABAQAAAQABAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgS
lBFRyB2NjIpLCBxdWFsaXR5ID0gOTAK/9sAQwADAgIDAgIDAwMDBAMDBAUIBQUEBAUKBwcGCAwKDAwL
CgsLDQ4SEA0OEQ4LCxAWEBETFBUVFQwPFxgWFBgSFBUU/9sAQwEDBAQFBAUJBQUJFA0LDRQUFBQUFBQ
UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU/8AAEQgBPwHhAwEiAAIRAQ
...
GETリクエストに対して,以下のようにBase64エンコードされたデータをDBから取得して,直接imgタグのsrcに入れることでクライアント側のブラウザでデコードされて表示されます。
// using mongoose
app.use(route.get('/image', function *(next) {
var items = yield sampleImageModel.find({ name: /^test_image/ }).exec();
var dataBase64 = items[0]["img_0"].data
this.body = '';
}))
余計な手間がなくて良いですね。
ファイルIO (fs) は書き込む時は fs.write で, 削除は fs.unlink なのが分かりにくい。ディレクトリ削除は fs.rmdir。
[1] KoaのExamples。
[2] yieldableについて。
[3] mongoose(object modeling tool)の使い方
[4] ここはstdinを使うときのハマりどころについて。
[5] devnullは要らないstreamを/dev/nullで破棄するときに使える。
[6] Nodejs関連の欲しいmoduleはここで大体見つかる