読者です 読者をやめる 読者になる 読者になる

たごもりすメモ

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

simpleoauth-gae直しながらTwitter APIと戯れる

GAE Python Twitter

simpleoauth-gae前の記事の時点以降で実用するのにちょっとずつ直してるんだけど、あれこれあったのでメモ程度に書いておく。要約するとTwitter APIに対するapi.twitter.comの挙動ヒドいっす。ゆるふわAPIとか言い得て妙すぎる。

23日前後の状況

前に書いて放り込んだときには、RequestTokenおよびAccessTokenの取得時に以下のようになってた。リクエストのパラメータに入れるのか署名に使うのか、あたりの細かい話は省略。

  • RequestToken取得時
    • oauth_callback_urlは指定しない
    • api.twitter.comへのリクエストには以下を使用
      • Consumer Token および Consumer Secret
  • AccessToken取得時
    • ユーザがTwitter.comで認証を済ませたあと戻ってくるときのパラメータに oauth_token しかない
    • api.twitter.comへのリクエストには以下を使用
      • Consumer Token および Consumer Secret
      • RequestToken として取得した Token と Secret
      • パラメータ oauth_verifier の値として、oauth_tokenとしてユーザリクエストから渡されたもの (実態は Request Token と同じ)

OAuthの仕様的には oauth_verifier はServiceProvider(ここではTwitter)の認証後に転送されてくるリクエストのパラメータに oauth_verifier として oauth_token とともに入っているはずだが、これはTwitterについては渡ってこず、かわりにえいやっと oauth_token を渡してみたらうまく動くので、まあいいか、という感じでスルーしてた。

数日前からの状況

実際にサービスに組込むのにRequestToken/AccessTokenとは関係なさそーな場所をいじっていたのだが、ふと気付くとAccessTokenの取得ができないようになってる。あれー? と思って調べてみても、そんなヘンな修正はやっぱりしていない。

で、数日気付かなかったのだが、前の記事にこんなコメントがついてた。この人のところでは*1とりあえず試した時点でそもそも動いてなかったらしい。
はて。例外名のtypoは自分でも気付いて直してあったが、verifierパラメータの追加が漏れてた? と思って見てみると……おお、やっぱり漏れてる。こりゃ動くわけないだろう。なんてものをcommitしたんだ自分は。
と思ったのでマージしたんだけど、動かない。oauth_verifierにoauth_token渡しても渡さなくても "oauth_verifierとかいう不正なパラメータがあるぞ" とかAccessToken取得リクエストのレスポンスメッセージで言われる始末。なんだそりゃふざけんな。oauth_verifierはそもそもこちとら受け取ってねーんだよ……。

さすがにおっかしいなーと思ったので、サービス登録設定の問題かな、と思って試してみたこと。

  • Consumer Key/Secret の更新
    • 結果: 変わらず
  • 別のサービスを新規にひとつ設定してそっちで試す
    • 結果: 変わらず

おっかしいなー、と調べてみると、認証後に転送されてきたユーザリクエストにoauth_verifierが無いこと自体はこのスレッドで中の人が認めてる。そのうち直るらしいけど、今はない、と。
じゃあ他のサービスどうしてるんだっけ、と思って見てみたら twitpic の認証時には oauth_verifier が渡ってるじゃねーか! なんだこりゃ話が違う!
ただし twitvite を見てみると、こっちは oauth_token しか渡ってない。あれー? twitpic はTwitterに買われちゃったサービスだから、実験的に実装が先行してる、とかなのかなあ。納得いかない。

んであれこれ悩んだが、リクエストから oauth_verifier パラメータ自体を外してやると、なんとあっさり動いた。あら? という感じ。つまりAccessTokenの要求には、RequestToken要求でもらったものをもう一度送るだけで、追加の情報はつけてはダメ、と。なんだそりゃ。
考えてみるとこれってコメント欄でもらったパッチ当てる前と同じ状況のはずなので、なんで前にうまく動かなかったのかが気になる。なにか大事なことでも忘れてたんだろうか。*2

結論:現状のTwitterへのOAuth AccessToken取得

今のところ oauth_verifier パラメータは「つけてはいけない」ように見えるが、twitpicみたいについてるのもあるので油断ができない。サービスによって違うかもしれないところを吸収するとなると、パターンとしては以下の2通りの処理が考えられる。

  1. RequestToken取得
  2. ユーザをTwitterの認証画面にredirect
  3. TwitterからコールバックURLにredirectされてきたリクエストを受ける
    • パラメータに oauth_token と oauth_verifier の両方が含まれている場合
      • 両方の値を使って getAccessToken() する
    • パラメータに oauth_token しかない場合
      • verifierは指定せずに getAccessToken() する
  4. AccessTokenが取れたら保存する

いちおうどっちでも対応可能なように simpleoauth_gae は修正してcommitしておいた。oauth_verifierがやってくるパターンが手元で発生してないので試せないんだけど、まあ動くんじゃないかな多分ー。(ちょう無責任
Twitterでの認証後のコールバックURLの受け口*3でのコードは以下のような感じだろうか。GAE/Pythonっぽいけど疑似コードってことで。

def post(self):
  from simpleoauth_gae import twitter

  oauth_token = self.request.get('oauth_token')
  oauth_secret = memcache.get(key=oauth_token, namespace="OAUTH_REQUESTINGS")
  client = twitter.OAuthApi(CONSUMER_KEY, CONSUMER_SECRET)

  token = dict({'oauth_token': oauth_token, 'oauth_secret': oauth_secret})
  oauth_verifier = self.request.get('oauth_verifier')
  if oauth_verifier:
    access_token = client.getAccessToken(token, oauth_verifier)
  else:
    access_token = client.getAccessToken(token)

こんな感じで書いてあれば、ある日に突然oauth_verifierが渡ってくるようになっても問題なく動く(はず)。

ところでTwitter APIがゆるふわな件

リクエスト署名がマズかったり認証関連のパラメータがマズかったりするとHTTPのコード 401 (Unauthorized) が返ってきてハイハイわかりましたよ俺が悪いんでしょ、ということになるわけだが、というよりなると思っていたわけだ。
が、認証通って AccessToken が取得できたあと、TweetのPOSTをやってても401が返ってきたりする。なんだ、どういうことだ、と思いつつ3回も全く同じリクエストを送ってみたら全部401。あれー? とか思ってたら、いっぽうTwitterクライアントの側に、401になったはずのTweetが表示されてる。ふざけんなどういうことだ。

思うに、TwitterAPIに対するレスポンスとしてうまくいかない場合には全部401を返してるんだろう。他のコード見たことないし。最初の401が何かはいざ知らず、エラーコードを返しつつも実際にはリクエストが正常に処理されたんだろう。で、あとの2回は同じ文面のtweetを連続して送ると出てくる "duplicate" とかなんとかいうメッセージが返ってきていたに違いない(見ずに捨てちゃってた。はっはっは。)*4

Twitterが突然改心して適切な失敗コードを返すようになるとも思えないので、今のところ simpleoauth_gae/oauth_gae.py に仕込んであるHTTPリクエストのリトライについては修正が必要っぽい。
今はHTTPステータスコードだけを見てリトライするかどうかを決めてるんだけど、ステータスコードとレスポンス本文への正規表現の組合せでリトライするエラーかどうかを決める、みたいな処理が必要そう。仮にもSP共通っぽくoauth_gae.pyという名前のモジュールだから、パターンは外部からプラグインできるようにしないといかんだろうなあ。
……という修正をやるつもりではいますが、もうちょい先になりそうです。w

この類のノウハウは多分あるところには腐るほどあるんだろうけど、どうなんだろね。状況が数日単位で変わっててもおかしくないから、蓄積された情報を全部眺めたらあちこち矛盾してそうで見ないほうがマシってことすらありそうなので、あんまり調べる気も起きない。
有名なTwitterクライアントのオープンソースになってるものでもあれば参考になること大なんだろうけど、それもねえ。得てしてこの類のバッドノウハウ対策コードは見てもさっぱり意図が取れないことが多いし。あとクライアントならユーザが手でリトライしてくれればOKだからなーというのもありそうだ。

結論はまだない。困ったね。

*1:なにかしらのid書いてあれば修正したとフィードバック通知ができるんだけどな……

*2:酒飲んだ状態でやってたかどうかが重要だが、もうどうだったか忘れてしまった……。(ぉぃ

*3:そういえばRequestTokenリクエスト時の oauth_callback_url パラメータ指定も試したけど結局一度も動いてないまま。

*4:改めて見てみると、HTTPのステータスコードって 409 Conflict とかもあんのねー。