Cosnomi
Cosnomi

医療×IT / 医学生 / Web(React, Flask) / 機械学習(画像認識, Keras)

Twitter / GitHub / Keybase

【Python】requestsを使うときは必ずtimeoutを設定するべき

Python で Web アプリを作るときなどhttpアクセスを行うときはrequests パッケージを使うことが多いと思います。

r = requests.get('https://blog.cosnomi.com')

インターネットでよく解説されているシンプルなコードは上のようなものだと思いますが、このコードをプロダクションで使用すると、対象のサーバーが応答しない場合フリーズしてしまう可能性があります。timeoutが設定されていない、つまり、最大何秒間待機すればよいか指定されていないからです。Webサービスの場合、待機中はユーザーに応答できなくなるので、本当は別の依存しているサーバーに責任があるのにユーザーにはあなたのWebサービスに問題があるように見えてしまうかもしれません。

Timeout を設定する必要性

ですから、timeout を設定しましょう。数秒待って応答が来なかったらエラーとして処理すればよいのです。

requests公式のドキュメントでは、

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 timeoutread 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 を設定し、引っ掛かった場合はエラーメッセージを表示するようにしましょう。