-
Notifications
You must be signed in to change notification settings - Fork 0
MS_UploadTopics
- 戻る(アップロード・ダウンロード)
ファイルのアップロードについての情報を纏めます。
RFC1867 で規定されている模様。
- フォームよるファイルアップロードの仕様
https://www.javadrive.jp/servlet/fileupload_tutorial/index2.html- Form-based File Upload in HTML
https://www.ietf.org/rfc/rfc1867.txt
- Form-based File Upload in HTML
multipart/form-data を使用する。
-
WWW ブラウザの Form からファイルアップロードする一般的方法
-
<INPUT type="file">を使用してアップロードできる。- INPUT type=file-HTMLタグリファレンス
http://www.htmq.com/html/input_file.shtml
- INPUT type=file-HTMLタグリファレンス
- POST メソッドを使用する。
- PUT は使用できない模様(後述)。
補足(RFC 1867 の現況と、
multipart/form-dataの構造): 原文が挙げる
RFC 1867 は 1995 年の実験的仕様で、
**現在の正式な規定は RFC 7578(2015 年)**である
(RFC 2388 を経て置き換えられた)。仕様の中身は概ね同じである。【multipart/form-data の中身】 POST /upload HTTP/1.1 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXyz ------WebKitFormBoundaryXyz Content-Disposition: form-data; name="title" 報告書 ------WebKitFormBoundaryXyz Content-Disposition: form-data; name="file"; filename="report.xlsx" Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet (バイナリ本体) ------WebKitFormBoundaryXyz--要点:
・boundary(区切り文字)で複数のパートを区切る ・各パートに Content-Disposition があり、name と filename を持つ ・【バイナリをそのまま送れる】(Base64 等の変換が不要)★ → application/x-www-form-urlencoded だとサイズが約 3 倍になる
filenameは利用者側から来る値であるため、
絶対に信用してはならない(後述の「現在の実装指針」)。
<input type="file">の現在の属性:<input type="file" name="files" multiple <!-- 複数選択 --> accept=".xlsx,.csv,image/*" <!-- 候補を絞る(強制ではない) --> capture="environment"> <!-- スマホでカメラを起動 -->
acceptはファイル選択ダイアログの絞り込みにすぎず、
検証にはならない(利用者は「すべてのファイル」を選べる)。
PUT メソッドなども使用できるが、
multipart/form-data とは組み合わせられない模様。
(application/x-www-form-urlencoded では動作する)
移行メモ(PUT と multipart は「組み合わせられない」わけではない): 原文の
記述は当時のフレームワーク側の実装事情を指していると読める。
HTTP の仕様上は、PUT でmultipart/form-dataを送っても構わない。【実際の制約】 ・サーバ側フレームワークの多くが、 【POST の multipart しかパースしない】実装だった - PHP の $_FILES は POST のみ - 古い ASP.NET / Java サーブレットも同様 ・したがって「使えない」のは【実装の都合】であって プロトコルの制約ではない 【現在】 ・ASP.NET Core は【PUT でも multipart を読める】 ([FromForm] / Request.Form はメソッドに依存しない) ・とはいえ、慣習として【POST を使う】のが無難POST と PUT の使い分け(原文の参考リンクの主題):
POST PUT 意味 「この下に作る」(サーバが URI を決める) 「この URI にこれを置く」 冪等性 なし(2 回送れば 2 件できる) あり(何回送っても同じ結果)★ 用途 一般的なファイル アップロード リトライしたい転送、S3 / Blob への直接 PUT 【冪等であることの実用的な価値】 通信が切れた際、【安全に再送できる】 → 大容量アップロードのリトライで効く → オブジェクト ストレージへの直接アップロードが PUT なのはこのため ★
-
WebDAV - Wikipedia
https://ja.wikipedia.org/wiki/WebDAV-
ボディ部では、クライアント・サーバ双方とも XML を用いる。
-
HTTP 1.1(のメソッド)に加え
- GET
- HEAD
- POST
- PUT
- DELETE
- OPTIONS
-
次のメソッドが存在する。
- PROPFIND
プロパティの取得 - PROPPATCH
プロパティの変更 - MKCOL
指定した URI の場所に新たな資源を作成する。 - COPY
指定した URI が示す資源およびその属性値を別の URI にコピーする。 - MOVE
指定した URI が示す資源およびその属性値を別の URI に移動する。 - LOCK
指定した URI が示す資源のファイルロックを設定する。
共有ロックと排他ロックの二種類が利用できる。 - UNLOCK
指定した URI が示す資源のロックを解除する。
- PROPFIND
-
補足(WebDAV の現況): 新規のシステムで採用することはほぼないが、
既存環境で遭遇するため知識として要る。【今も残っている場所】 ・SharePoint / OneDrive の「エクスプローラーで開く」 ・IIS の WebDAV 発行 ・NAS の Web アクセス機能 ・Nextcloud / ownCloud 【廃れた理由】 ・プロトコルが重い(XML の往復、LOCK の管理) ・ファイアウォール / プロキシとの相性が悪い ・【ファイル名の文字コード】で日本語が化ける([エンコーディング](MS_Encoding)) ・オブジェクト ストレージ + REST API に置き換わった ★セキュリティ上の注意:
【IIS の WebDAV は、使わないなら無効化する】 ・PUT / DELETE / PROPFIND が意図せず有効になっていると、 【ファイルを書き込まれる】危険がある ・過去に CVE も出ている(バッファ オーバーフロー等) → [IIS、ASP.NETでのHTTPメソッドの制御](MS_HTTPMethodControl) を参照
大量データのアップロードには、以下の方式がある。
- 分割アップロード
- その他の方法
- MTOM
Streaming、MTOM などに対応している
AP サーバは少ないと考えられるため、
分割アップロードが一般的設計と考える。
主に、AP サーバの Streaming 対応の有無によって、
分割アップロードが必要になることがある。
Streaming 対応が無い AP サーバでは、
multipart/form-data を全てメモリに溜め込んでしまうため、
メモリ・リークに陥る。この対応として分割アップロードがある。
移行メモ(「メモリ・リーク」は正確には「メモリ枯渇」): 原文の
**「メモリ・リークに陥る」**という表現は、
厳密にはメモリ リーク(解放漏れ)ではない。【実際に起きること】 ・受信データを全部メモリに載せる ・1GB のファイル × 同時 10 人 = 10GB → OutOfMemoryException / プロセス強制終了 → .NET なら【LOH(Large Object Heap)の断片化】も起こす ★ → 解放漏れではなく【一時的な使用量の過大】 ただし、実務上の症状(サーバが落ちる)は同じである.NET での「メモリに載せてしまう」実装の例:
// ✗ ファイル全体をメモリに読む using var ms = new MemoryStream(); await file.CopyToAsync(ms); var bytes = ms.ToArray(); // ← ここで更にもう 1 部コピーされる // ○ ストリームのまま流す await using var dest = File.Create(path); await file.CopyToAsync(dest); // 既定 81,920 バイトのバッファで転送
-
クライアントを作りこむ。
HTTP クライアントライブラリ(API)を使用して
ファイル分割アップロード処理を行う HTTP クライアントを開発する。 -
JavaScript ライブラリを使用する。
jQuery-File-Upload を使用して、分割アップロードが可能。- jQuery File Uploadを利用した大容量ファイルの
アップロードをChunked uploadで実現する - KayaMemo
http://kayakuguri.github.io/blog/2015/10/01/jquery-chunked-upload/
以下も参考になるが、こちらは、ノーマルな jQuery のサンプルのもよう。
- asp.net MVCとjQueryでファイルアップロードするメモ | リグレックス社員ブログ
http://blog.regrex.jp/2016/06/03/post-498/
- jQuery File Uploadを利用した大容量ファイルの
補足(現在の分割アップロード): 原文の
**「分割アップロードが一般的設計」**という結論は今も正しいが、
手段が変わった。① ブラウザ標準の API で分割できる
// File は Blob なので slice できる(jQuery プラグイン不要) const CHUNK = 5 * 1024 * 1024; // 5MB for (let start = 0; start < file.size; start += CHUNK) { const chunk = file.slice(start, start + CHUNK); await fetch(`/api/upload/${uploadId}?index=${start / CHUNK}`, { method: 'PUT', body: chunk }); } await fetch(`/api/upload/${uploadId}/complete`, { method: 'POST' });② ライブラリを使う
ライブラリ 内容 tus / tus-js-client レジューム可能アップロードの標準的なプロトコル ★ Uppy UI 込み。tus / S3 / Azure に対応 Dropzone.js 手軽。分割にも対応 jQuery-File-Upload アーカイブ済み(原文のもの。新規採用は不可) ③ そもそもアプリ サーバを経由させない(推奨)
【SAS URL / 署名付き URL 方式】 ① ブラウザ → アプリ: 「アップロードしたい」 ② アプリ → ブラウザ: 【期限付きの書き込み URL】を返す ③ ブラウザ → Blob Storage / S3 に【直接】アップロード (SDK が分割・並列・リトライを自動で行う) ④ ブラウザ → アプリ: 「完了した」(Blob 名を通知) ⑤ アプリ: 検証(サイズ、種類、ウイルス スキャン) → アプリのメモリ・帯域・タイムアウトの問題が【まとめて消える】★// ② SAS URL の発行(書き込みのみ・15 分・特定の Blob 名に限定) var sas = blobClient.GenerateSasUri(new BlobSasBuilder( BlobSasPermissions.Write | BlobSasPermissions.Create, DateTimeOffset.UtcNow.AddMinutes(15)) { BlobContainerName = "uploads", BlobName = $"{userId}/{Guid.NewGuid()}" });【SAS 発行時の必須事項】 ・権限を【Write / Create だけ】に絞る(Read や Delete を付けない) ・【有効期限を短く】する ・【Blob 名をサーバ側で決める】(利用者に指定させない)★ → 他人のファイルを上書きされないため
こちらと同様、2GB 以上のファイルを扱えないケースがある。
補足(2GB 制限の現況): 原文が指す 2GB の壁は、
多くが解消したが、一部は残っている。
箇所 当時 現在 Array/byte[]の要素数2GB 上限 gcAllowVeryLargeObjectsで緩和可(要素数上限は残る)MemoryStream2GB 上限 同左(内部が byte[])★ASP.NET(.NET Framework)の要求サイズ maxRequestLength(KB 単位、int)同左 ASP.NET Core の要求サイズ — longなので 2GB 超も可(既定は 30MB)IIS の maxAllowedContentLengthuint(約 4GB)同左 ファイル システム NTFS は問題なし 同左(FAT32 は 4GB 上限) 【結論】 ・ストリームで扱えば 2GB 超も扱える ・【メモリに載せる実装をした瞬間に 2GB の壁に当たる】★ → 分割アップロード/直接アップロードにすれば、そもそも当たらない<!-- .NET Framework:両方の設定が要る(片方だけだと効かない) --> <system.web> <httpRuntime maxRequestLength="2097151" executionTimeout="3600" /> </system.web> <system.webServer><security><requestFiltering> <requestLimits maxAllowedContentLength="2147483648" /> </requestFiltering></security></system.webServer>// ASP.NET Core [RequestSizeLimit(1_073_741_824)] // 1GB [RequestFormLimits(MultipartBodyLengthLimit = 1_073_741_824)] public async Task<IActionResult> Upload() { ... }
MTOM(Message Transmission Optimization Mechanism)
-
Buffer モードと Streaming モードがあり、後者でこの問題を解決できる。
-
AP サーバの Streaming 対応が無い場合も、MTOM で Streaming がサポートされる。
-
JavaScript から MTOM の Web サービスは利用できないので
Web サービス・クライアントの開発が必要になる。 -
MTOM は WCF などを使用した Web サービスで利用可能
(対応可能かどうか?は使用する AP サーバ・フレームワークに依存)。 -
参考
-
大規模データとストリーミング | Microsoft Docs
https://learn.microsoft.com/ja-jp/dotnet/framework/wcf/feature-details/large-data-and-streaming -
WCF: Streaming Large Data Files
https://www.c-sharpcorner.com/uploadfile/afenster/wcf-streaming-large-data-files/
-
補足(MTOM とは何か、そして現況): 名前だけでは分かりにくいので
補っておく。【SOAP の弱点】 SOAP のボディは XML → バイナリを入れるには【Base64 にする】必要がある → サイズが約 4/3 に膨らむ([エンコーディング](MS_Encoding)) → XML パーサがメモリに全部載せる 【MTOM の解】 バイナリ部分を XML の外に出し、 【MIME のパートとして生のまま】送る → サイズが膨らまない → ストリームで処理できる移行メモ(WCF と MTOM の現況):
・【WCF は .NET(Core 系)には移植されなかった】 → クライアント側のみ System.ServiceModel.* で部分的に利用可 → サーバ側は【CoreWCF】(コミュニティ主導)が受け皿 ・SOAP / MTOM 自体、新規採用はほぼない → REST + オブジェクト ストレージ、または gRPC へ原文の「JavaScript から MTOM の Web サービスは利用できない」
という指摘は、SOAP 系が Web フロントエンドと相性が悪いという
一般的な問題を突いており、
これが REST が主流になった理由の 1 つでもある。
補足(アップロード実装の現在の必須事項): 本ページ全体を通した
セキュリティ上の要点をまとめる。すべて実際の事故につながる。① ファイル名を信用しない(最重要)
// ✗ 送られてきた名前でそのまま保存する var path = Path.Combine(uploadDir, file.FileName); // FileName が "..\\..\\web.config" だったら?(パス トラバーサル)★ // ○ 保存名はサーバが決める var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); if (!Allowed.Contains(ext)) return BadRequest(); var stored = $"{Guid.NewGuid():N}{ext}"; var path = Path.Combine(uploadDir, stored); // 元のファイル名は DB のメタデータとして保持する② 拡張子と Content-Type を信用しない
・Content-Type はクライアントが自称するだけ ・拡張子も自由に付けられる → 【中身(マジック ナンバー)を確認する】 → 画像なら、実際にデコードできるか試す → [ウイルススキャン](MS_VirusScanning) を通す③ 保存先を公開ディレクトリにしない
✗ wwwroot/uploads/ に置く → アップロードされた .aspx / .php が【実行される】危険 ★ → 少なくとも実行を無効化する(handlers の削除) ○ ・Web ルート外のディレクトリに置き、 配信は【コントローラ経由】(認可を効かせる) ・またはオブジェクト ストレージに置く(推奨)④ サイズ・件数・拡張子を必ず上限で縛る
・1 ファイルのサイズ、1 リクエストの合計、1 ユーザーの総容量 ・上限がないと【容量枯渇による DoS】になる⑤ 認可を忘れない
・アップロードできるのは誰か ・アップロードしたファイルを取得できるのは誰か → 【推測しにくい名前】は認可の代わりにならない ★
- ファイルのアップロード(
OTR_FileUpload.md)
- ASP.NET Core でのファイルのアップロード
https://learn.microsoft.com/ja-jp/aspnet/core/mvc/models/file-uploads - 共有アクセス署名 (SAS) の概要
https://learn.microsoft.com/ja-jp/azure/storage/common/storage-sas-overview - BlobClient.UploadAsync(分割・並列は SDK が担う)
https://learn.microsoft.com/ja-jp/dotnet/api/azure.storage.blobs.blobclient.uploadasync
Tags: 移行, その他、開発の色々
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。