前回に引き続きGoogleDriveAPIのお話です。 今回はファイルの新規アップロードについてです。
ファイルを新規にアップロードするにはcreateエンドポイントを使用します。 このエンドポイントが曲者で、アップロード時に3つのアップロードタイプから一つを選んで明示的に指定する必要があります。
現実問題としてメタデータ無しでファイルをアップロードしたいという局面は殆ど無いと思われます。 Google Driveではフォルダの階層構造をファイルのメタデータで管理しているため、メタデータ抜きでは親ディレクトリの指定すらできません。
ということで多くの場合においてアップロードタイプとしてmultipartかresumableを指定することになるでしょう。 複数回リクエストを投げるよりは一回のリクエストで片付けてしまいたいので、自分の中でmultipartを使ってみようということになりました。 ところがこのmultipart、RFC 2387というプロトコルに則ってリクエストを作成する必要があります。日本語の文献もほぼ無いということで素直にRFCを流し読みしました。
RFC 2387 The MIME Multipart/Related Content-type RFC 2387 The MIME Multipart/Related Content-type
複数の異なるMimeTypeのデータをまとめてアップロードするための「Multipart/Related」というMimeTypeについてまとめてたものです。あまりしっかりと読めているわけではないですが、要点は以下の通りとなっています。
これだけだと抽象的で少し分かりづらいですが、RFCの原本に載っている例がわかりやすいです。
Content-Type: Multipart/Related; boundary=example-1 start="[email protected]"; type="Application/X-FixedRecord" start-info="-o ps"
--example-1 Content-Type: Application/X-FixedRecord Content-ID: <[email protected]> 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: <[email protected]> T2xkIE1hY0RvbmFsZCBoYWQgYSBmYXJtCkUgSS BFIEkgTwpBbmQgb24gaGlzIGZhcm0gaGUgaGFk IHNvbWUgZHVja3MKRSBJIEUgSSBPCldpdGggYS BxdWFjayBxdWFjayBoZXJlLAphIHF1YWNrIHF1 YWNrIHRoZXJlLApldmVyeSB3aGVyZSBhIHF1YW NrIHF1YWNrCkUgSSBFIEkgTwo= --example-1--
HTTP本体のリクエストヘッダにはMultipart/Relatedを指定しています。 startなどはどのデータから読み込めばいいかを示す情報のようです(それっぽいことが書いてありましたが良く理解できませんでした)。 リクエストボディの各セクションでは、それぞれのデータに対して改めてヘッダを定義しています。
Multipart/Relatedのリクエストボディを生成する関数を作成した。この関数でエンコードした文字列をリクエストボディにして、リクエストヘッダにはMultipart/Relatedを指定すればOK
/\*\*
\* 引数で指定したオブジェクトを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;
}