たごもりすメモ

コードとかその他の話とか。

GAE用のOAuthライブラリ simpleoauth-gae を作ってる

ある人の発言から時事ネタサービスを作ろうと思いたったわけだが、TwitterとのOAuthまわりでハマりにハマって、結局有りモノに手を入れてしまったのでその話。

結論からいくと、使いやすい(と自分では思っている)ライブラリに修正して公開しましたよ、ということで。
simpleoauth-gae - Simple OAuth Library for GAE Environment - Google Project Hosting

あれこれ調べる、試す

Twitterに対してOAuthやろうと思うと思ったが、dev.twitter.comでPython用として紹介されているのは以下のライブラリ。組み合わせて使う。

前者 oauth2(python-oauth) が汎用的なOAuth用のライブラリで、後者 oauthtwitter(oauth-python-twitter2) がそのラッパー、Twitterへの操作を定義してある層、という位置付け。
ただしこの組合せは socket() を使ってHTTP通信を自分でやるため、そのままではGAE環境では当然使えない。残念。

Twitter経由で@najeiraさんに教えてもらったのがhttp://code.google.com/p/gaema/。コードを眺める限りだとシンプルで良さそうに見えたんだけど、よーく見てみるとなんか色々と許し難いものがあった。

  • tornado.authから認証関連を抽出したモジュールなので、OAuth以外にもあれこれ入ってる
  • tornadoの作りにかなり大きく依存している
    • 認証の実装を RequestHandler サブクラスにMixinするってマジで……! みたいな
  • 認証後の処理呼び出しなどがcallback渡しになってて処理の流れが複雑化する
    • どうせ認証なんて通らなきゃその後の処理できないんだから非同期化する意味ないだろ、と
    • 認証以外の処理も、レスポンスタイムの極小化&Timeoutリトライを考えたらTaskqueueに出すに決まってる*1んだから非同期でやってもしょうがないってば

世の中の人はなんかフレームワーク好きなのでgaemaでもいいのかもしれんが、俺はやだ。
他にもググると実装してみた系のcode snippetはあれこれ出てくるけど、BASIC認証なコードでもう役立たずになることが目に見えてたり、POSTで投げるべき操作をGETで投げて平然としてたり、そのまま使うのはどうにも許し難かったりするのばっかり。

ということでしょうがないからあちこちのコード断片を参考にしながら自分で書いてみた。ら、動かない。
多分なんかものすごく勘違いしてるんだろうけど、最初のRequest Tokenの要求の時点でHTTPリクエスト署名の検証に失敗するとかいって401が返ってくる。ムキー。

oauth2, oauthtwitterに手を入れる

がっくり来てるところであらためてoauth2のコードを眺めてみると、HTTP関連のコードはうまいぐあいに局所化されてることに気付く*2
具体的には Client クラスだけにうまく整理されている。これってこの部分だけ urlfetch 使うように修正すりゃすぐ動くんじゃね?

ということで修正してみたら、実際すぐ動いた。おおお、やるじゃん。ていうか oauth2 は設計いいなー。どこに手を入れればいいかが実に分かりやすい。すばらしい。
反面 oauthtwitter はせっかく oauth2 がナイスなインターフェイスを用意してくれてるのに、それを使おうとしてないコードがあったりでちょっとしょんぼりな出来。HTTPリクエストのヘッダ/ペイロードの構成とか oauth2 がやってくれるのに、それを使わずに自分で低レイヤ関数を呼び出してやってたりする。もったいないぞ。

やってみる

冒頭リンクの simpleoauth_gae ディレクトリを適当なディレクトリに置き、remote_api_shellで簡単に試せる。自分のアプリケーションにremote_apiをセットアップしておくこと。
やってみる内容はoauthtwitterに含まれてた sample-workflow.py のままでOKです。ただしモジュールの読み込みパスに simpleoauth-gae のあるディレクトリを加える必要はあり。
以下の各行を remote_api_shell 上で実行すれば簡単に試せる。

import sys
sys.path = ['/path/to/my/project'] + sys.path
from simpleoauth_gae.twitter import OAuthApi

consumer_key = "xxxxxxxxxxxxxxxx"   # your consumer key
consumer_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"   # your consumer secret

twitter = OAuthApi(consumer_key, consumer_secret)

# api.twitter.comからRequest Tokenを取得
temp_credentials = twitter.getRequestToken()

# エンドユーザをredirectするURLをこれで表示 (表示されるURLをブラウザにペーストすればOK)
print(twitter.getAuthorizationURL(temp_credentials))

# ブラウザ上でアクセスを許可すると飛ばされるURLに含まれるコードを、以下の行の実行後に聞かれるプロンプトで入れる
oauth_verifier = raw_input('What is the PIN? ')

# これでAccess Tokenが取得できるので、アプリケーションのコード上ではこれを保存しておく
access_token = twitter.getAccessToken(temp_credentials, oauth_verifier)
print("oauth_token: " + access_token['oauth_token'])
print("oauth_token_secret: " + access_token['oauth_token_secret'])

# ここからは保存してあるAccess Tokenを使用してユーザの権限でTwitterにアクセス
twitter = OAuthApi(consumer_key, consumer_secret, access_token['oauth_token'], access_token['oauth_token_secret'])

# このユーザのタイムラインを取得
user_timeline = twitter.GetUserTimeline()

pp = pprint.PrettyPrinter(indent=4)
pp.pprint(user_timeline)

# Tweetする場合はこんな感じ
twitter.UpdateStatus('Tweetするメッセージ。クライアント名が自分のサービス名になってるよ!')

これから

前回の反省を生かして公開にあたってライセンスの明記はやったけど、エラーハンドリングはテキトー……というかロクにやってないし、サンプルコードなんかの(英語の)ドキュメントもぜんぜんだし、元のoauth2やoauthtwitterの流用&改変にあたっての注記やコメント修正もやってない。やらないとなー。

あとはTwitterに対する操作で twitter.py にあるものがもうかなり少ないので、追加していく予定。少なくとも自分で使うから確実に追加するのは以下かな。

他になんの操作を追加するかはその時の気分次第で。

*1:決まってるよね?

*2:oauthtwitterがそのことをどうも理解してなかったらしく、自分でhttplib2を使うコードを書いてたせいで混乱して気付くのが遅れた……ということにしておく。w