Google Drive APIでファイルをアップロード - Multipart/Related
前回に引き続きGoogleDriveAPIのお話です。 今回はファイルの新規アップロードについてです。
createエンドポイント
ファイルを新規にアップロードするにはcreateエンドポイントを使用します。
https://developers.google.com/drive/api/v3/reference/files/create
このエンドポイントが曲者で、アップロード時に3つのアップロードタイプから一つを選んで明示的に指定する必要があります。
現実問題としてメタデータ無しでファイルをアップロードしたいという局面は殆ど無いと思われます。Google Driveではフォルダの階層構造をファイルのメタデータで管理しているため、メタデータ抜きでは親ディレクトリの指定すらできません。
ということで多くの場合においてアップロードタイプとしてmultipartかresumableを指定することになるでしょう。複数回リクエストを投げるよりは一回のリクエストで片付けてしまいたいので、自分の中でmultipartを使ってみようということになりました。ところがこのmultipart、RFC 2387というプロトコルに則ってリクエストを作成する必要があります。日本語の文献もほぼ無いということで素直にRFCを流し読みしました。
RFC 2387 The MIME Multipart/Related Content-type
複数の異なるMimeTypeのデータをまとめてアップロードするための「Multipart/Related」というMimeTypeについてまとめてたものです。あまりしっかりと読めているわけではないですが、要点は以下の通りとなっています。
- Content-TypeでMultipart/Relatedを指定し、同時にBoundary(境界文字列)も指定する。
- リクエストボディは"--"+Boundaryで区切って、異なるデータのリクエストボディを指定する。
- リクエストの終わりは"--"+Boundary+"--"で締める
これだけだと抽象的で少し分かりづらいですが、RFCの原本に載っている例がわかりやすいです。
Content-Type: Multipart/Related; boundary=example-1 start="950120.aaCC@XIson.com"; type="Application/X-FixedRecord" start-info="-o ps"
--example-1 Content-Type: Application/X-FixedRecord Content-ID: <950120.aaCC@XIson.com> 25 10 34 10 25 21 26 10 --example-1 Content-Type: Application/octet-stream Content-Description: The fixed length records Content-Transfer-Encoding: base64 Content-ID: <950120.aaCB@XIson.com> T2xkIE1hY0RvbmFsZCBoYWQgYSBmYXJtCkUgSS BFIEkgTwpBbmQgb24gaGlzIGZhcm0gaGUgaGFk IHNvbWUgZHVja3MKRSBJIEUgSSBPCldpdGggYS BxdWFjayBxdWFjayBoZXJlLAphIHF1YWNrIHF1 YWNrIHRoZXJlLApldmVyeSB3aGVyZSBhIHF1YW NrIHF1YWNrCkUgSSBFIEkgTwo= --example-1--
HTTP本体のリクエストヘッダにはMultipart/Relatedを指定しています。startなどはどのデータから読み込めばいいかを示す情報のようです(それっぽいことが書いてありましたが良く理解できませんでした)。リクエストボディの各セクションでは、それぞれのデータに対して改めてヘッダを定義しています。
javascriptでの実装
Multipart/Relatedのリクエストボディを生成する関数を作成した。この関数でエンコードした文字列をリクエストボディにして、リクエストヘッダにはMultipart/Relatedを指定すれば
/** * 引数で指定したオブジェクトをMultipart/Relatedのリクエストボディに変換 * 以下のような配列を第一引数として指定する必要がある * e.g) * let objects = [ * { header: ['Content-Type': 'text/plane'], body: '内容1' }, * { header: ['Content-Type': 'text/plane'], body: '内容2' }, * ] * * @param {Array} エンコードしたいオブジェクトのリスト * @param {String} 境界文字列 * @returns {String} エンコード済みの文字列 */ function encodeMutipart(objects, separator) { let encoded = ''; for (let o of objects) { encoded += `--${separator}`; encoded += '\n'; Object.keys(o.headers).forEach((key) => { encoded += key; encoded += ': '; encoded += o.headers[key]; encoded += '\n'; }); encoded += '\n'; if (typeof o.body === 'object') { encoded += JSON.stringify(o.body); } else if (typeof o.body === 'string') { encoded += o.body; } encoded += '\n'; } encoded += `--${separator}--`; return encoded; }