Open5

APIでファイルをアップロードする方法について理解したい

むっくむっく

APIでファイルを送信する方法は主に「multipart/form-data」と「Base64エンコード」の2つ

むっくむっく

multipart/form-dataとは

HTTPリクエストのContent-Typeの1つ
主にフォームからファイルをアップロードする際に使用される

使用例

フォームでユーザー名を入力 &アバター画像を選択する例
このフォーム送信時、ブラウザはContent-Type: multipart/form-dataを使ってリクエストボディを構成する

<form action="/upload" method="POST" enctype="multipart/form-data">
  <input type="text" name="username" />
  <input type="file" name="avatar" />
  <button type="submit">送信</button>
</form>

各パートはboundary(境界線)によって区切られる
boundaryはContent-Typeヘッダーの一部です
下記のboundaryは----WebKitFormBoundary12345

Content-Dispositionはデータについてサーバーに情報を伝えるために使用している

POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary12345

------WebKitFormBoundary12345
Content-Disposition: form-data; name="username"

naoki
------WebKitFormBoundary12345
Content-Disposition: form-data; name="avatar"; filename="profile.png"
Content-Type: image/png

(binary data...)
------WebKitFormBoundary12345--
むっくむっく

NestJSでファイルアップロードAPIを実装してみた(ファイルデータの受け取り側)

@Post('image')
async uploadImage(@Req() req: FastifyRequest): Promise<void> {
  // Fastifyでファイルを取得(filenameやfile(Stream)が含まれる)
  const fileData = await req.file()

  // ファイルの拡張子を抽出するメソッドを実行(profile.pngの場合はpng)
  // ファイルをアップロードする際にファイル形式を明示するため
  const extension = extractExtension(fileData.filename)

  // ファイルのバイナリデータのストリームと拡張子を渡して、画像のアップロード処理を実行
  // ざっくりいうとファイルの実体がfileData.fileである
  await this.xxxService.uploadImage(fileData.file, ext)
}

request.file()の中身

{
  fieldname: 'avator',
  filename: 'profile.png',
  encoding: '7bit',
  mimetype: 'image/png',
  file: ReadableStream
}
むっくむっく

NestJSでファイルアップロードAPIを実装してみた(ファイルデータの送信側)

ストリームはバイナリデータなどのデータを少しずつ順番に処理する仕組み

[一括読み込み(Buffer)]
┌──────────────┐
│ すべてのデータを読み込み │ → 保存・処理
└──────────────┘

    ↓

[ストリーム(Stream)]
┌────┐┌────┐┌────┐┌────┐
│Chunk│→│Chunk│→│Chunk│→│Chunk│→ 保存・処理
└────┘└────┘└────┘└────┘

下記のコードはデータのかたまりを少しずつ順番に送ってファイルをアップロードしている

await s3.upload({
  Bucket: 'my-bucket',
  Key: `images/${uuid()}.${extension}`
  Body: fileData.file, // ストリームを直接渡す
  ContentType: fileData.mimetype,
}).promise()
[アップロード元(サーバ)]
 fileData.file
   ↓
 ┌────────┐
 │ Chunk1 │  →┐
 └────────┘   │
 ┌────────┐   │
 │ Chunk2 │  →├─▶ Amazon S3
 └────────┘   │
 ┌────────┐   │
 │ Chunk3 │  →┘
 └────────┘

[アップロード先(S3バケット)]