PythonでWebアプリを作るときなどhttpアクセスを行うときはrequestsパッケージを使うことが多いと思います。
r = requests.get('https://blog.cosnomi.com')
インターネットでよく解説されているシンプルなコードは上のようなものだと思いますが、このコードをプロダクションで使用すると、対象のサーバーが応答しない場合フリーズしてしまう可能性があります。timeoutが設定されていない、つまり、最大で何秒待機すればよいか指定されていないからです。Webサービスの場合、待機中はユーザーに応答できなくなるので、本当は別の依存しているサーバーに責任があるのに、ユーザーにはあなたのWebサービスに問題があるように見えてしまうかもしれません。
Timeout を設定する必要性
ですから、timeout
を設定しましょう。数秒待って応答が来なかったらエラーとして処理すればよいのです。
requests公式のドキュメントでもtimeoutを設定することが推奨されています。
Most requests to external servers should have a timeout attached, in case the server is not responding in a timely manner. By default, requests do not time out unless a timeout value is set explicitly. Without a timeout, your code may hang for minutes or more.
(拙訳)
大抵の場合、外部サーバーへのリクエストには、対象のサーバーが時間内に応答しない場合に備えて、timeout を設定するべきです。明示的に指定しない限り、デフォルトでは、リクエストは time out しません。timeout が設定されていないと、コードが数分あるいはそれ以上、ハングアップする恐れがあります。
(URL: http://docs.python-requests.org/en/latest/user/advanced/#timeouts)
フリーズしたくなければ明示的に timeout
パラメータを指定しろということです。デフォルトでは timeout=None
となっていて、この場合には応答があるまでずっと待ち続けるということになります。
2 種類の timeout
timeoutには2種類のタイムアウトがあります。connect timeoutとread timeoutです。
connect timeout
相手のサーバーと接続を確立する(establish a connection)までの待機時間です。TCPの仕様では、3秒(以下?)ごとに再送信(retransmit)するらしいので、3の倍数が良いようです。 相手のサーバーがダウンしてる場合は、接続を確立できないはずなので、このconnect timeoutに引っかかります。
read timeout
サーバーがレスポンスを返してくるまでの待機時間です。厳密には、サーバーが最後のバイトを送信してからの待機時間ですが、普通は応答時間と考えて問題なさそうです。接続確立後の待機時間ですので、相手のサーバーダウンというよりは、相手が応答を返すのに時間がかかっているということになります。
例えば、サーバーAがサーバーBにリクエストを送ったとします。サーバーBはダウンしていないものの、レスポンスを作るためにDBから膨大な量のデータを引っ張ってくる必要があり、応答に長い時間がかかったとします。この場合はread timeoutに引っかかります。
timeoutの指定方法
connect と read を別々の値に設定する
(float, float)
のタプルで (connect timeout, read timeout)
の順番に指定します。
r = requests.get('https://blog.cosnomi.com', timeout=(3.0, 7.5))
この場合、connect timeoutが3.0秒、read timeoutが7.5秒となります。
connect と read を同じ値に設定する
タプルではなくfloat型の数値を指定すれば、connect timeoutとread timeoutはどちらもその値に指定されます。
r = requests.get('https://blog.cosnomi.com', timeout=3.5)
この場合、connect timeoutが3.5秒、read timeoutも3.5秒となります。
time out した場合の処理
timeoutを設定してそれに掛かった場合、requests.exceptions.Timeoutが発生します。ですから、次のようにして処理します。
from requests.exceptions import Timeout
try:
r = requests.get('https://blog.cosnomi.com', timeout=3.5)
except Timeout:
# エラーページを表示させるなどの処理
pass
まとめ
ユーザーから見ると、何も反応せず真っ白な画面というのが一番不安なはずです。requestsで外のサーバーに接続するときは原則、timeoutを設定し、引っ掛かった場合はエラーメッセージを表示するようにしましょう。