Google APIのBatch Requestの仕組みを理解する

2018/3/26
[Discontinuing support for JSON-RPC and Global HTTP Batch Endpoints](
https://developers.googleblog.com/2018/03/discontinuing-support-for-json-rpc-and.html)

Batch Request のエンドポイントが変わるようです。
お使いの方はブログの内容をみて対応しましょう。(2019/3/25までに)

```
www.googleapis.com/batch
↓↓↓
www.googleapis.com/batch/<api>/<version>.
```

---

Google APIには**Batch Request**というAPIを複数実行するための仕組みがあります。

Batch Requestを使用することで**API発行側は実行速度の向上**になります。
リクエスト数が減ることによりGoogle側の負荷も減るはずなので
基本的にデメリットはないという認識です。

**複数APIを実行する際はBatch Requestを積極的に使っていきましょう。**

また、BatchRequestの仕組みはメール送信やWebの添付ファイル送信でも似たような仕組みが使われています。
Google APIなんて使わないという方も知っておいて損はないかと思います。

＠本記事はBatch Requestの仕組みの理解を目的にしたものです。
実際に使用する際は各種Google提供のライブラリに用意されていると思いますのでそちらを利用することをおすすめします。
https://developers.google.com/discovery/libraries



# 前提
アクセストークンの取得まではこちらの記事を参照してください
http://qiita.com/shin1ogawa/items/49a076f62e5f17f18fe5

本記事では以下のようにACCESS_TOKENは環境変数に入れて実行しています。

```console
export ACCESS_TOKEN=ya29.fAIcwrExRp....
```

# 普通にAPIを発行
バッチリクエスト化する前にまず普通にAPIを実行してみます。

## APIの送り方

<dl>
<dt>URL</dt>
<dd>各種APIによる</dd>
<dt>Method</dt>
<dd>各種APIによる 主にGET、POST、PATCH、DELETE</dd>
<dt>Authorization Header</dt>
<dd>Authorization: Bearer {ACCESS_TOKEN}</dd>
<dt>Body</dt>
<dd>POSTやPATCHで使用 主にJSON</dd>
</dl>


### Drive APIを使用してファイルの一覧を取得する例

**Request**

```console
curl https://www.googleapis.com/drive/v3/files -H "Authorization: Bearer $ACCESS_TOKEN" 
```

**Response**

```json
{
 "kind": "drive#fileList",
 "files": [
  {
   "kind": "drive#file",
   "id": "1q5eHxwMySsmnAGV4x831txwWVZ6DDD1CmwCP4QcRBAs",
   "name": "ドキュメント001",
   "mimeType": "application/vnd.google-apps.document"
  },
  {
   "kind": "drive#file",
   "id": "1xXnBefD_jDcjZ-vEb50LO7xS01-AsHgdpKioQLegHrQ",
   "name": "スプレッドシート001",
   "mimeType": "application/vnd.google-apps.spreadsheet"
  },
  {
   "kind": "drive#file",
   "id": "1NT8jIxP_NKbzgdJyQ4FPJTFaiAROmKZmIPq_TBS8wJY",
   "name": "プレゼンテーション001",
   "mimeType": "application/vnd.google-apps.presentation"
  }
 ]
}
```


# Batch Requestに変換
実際に複数にすると長くなってわかりづらいので1件だけのBatch Requestにします。
※1件がわかれば複数も簡単です。

## Batch Requestの送り方
Batch Requestは以下のように送る必要があります。

<dl>
<dt>URL</dt>
<dd>https://www.googleapis.com/batch</dd>
<dt>Method</dt>
<dd>POST</dd>
<dt>Authorization Header</dt>
<dd>Authorization: Bearer {ACCESS_TOKEN}</dd>
<dt>Content-Type Header</dt>
<dd>Content-Type: multipart/mixed; boundary=BOUNDARY</dd>
<dt>Request Body</dt>
<dd>ここにそれぞれのリクエストを記載します。詳細は後述。</dd>
</dl>

Content-Typeを**multipart/mixed**にして「リクエストを複数含みます」と教えてあげます。
**boundary**は「それぞれのリクエストをこの文字で区切ります」という意味です。本記事では"BOUNDARY"という文字列を指定します。

### Request Body

フォーマットとしては以下のとおりです。

```
--{BOUNDARY}
Content-Type: application/http
Content-ID: xxx

HTTP情報

--{BOUNDARY}--
```

HTTPリクエストを表すContent-Typeとそれぞれのリクエストに対するContent-IDを指定します。
Content-IDはなんでもいいですが被らないようにしてください。また、レスポンスを解析するときに使います。（私は主キーとなりうる値を設定することが多いです）

複数送信するHTTPのRequest情報がRequest Bodyに入る形になります。
なお、この際にAuthorizationは外します。（入れても効かなかったです）
Batch Requestに設定したAuthorizationが強制的にに適用されるみたいです。

図に表すとこんな感じになります。
![Batch Request概要図.png](https://qiita-image-store.s3.amazonaws.com/0/35595/870766bf-958b-1904-a788-4d6c405a62af.png)



## 実際に送ってみる
Request Bodyはcurlからファイル指定で実行するためにファイルで保管しておきます。

```databinary.txt
--BOUNDARY
Content-Type: application/http
Content-ID: 1

GET https://www.googleapis.com/drive/v3/files

--BOUNDARY--
```

**Request**

```console
curl https://www.googleapis.com/batch -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: multipart/mixed; boundary=BOUNDARY" --data-binary @databinary.txt
```

**Response**

```http
--batch_oysSsPRlgxc_AAP4j4WrTtE
Content-Type: application/http
Content-ID: response-1

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Date: Tue, 02 Feb 2016 21:40:32 GMT
Expires: Tue, 02 Feb 2016 21:40:32 GMT
Cache-Control: private, max-age=0
Content-Length: 610

{
 "kind": "drive#fileList",
 "files": [
  {
   "kind": "drive#file",
   "id": "1q5eHxwMySsmnAGV4x831txwWVZ6DDD1CmwCP4QcRBAs",
   "name": "ドキュメント001",
   "mimeType": "application/vnd.google-apps.document"
  },
  {
   "kind": "drive#file",
   "id": "1xXnBefD_jDcjZ-vEb50LO7xS01-AsHgdpKioQLegHrQ",
   "name": "スプレッドシート001",
   "mimeType": "application/vnd.google-apps.spreadsheet"
  },
  {
   "kind": "drive#file",
   "id": "1NT8jIxP_NKbzgdJyQ4FPJTFaiAROmKZmIPq_TBS8wJY",
   "name": "プレゼンテーション001",
   "mimeType": "application/vnd.google-apps.presentation"
  }
 ]
}

--batch_oysSsPRlgxc_AAP4j4WrTtE--
```

リクエストを送った時と同じようにGoogleが設定したBOUNDARYが設定されていますのでクライアント側はBOUNDARYで区切って処理していきます。

この時のContent-IDは``response-{リクエスト時に送ったContent-ID}``という形になります。
この値によりどのリクエストのレスポンスかどうかの判断が可能です。（リクエストと同じ順番で返ってくるかもしれませんがそのほうが安全だと思います）

# おまけ（POSTリクエスト例）
Content-Lengthがなくても一応通るようです。

※ちなみにこのリクエストは1つ目で権限を追加して、2つ目で権限の一覧を抜いていますが、2つ目の権限一覧の結果には1つ目のリクエストで追加した権限は反映されていません。
バッチリクエストは順番を保証しないため順番をあてにしたリクエストはNGです。

```
--BOUNDARY
Content-Type: application/http
content-id: 1

POST /drive/v3/files/17zoAl-wCvZdtvXLyW9lnlwODU77golDR_CcLK1mrgiE/permissions
Content-Type: application/json; charset=UTF-8

{
  "emailAddress": "user1@howdylikes.jp",
  "role": "writer",
  "type": "user"
}
--BOUNDARY
Content-Type: application/http
content-id: 2

GET /drive/v3/files/1428x4m9MSiMerQizvKXWbKhXh_a3Ye6lHhUl0HwjkEw/permissions

--BOUNDARY--
```

# その他
## リクエスト上限
APIの種別によりまちまちですのでリファレンスを参照してください。
Driveは100 https://developers.google.com/drive/v3/web/batch
Calendarは50 https://developers.google.com/google-apps/calendar/batch
Directoyは1000 https://developers.google.com/admin-sdk/directory/v1/guides/batch

## 注意点
すべてのAPIがBatch Request化できるわけではありません。