Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RemixとCloudflare Stack におけるFile Upload

Ossamoon
September 25, 2024

RemixとCloudflare Stack におけるFile Upload

Ossamoon

September 25, 2024
Tweet

Other Decks in Programming

Transcript

  1. File Uploadに求めること - 認証・認可 - ユーザーを認証したい - アップロード上限を超えていないかをチェックしたい - データベースの更新

    - アップロードした音源に関する情報をデータベースに保存したい - アップロードに失敗した場合はデータベースの更新も失敗して欲しい( Atomicity: 原子性) - Cloudflareの制約を満たす - 特にCloudflare Workersは実行時間やメモリの制約が厳しいため、考慮する必要がある
  2. RemixのFile Uploadに関するAPI - unstable_parseMultipartFormData - unstable_createMemoryUploadHandler - unstable_createFileUploadHandler - Node.jsでのみ動作

    - unstable_composeUploadHandlers - APIリファレンスにも載っていない内部的な API すべてmultipart/form-dataを使う想定のAPI 今回は この2つを利用 ← ←
  3. multipart/form-dataとは - HTTPに定義されている仕様 - formに入力したテキストと ファイルのバイナリを 同時に送信できる POST /upload HTTP/1.1

    Host: example.com Content-Type: multipart/form-data; boundary=----- Content-Length: 1024 ----- Content-Disposition: form-data; name="field1" value1 ----- Content-Disposition: form-data; name="field2" value2 ----- Content-Disposition: form-data; name="file"; filename="example.jpg" Content-Type: image/jpeg (...ファイルのバイナリデータ...) -----
  4. APIの使い方(action) const uploadHandler = unstable_createMemoryUploadHandler({ maxPartSize: 5 * 1024 *

    1024, // これを超えるサイズのファイルは扱えない }); const formData = await unstable_parseMultipartFormData(request, uploadHandler); const title = formData.get("title") as string; const audioFile = formData.get("audiofile") as File; 1. unstable_createMemoryUploadHandler で uploadHandler を作成 2. unstable_parseMultipartFormData で formDataを作成 3. formDataから必要なデータを取得
  5. export async function action({ request, context }: ActionFunctionArgs) { //

    認証 const user = await authenticator.isAuthenticated(request); // FormDataを取得 const uploadHandler = unstable_createMemoryUploadHandler({ maxPartSize: 5 * 1024 * 1024, // 5MB }); const formData = await unstable_parseMultipartFormData( request, uploadHandler ); const title = String(formData.get("title")); const audioFile = formData.get("audiofile") as File; // R2にファイルをアップロード const { R2 } = context.cloudflare.env; const object_key = crypto.randomUUID(); const upload_info = await R2.put(object_key, await audioFile.arrayBuffer(), { httpMetadata: { contentType: audioFile.type, }, }); // TrackをDBに追加 const track = await db.track.create(object_key, title, user.id); redirect("tracks"); } 認証とDB更新 も含めた コードの全体像
  6. APIの使い方(component) 1. <Form>のencTypeにmultipart/form-dataを指定 2. あとは普通のformと同様に書く <Form method="post" encType="multipart/form-data"> <label htmlFor="title">タイトル</label>

    <input id="title" type="text" name="title" /> <label htmlFor="audiofile">音源ファイル</label> <input id="audiofile" accept="audio/*" type="file" name="audiofile" /> <button type="submit">送信</button> </Form>
  7. multipart/form-dataのメリット・デメリット 👍 実装がシンプル 👍 認証・認可をaction内で処理できる 👍 DB更新も同じaction内で記述することができ る 👎 Workerの実行時間・使用メモリがファイルサ

    イズに大きく依存する 👎 Multipart uploadやprogressの表示といった ユーザー体験の最適化が実現しにくい FileUploadのもう一つの実装方法である 署名付きURLも検討したい
  8. 署名付きURLの流れ 1. クライアントがWorkerに署名付き URLをリクエスト 2. Workerが認証 3. Workerがデータベースに新しいレ コードを作成 4.

    Workerが署名付きURLを返す 5. クライアントが署名付きURLを用 いてR2にファイルをアップロード 6. クライアントがWorkerにアップ ロード完了を伝える 7. Workerがデータベースに追加し たレコードを更新 8. (定期実行) ファイルアップロード に失敗した分のデータを削除
  9. さらなるユーザー体験の向上へ - Multipart upload - ファイルをいくつかの partに分割してアップロードする - 並列化による最適化や、ネットワーク切断からの回復に貢献する -

    大容量(だいたい100MB以上)のファイルアップロードによく使われる - Progressの表示 - ファイルの何%がアップロードできたかを表示したい - fetchのbodyにReadbleStreamを指定することで、経過を観察することができる - 上記機能はChromeでのみ利用可能 - 他のブラウザもサポートしたい場合、 XMLHttpRequestやaxiosを利用する必要がある これらはRemixの<Form>と組み合わせるのが難しいため、 署名付きURLと組み合わせて使う方がメリットを享受しやすい