ファイルアップロード
ここでは画像やPDFなどの文書ファイルをアップロードし、別のWebページで画像を表示するのに使ったり、別のユーザにアップロードした文書ファイルを見せたりダウンロードさせたりする用途を考える。
ExcelやCSVなどのデータをシステムに取り込むためのアップロードに関しては、別途記す。(至高のファイルアップロード みたいなやつ) 設計のポイント
アップロードさせるファイルの種類
ファイルアップロードダイアログで拡張子制限できる。
アップロード容量制限
アップロードはネットワーク帯域を使うので、DoS攻撃を仕掛けやすいポイントでもある。必ず上限サイズを決めて、それ以上のアップロードリクエストは中断するように設計する。
ウィルスチェック
原則として、インターネットのシステムであろうがイントラネットのシステムであろうが、どんな類のファイルアップロードも、それを別のユーザが直接ダウンロードなどできるものに関しては、アップロードファイル保存前に、ウィルスチェックを実行する。
プレビュー
アップロードしたファイルを確定前にプレビューさせてミスを防ぎたい、という要件。これも
トランザクション
下記参照
トランザクション
他のフォームの入力と整合性をとってファイルをアップロードする場合、フォームの入力内容と同一トランザクションにする必要がある。
Disk fullなどでファイルのアップロードのみ失敗した、またはフォーム入力内容のRDBへの保存だけが失敗した場合、不整合な状態が発生する。SQLアンチパターンでいうところの「ファントムファイル」である。ファントムファイルの解消方法として、RDBのテーブルにBLOBで保存する、やDisk fullを起こさないS3に保存するなどが挙げられている。
だが、BLOBに置くのはトランザクション一貫性を保つためにはよいが、読み出しのためには性能ボトルネックを避けるため(たとえば画像表示するためにデータベースアクセス発生するようになるので)、別のストレージに置き直すソリューションが必要になるだろう。
ユースケースを考えれば、たいていの場合、Gmailがそうであるように、ファイルのアップロードとフォームの入力は別トランザクションでよいのではないだろうか? これは次節のソリューションで述べる。
ちなみに、画像をS3などにおいた場合の権限制御は、トークンベースで実装すると簡単で速い。
ソリューション
非同期アップロード
https://gyazo.com/b07e7c6c1bf600bd3540f84bad47b73f
Gmailではファイル添付すると、単体でまずサーバにファイルアップロードされる(25MBを越えるファイルはGoogle Driveにアップロード)。この途中または、アップロード完了時に送信ボタンを押すと、ファイルと本文が両方サーバ側に送信完了されたことを条件に、実際にメール送信が行われる。
これを普通のフォーム入力&ファイルアップロードにも適用すると、まずファイルを非同期でアップロードさせ、これが完了するまでは、送信ボタンを非アクティブにしておく。アップロードが完了したら、そのアップロード先のファイルのURLをレスポンスで受け取り、フォームの送信ボタンが押せるようになる。フォームを送信するときに、このファイルのURLも同時に送り、それを同じトランザクションで保存する。
フォーム入力で離脱したらゴミファイルが残るが、次に示すように検疫の仕組みをどうせ作らなくてはならないので、そこで同時に作り込むことになる。
検疫
アンチウィルスを実行するために、一度アップロードされたファイルは検疫のため、通常のアプリケーションとは隔離されたところに置く。フレームワークによってはファイルのサイズによって、ストリーム処理にするか、一時ファイルを作るか選べるものが多いが、この閾値は0に設定し全てを一時ファイル作ってアンチウィルスを実行する検疫の仕組みを作る方のが手堅い。
検疫所に置かれて問題のないファイルは後続の処理によって、正規の置き場所にMoveする。したがって、一定期間以上アクセスされていないファイルは削除するスケジューリングジョブを用意する。
https://gyazo.com/379074a2d59174612e6f799cba6b3a4d