busboy と fsモジュールの微妙な関係性
kirisima部活やめるってよ
皆さんいかがお過ごしでしょうか。NodeJSをつかった開発では画像ファイルなどの送信をbusboyで受け取って、fsでどこかに一旦保存して、storageにあげるみたいな処理をすることが多いのじゃないかなーと思います。僕もその一人です。しかしながらこの間、こんな相思相愛の二人の微妙な関係性をみつけてしまいました。しかしながら、なかなかに言及しづらい部分なのか、あまり記事がなかったので共有いたします!!
ええ、kirisima部活やめるん?マジで?
さて、起きた事象を以下に書き下してみます。最も困ったことはこれが不定期に起きていたことでした。
busboyでContent-type: multipart/form-dataなHTTPリクエストを受け取る
busboy.on('file', func(){})のfunc内でfs.createWriteStreamを用いて、指定の場所に一意のファイルネームをもつjpegファイルを作成
busboy.on('end' func(){})でbusboyの処理を終了
次の処理にてpath/一意のファイルネーム.jpegを見に行くと、ファイルサイズが0
ファイルサイズが0の場合はエラーで止まるようにしていた。
おい、なんだそれ!?!?しかも、これが不定期に起きているだと!?!?!?
おい、kirisima聞いたぞ〜、なんでなん?
僕はサバサバ系なので結論から言ってしまうと、fs.createWriteStreamがpath/一意のファイルネーム.jpegに書き込んでいる最中に、次の処理にてpath/一意のファイルネーム.jpegを見に行ってしまっていたことがでした。
つまり、busboy.on('end' func(){})とwritableなstrema.on('end', func(){})の両方を確認してから、次の処理に進む必要があったわけなんですね。
みなさんも、ちゃんとbusboyのendイベントとfsのendイベントの両方を待つようにしましょうね!!
やめても、kirisimaはkirisimaだから、これからもよろしくな!!
じゃあ、両方まてば終わりじゃん!!というわけなんですが、実際にそのようなコードを書くと大体以下のような感じになりました。雰囲気が伝われば十分なので、ちょっと雑です。
コード1
let writable_stream = '';
busboy.on('file', (file, etc...)=>{
writable_stream = fs.createWriteStream(filepath);
file.pipe(writable_stream)
})
const await_busboy_and_fs = new Promise((resolve, reject) => {
busboy.on('end', () => { return resolve(); })
}).then (() => {
return new Promise((resolve, reject) => {
writable_stream.on('end', () => {return resolve();}
})
});
await Promise.all([await_busboy_and_fs]);
これで、どちらのendイベントも待てるようになったわけですが、こちらの欠点としては、fsイベントのendイベントが受け取れない可能性があるということです。なぜなら、busboy.on('end')の処理が終わってからしか、writable_stream.on('end')を受け取れないからです。
なので本当は以下のようにかきたかったです。
コード2
let writable_stream = '';
busboy.on('file', (file, etc...)=>{
writable_stream = fs.createWriteStream(filepath);
file.pipe(writable_stream)
})
const await_busboy = new Promise((resolve, reject) => {
busboy.on('end', () => { return resolve(); })
});
const await_fs = new Promise((resolve, reject) => {
writable_stream.on('end', () => { return resolve(); })
});
await Promise.all([await_busboy, await_fs]);
しかしながら、これだとエラーがでます。なぜなら、busboy.on('file')内で初めて、writableなstreamを生成しているからです。なので確実にbusboy.on('file')内の処理を通ったあとに、writable_stream.on('end')は書かないといけないという制約が爆誕しているのです。
そう、これが誰も言及したがらない、busboyとfsモジュールの微妙な関係ということです。
kirisimaのその後
じゃあ結局どうしたのかといえば、コード1で行いました。理由としては以下の2点です
・実行ログをみていると、改修入れる前からbusboyのendイベントが常に先に来ていた。
・busboy('end')内ではほぼ処理をしていない。
ということで、コード1でも成立しました。
しかしながら、個人的には画像データをbase64でエンコードしてjsonにつめてしまうというのが良いと思います。ただ、そうすると送信するデータ量が増えてしまうはずなのでそことの相談ですね。
Discussion