multipart/form-data(バイナリ)とbase64の比較、どっちがいいのか?

覚書です。

multipart/form-data(バイナリ)とbase64の比較、どっちがいいのか?

multipart/form-dataはデータ送信の構造を指し、Base64は画像データのエンコード方法を指します。したがって、multipartの文脈でBase64という言葉を使うことはある気がします。

Base64 エンコードの特徴

テキスト化

Base64 はバイナリデータをテキスト形式に変換します。これにより、JSON のようなテキストベースのデータ構造に直接組み込むことができ、HTTPリクエストのボディとして扱いやすくなります。

互換性

既存の実装や API の仕様が base64 を前提としている場合、そのまま使うと既存のコードとの互換性が保たれます。これにより、変更による不具合やミスのリスクが少なくなります。

オーバーヘッド

一方で、base64 エンコードすると元のデータサイズが約 33% 増加するというデメリットがあります。

multipart/form-data の特徴

バイナリ送信

multipart/form-data は、ファイルなどのバイナリデータをそのまま分割して送信する形式です。エンコードのオーバーヘッドがなく、データサイズがそのままで済むため、大容量ファイルの送信時に有利です。

柔軟性

複数のフィールド(テキストデータやファイルデータ)を同時に送信できるので、複雑なフォームデータのやり取りに適しています。

実装の複雑性

ただし、multipart/form-data を扱うためには、リクエストの境界文字列やパートごとのヘッダーなど、細かい設定が必要になるため、実装がやや複雑になりがちです。また、API 側もこの形式での受信に対応していなければなりません。

Ensure the POST is a multipart/form-data request. Either upload the raw binary (media parameter) of the file, or its base64-encoded contents (media_data parameter). Use raw binary when possible, because base64 encoding results in larger file sizes

https://developer.x.com/en/docs/x-api/v1/media/upload-media/api-reference/post-media-upload

X APIではbase64はサイズが大きくなると説明されています。

Webの口コミをみると、意見はわれるようです。

multipart/form-data(バイナリ)のサンプルコード

boundaryは乱数、PNGはbase64にします。

"use strict";

/*
 * Minimal multipart/form-data sample code
 * Now with a random boundary and base64 dummy data for the file.
 */

(async () => {
  // 1) boundary文字列の用意
  //    この文字列は区切りとして機能し、フォームデータの開始・終了を分かりやすくするためのものです。
  // 衝突しにくいようランダムに生成することも多いです。
  const boundary = "----Boundary" + Math.random().toString(36).slice(2);

  // 2) Prepare base64 data for the file (dummy)
  //    実際には画像などをBase64化した文字列をここに入れてください。
  const base64DummyData = "iVBORw0KGgoAAAANSUhEUgAAAAUA...";

  // 3) Build request body
  //    boundaryを使って区切りを明示的に入れながら、テキストデータやファイルデータを組み立てます。
  //    \r\n は改行を意味しています(Windows系の改行コードですが、HTTPヘッダなどでは一般的に使われます)。
  //    boundaryによる区切りと、Content-Dispositionなどのヘッダ情報を手動で書いています。
  const body =
    `--${boundary}\r\n` +
    `Content-Disposition: form-data; name="username"\r\n\r\n` +
    `sampleUser\r\n` +
    `--${boundary}\r\n` +
    `Content-Disposition: form-data; name="avatar"; filename="test.png"\r\n` +
    `Content-Type: image/png\r\n\r\n` +
    base64DummyData + // Base64でエンコードされたダミー文字列
    `\r\n` +
    `--${boundary}--\r\n`;

  // 4) Send POST request using Fetch API
  //    multipart/form-data; boundary=<boundary> を明示し、組み立てた body を送信します。
  const response = await fetch("/upload", {
    method: "POST",
    headers: {
      "Content-Type": `multipart/form-data; boundary=${boundary}`
    },
    body: body
  });

  // 5) Print out the result from server
  console.log("Upload result:", await response.text());
})();


multipart/form-data で区切るための boundary(境界文字列)を設定しています。実際は好きな文字列でOKですが、衝突しにくいようランダムに生成することも多いです。

// 実行結果例
Math.random()                    // 0.7123456789
Math.random().toString(16)       // "0.b67d3"
Math.random().toString(16).slice(2)  // "b67d3"

Build request bodyのフォーマットはそういうものだと覚えるとよいでしょう。

  • 文字列で手動作成する場合、以下のような書式を意識してください。
  • –<boundary>\r\n で始まり、Content-Disposition: form-data; name=”フィールド名” やファイルの情報を記述します。
  • データを入れたら改行し、次のフィールドに移ります。
  • 最後は –<boundary>–\r\n としてフォームデータの終了を明示します。
  • テキストデータとファイルをまとめて送れるのが multipart/form-data のメリットです。
  • 実際のファイルバイナリを扱う場合は、File オブジェクトやBlob、またはArrayBufferなどを使ってバイナリデータを構築し、文字列連結ではなくUint8Arrayなどで連結する必要がある点に注意してください。

既存のコードをどう書き換えていけばいいのかという覚書です。

  videoInitOption = {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + oauth2Service.getAccessToken(),
      'Content-Type': 'application/json'
    },
    payload: JSON.stringify({
      command: 'INIT',
      media_type: 'video/mp4',
      media_category: 'tweet_video',
      total_bytes: videoFileSize
    }),
    muteHttpExceptions: true
  };

このように書き換えます。

  const boundary = '----WebKitFormBoundary' + Math.random().toString(16).slice(2);
  const initPayload =
    '--' + boundary + '\r\n' +
    'Content-Disposition: form-data; name="command"\r\n\r\n' +
    'INIT\r\n' +
    '--' + boundary + '\r\n' +
    'Content-Disposition: form-data; name="media_type"\r\n\r\n' +
    'video/mp4\r\n' +
    '--' + boundary + '\r\n' +
    'Content-Disposition: form-data; name="media_category"\r\n\r\n' +
    'tweet_video\r\n' +
    '--' + boundary + '\r\n' +
    'Content-Disposition: form-data; name="total_bytes"\r\n\r\n' +
    videoFileSize + '\r\n' +
    '--' + boundary + '--'; // Important to close boundary

  videoInitOption = {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + oauth2Service.getAccessToken(),
      'Content-Type': 'multipart/form-data; boundary=' + boundary
    },
    payload: initPayload,
    muteHttpExceptions: true
  };

GASとGoogleドライブのAPIと画像データ

  • Google APIなしにGoogleドライブにアクセスすることはできるよう!同じGoogleのサービスだから特別にAPIなしにということでしょうか。アップロードやダウンロードができます。(ただし、Google Picker APIを使うためにはやはりGoogleドライブのAPIが必要で、できることは限定されています。ユーザーがGUI上でファイル選択の操作できない)
  • Googleドライブを画像置き場として使った場合、Googleの画像Urlは拡張子がつかない形式です。そのあと、使おうとするときいろいろまずいようで一工夫必要そうです。

Googleドライブの画像を使うにあたり、3パターンほどやりくちがありそう!

  • https://drive.google.com/uc?export=view&id=YOUR_FILE_IDというURLに変える方法!この方法は簡単ですが、Googleの正式仕様ということではないっぽい。
  • Blobデータを直接利用する。バイナリデータでエンコードをしなくてもいいけど、ちょっとややこい。
  • Base64エンコードを利用する。Base64エンコードはよく使われますが、バイナリデータよりも画像が約1/3大きくなるため、データ量が増えます。

個人的にややこいBlobデータを使いました。どの方法もGoogle APIを使うことは回避できます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする