Google App EngineのTask Queue REST APIの使い方

[Google Cloud Platform Advent Calendar 2014](http://qiita.com/advent-calendar/2014/gcp)の4日目担当のshin1ogawaです。

最近はGoogle App Engine（以降GAEと表記）でやれば構築・メンテナンス・ランニングコストが1/10以下になるであろう案件を、あえてGCEで構築するということに関わっていて、GAEの素晴らしさをますます再認識しています。GAEでササッとやれば楽なのに…

## Task Queue REST API とは？

Google App Engine（以降GAEと表記）には **Task Queue**という機能があります。処理（タスク）をキューに放り込んでおけば、処理が成功するまではGAEが何度でもリトライしてくれる便利な機能です。このTask Queueには、キューに追加した処理がGAEにより自動実行されない **Pull Queue** という種類があり、これを使うとGAEではない外部のプラットフォームからキューに入っている処理を引き出す事ができます。このPull Queue内の処理を操作するAPIが[Task Queue REST API](https://cloud.google.com/appengine/docs/java/taskqueue/rest/)です。[Discovery based API](https://developers.google.com/discovery)に準拠しているので、他のGoogle APIを使ったことがある方は自然に使えると思いますが、設定にはまりどころがあるので、主に設定方法について説明します。

例えばGAEでは実装できない、動画やPDFを処理する機能を提供するために、アプリケーションはGAE上で運用するが一部機能だけGoogle Compute Engine（以降GCEと表記）やAWS上のインスタンスで処理する、といった時に使用できます。

なお、GAEではQueueに入れる「処理（タスク）」とは、「アプリケーションに対するHTTPリクエスト」となります。処理のためのパラメータは、リクエストパラメータとして設定することになります。例えば特定のファイル（GAEの場合はGoogle Cloud Storageに配置することが多いですね）を取得して何か加工するといった場合は、キューには「ファイルのパス」をパラメータとして設定したHTTPリクエストを追加することになります。

この記事では外部環境からGAEのPull Queue内のタスクを利用する方法を説明します。

## Pull Queue を作成する

Pull Queueを作るだけであれば、通常のqueue定義に```<mode>pull</mode>```を追加するだけです。

```xml

<queue-entries>
    <queue>
        <name>normalqueue</name> <!-- 自動実行される通常のQueue(Push Queue) -->
        <rate>10/s</rate>
    </queue>
    <queue>
        <name>pullqueue</name>
        <mode>pull</mode> <!-- 自動実行されないPull Queue -->
    </queue>
</queue-entries>
```

## Pull Queue にタスクを追加する

通常のTask Queueとの違いは大きくは2点で、「HTTPリクエストのメソッドとして"PULL"という特別な値を設定する」「自動実行される時のハンドル先（URL）を設定しない」という点です。

例えばJavaなら次のようになります。

```java

QueueFactory.getQueue("pullqueue").add(
  TaskOptions.Builder.withMethod(TaskOptions.Method.PULL).payload("parameters...");
```

GAE上のアプリケーションからPull Queueに処理を追加する方法は通常のTask Queueの使い方とほとんど同じで簡単ですので、次の公式リファレンスを参照してください。

* https://cloud.google.com/appengine/docs/java/taskqueue/overview-pull
* https://cloud.google.com/appengine/docs/python/taskqueue/overview-pull
* https://cloud.google.com/appengine/docs/go/taskqueue/overview-pull

## Task Queue REST API のための準備

1. [Developer Console](https://console.developers.google.com/project)にプロジェクトが必要ですが、GAEアプリケーションがすでにある場合は、それと同じプロジェクトを使うとよいです（GAEアプリケーションとは別にプロジェクトを作っても問題ありません）。
2. Developer Consoleのプロジェクトで、OAuth2認可のためのClient IDを発行します。詳しくは[この記事](http://codezine.jp/article/detail/7977)など、Google APIのOAuth2を使う記事を参考にしてください。
3. 発行したClient ID, Client Secret, Project IDを使って、スコープ: ```https://www.googleapis.com/auth/taskqueue``` に対する認可を行い、アクセストークン（1時間で期限が切れます）かリフレッシュトークンを取得しておきます。これについても上記の記事などが参考になります。

## まだ設定が足りない！

さて、ここまでの手順で、Task Queue REST APIに必要であろう、Client IDが発行済で、それを使ったアクセストークン（リフレッシュトークン）も取得できた。後は[API Explorer](https://developers.google.com/apis-explorer/?#p/taskqueue/v1beta2/)や自身のプログラムからPull Queueを操作できるね…と思ってしまいます。特に、普段からGoogle APIに慣れている人はそう思いがちです。私は何回もハマってます。

上記の手順では設定が足りません。GAEのPull Queueの設定に、外部から「アクセス可能なユーザ」の設定をメールアドレス単位で設定する必要があるのです！なにこれいびつ、気持ち悪！

```xml

<queue-entries>
    <queue>
        <name>pullqueue</name>
        <mode>pull</mode> <!-- 自動実行されないPull Queue -->
        <acl>
            <user-email>shin1ogawa@example.com</user-email>
            <writer-email>shinichi.ogawa@example.com</writer-email>
        </acl>
    </queue>
</queue-entries>
```

```wrirer-email``` と ```user-email``` なので、書き込み可能な権限と、読み取り可能な権限と思いきや、そうでもありません。
```user-email``` では ```list, get, delete, lease, update``` といった操作が可能です。```insert```だけは ```writer-email```にする必要があります。
もちろん、アクセストークン（リフレッシュトークン）は、ここで指定されたアカウントで認可して取得する必要があります。

ここまでくれば、今度こそ通常のDiscovery basedなGoogle APIと完全に同じように使えます。ε-(´∀｀*)
主な手順は次のようになります。

1. 処理をキューから ```lease``` する（期限を指定する）
2. 完了した処理をキューから ```delete``` する（deleteまでに処理期限が切れたら、自動的にQueueに戻される）

[公式リファレンス](https://cloud.google.com/appengine/docs/java/taskqueue/rest/libraries)に複数の言語に対応したライブラリが記載されていますので、それらを使うと便利です。

## まとめ

Task Queu REST APIは他のGoogle APIと同じ使い方のはずなのに、Developer Consoleのプロジェクトに設定されたアカウント類が適用されないという、ビミョーな注意点があります。そこだけ注意すれば良いですが、気持ち悪い。

**GAE の Pull Queueの設定にメールアドレス単位のACLを追加しろ** 

この一言で十分でしたが、今年に入ってから二回ハマり、ムダに時間を消費した経験から記事としました。