URLフェッチのタイムアウト時間を設定する

先日デプロイしたスコアアタックしりとりなのですがURLフェッチに意図しないエラーがあったようで原因を調べてみました。
ちなみにURLフェッチというのはGoogle App Engine上のアプリケーションが外部のサーバから必要な情報をhttp(s)で取得することです。

スコアアタックしりとりではプレイヤーが単語を入力するとJavascriptで簡単なバリデーション(すでに使った単語ではないか、10個以上の単語が入力されていないかなど)を行ったあとサーバに送られしりとりのルールに適合しているか確認されます。
この際しりとりですから各単語の「読み方」の情報が必要なので、これをはてなキーワードAPIを利用して取得します。この際URLフェッチを使うわけですがログをみると予期せず失敗するケースが発生していました。

はてなキーワードAPIに問い合わせた結果うまく情報が取得できなかった場合プレイヤーには「○○ははてなAPIの問題で選択出来ません。」というメッセージが表示されます。これは以前書いたはてなキーワードAPIに存在しているバグに該当する単語に備えたものなのですがそれ以外の単語でも発生していました。

調べたところ、はてなキーワードAPIへの問い合わせ時にタイムアウトしてしまうことが原因のようです。GAEのURLフェッチはデフォルトでは5秒でタイムアウトしてしまうので、はてなサーバが混雑しているなど返答が遅れるケースでこの問題が発生するようです。そこでURLフェッチのタイムアウト時間を延長することにしました。

これまではPython標準ライブラリであるurllib2のurlopenメソッドを使って

dom = parse(urllib2.urlopen(requestURL))

というようにリクエストを行なっていました。(以前作成していたローカルのはてなキーワードしりとりと全く同じです。)

このままtimeout引数を設定して

dom = parse(urllib2.urlopen(requestURL, timeout=20))

というようにしても良いのですが、今回GAEのライブラリに用意されているurlfetchを使うように変更しました。GAEライブラリのurlfetchはurllib2にくらべGAEの仕様に合わせた例外処理ができるなどのメリットがあります。

GAEの仕様上設定可能なタイムアウト時間は最大60秒です。公式のチュートリアルで最大は10秒と書かれていますがこれは古い仕様でチュートリアルの日本語化が追いついていないのだと思います。英語のチュートリアルによると現在の仕様はユーザーからのhttpリクエストを受けて行うurlfetchでは最大60秒、task queue および cron job から行うurlfetchでは最大10分です。

今回はタイムアウトのリミットを20秒に設定することにします。urlfetchは

from google.appengine.api import urlfetch

data = urlfetch.fetch(url=requestURL, deadline=20)

というように使います。タイムアウトまでの時間を決める引数名はtimeoutではなくdeadlineです。
urllib2.urlopenとは戻り値の型が違います。
urllib2.urlopenは受け取ったデータをファイル型オブジェクトとして返すので

import urllib2
from xml.dom.minidom import parse

dom = parse(urllib2.urlopen(requestURL, timeout=20))

という形でDOM化することができましたが、GAEのurlfetch.fetchはcontentというプロパティが本文にあたる部分の文字列を参照しています。その他のプロパティなどはこちら
そのためparse関数ではなくparseString関数を使います。

from google.appengine.api import urlfetch
from xml.dom.minidom import parseString

content = urlfetch.fetch(url=requestURL, deadline=20).content
dom = parseString(content)

スコアアタックしりとりも上のように変更しました。(実際は例外処理があります)

これでしばらく様子をみようと思います。

ぜひ一度プレイしてみてください
スコアアタックしりとり