私はこのようなURLを読むためのコードを持っています:
from urllib2 import Request, urlopen
req = Request(url)
for key, val in headers.items():
req.add_header(key, val)
res = urlopen(req, timeout = timeout)
# This line blocks
content = res.read()
タイムアウトはurlopen()呼び出しで機能します。しかし、コードはres.read()呼び出しに到達し、そこで応答データを読み取りたいのですが、そこでタイムアウトは適用されません。そのため、読み取り呼び出しは、サーバーからのデータを待ってほぼ永久にハングする可能性があります。私が見つけた唯一の解決策は、シグナルを使用してread()を中断することです。これは、スレッドを使用しているため、私には適していません。
他にどのようなオプションがありますか?読み取りタイムアウトを処理するPythonのHTTPライブラリはありますか?httplib2とリクエストを確認しましたが、上記と同じ問題が発生しているようです。独自のノンブロッキングを記述したくありません。このためのライブラリがすでにあるはずなので、ソケットモジュールを使用したネットワークコード。
更新:以下の解決策のどれも私のためにそれをしていません。大きなファイルをダウンロードする場合、ソケットまたはurlopenタイムアウトを設定しても効果がないことがわかります。
from urllib2 import urlopen
url = 'http://iso.linuxquestions.org/download/388/7163/http/se.releases.ubuntu.com/ubuntu-12.04.3-desktop-i386.iso'
c = urlopen(url)
c.read()
少なくともPython 2.7.3のWindowsでは、タイムアウトは完全に無視されています。
スレッドなどを介して何らかの非同期タイマーを使用せずに、ライブラリでこれを行うことはできません。その理由は、timeout
、urllib2
およびその他のライブラリで使用されるhttplib
パラメータが、基になるtimeout
にsocket
を設定するためです。そして、これが実際に行うことは、 ドキュメント で説明されています。
SO_RCVTIMEO
入力関数が完了するまで待機する最大時間を指定するタイムアウト値を設定します。入力操作が完了するのを待機する時間の制限を指定する秒数とマイクロ秒のtimeval構造を受け入れます。追加データを受信せずに受信操作がこれだけ長い間ブロックされた場合、部分カウントまたはerrnoを[EAGAIN]または[EWOULDBLOCK]に設定して戻ります。 ]データが受信されない場合。
太字部分が重要です。 socket.timeout
は、timeout
ウィンドウの期間中に1バイトが受信されなかった場合にのみ発生します。言い換えれば、これは受信したバイト間のtimeout
です。
threading.Timer
を使用した簡単な関数は次のようになります。
import httplib
import socket
import threading
def download(Host, path, timeout = 10):
content = None
http = httplib.HTTPConnection(Host)
http.request('GET', path)
response = http.getresponse()
timer = threading.Timer(timeout, http.sock.shutdown, [socket.SHUT_RD])
timer.start()
try:
content = response.read()
except httplib.IncompleteRead:
pass
timer.cancel() # cancel on triggered Timer is safe
http.close()
return content
>>> Host = 'releases.ubuntu.com'
>>> content = download(Host, '/15.04/ubuntu-15.04-desktop-AMD64.iso', 1)
>>> print content is None
True
>>> content = download(Host, '/15.04/MD5SUMS', 1)
>>> print content is None
False
None
をチェックする以外に、関数の内部ではなく外部でhttplib.IncompleteRead
例外をキャッチすることもできます。ただし、HTTPリクエストにContent-Length
ヘッダーがない場合、後者の場合は機能しません。
テストで( ここ で説明されている手法を使用して)、urlopen()
呼び出しで設定されたタイムアウトがread()
呼び出しにも影響することがわかりました。
import urllib2 as u
c = u.urlopen('http://localhost/', timeout=5.0)
s = c.read(1<<20)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/socket.py", line 380, in read
data = self._sock.recv(left)
File "/usr/lib/python2.7/httplib.py", line 561, in read
s = self.fp.read(amt)
File "/usr/lib/python2.7/httplib.py", line 1298, in read
return s + self._file.read(amt - len(s))
File "/usr/lib/python2.7/socket.py", line 380, in read
data = self._sock.recv(left)
socket.timeout: timed out
多分それは新しいバージョンの機能ですか?私は箱から出してすぐに12.04UbuntuでPython 2.7を使用しています。
考えられる(不完全な)解決策の1つは、グローバルソケットタイムアウトを設定することです。これについて詳しく説明します ここ :
import socket
import urllib2
# timeout in seconds
socket.setdefaulttimeout(10)
# this call to urllib2.urlopen now uses the default timeout
# we have set in the socket module
req = urllib2.Request('http://www.voidspace.org.uk')
response = urllib2.urlopen(req)
ただし、これは、ソケットモジュールのallユーザーのタイムアウトをグローバルに変更する場合にのみ機能します。 Celeryタスク内からリクエストを実行しているので、これを行うと、Celeryワーカーコード自体のタイムアウトが台無しになります。
他の解決策を聞いてうれしいです...
非同期ネットワークライブラリでは、I/O操作に合計タイムアウトを適用できるようにする必要があります。たとえば、次のようになります イベントコード例 :
#!/usr/bin/env python2
import gevent
import gevent.monkey # $ pip install gevent
gevent.monkey.patch_all()
import urllib2
with gevent.Timeout(2): # enforce total timeout
response = urllib2.urlopen('http://localhost:8000')
encoding = response.headers.getparam('charset')
print response.read().decode(encoding)
そしてここに asyncio相当 :
#!/usr/bin/env python3.5
import asyncio
import aiohttp # $ pip install aiohttp
async def fetch_text(url):
response = await aiohttp.get(url)
return await response.text()
text = asyncio.get_event_loop().run_until_complete(
asyncio.wait_for(fetch_text('http://localhost:8000'), timeout=2))
print(text)
pycurl.TIMEOUT
オプションはリクエスト全体で機能します :
#!/usr/bin/env python3
"""Test that pycurl.TIMEOUT does limit the total request timeout."""
import sys
import pycurl
timeout = 2 #NOTE: it does limit both the total *connection* and *read* timeouts
c = pycurl.Curl()
c.setopt(pycurl.CONNECTTIMEOUT, timeout)
c.setopt(pycurl.TIMEOUT, timeout)
c.setopt(pycurl.WRITEFUNCTION, sys.stdout.buffer.write)
c.setopt(pycurl.HEADERFUNCTION, sys.stderr.buffer.write)
c.setopt(pycurl.NOSIGNAL, 1)
c.setopt(pycurl.URL, 'http://localhost:8000')
c.setopt(pycurl.HTTPGET, 1)
c.perform()
このコードでは、タイムアウトエラーが約2秒で発生します。チャンク間のタイムアウトよりも短い時間で複数のチャンクで応答を送信するサーバーで、合計readタイムアウトをテストしました。
$ python -mslow_http_server 1
どこ slow_http_server.py
:
#!/usr/bin/env python
"""Usage: python -mslow_http_server [<read_timeout>]
Return an http response with *read_timeout* seconds between parts.
"""
import time
try:
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer, test
except ImportError: # Python 3
from http.server import BaseHTTPRequestHandler, HTTPServer, test
def SlowRequestHandlerFactory(read_timeout):
class HTTPRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
n = 5
data = b'1\n'
self.send_response(200)
self.send_header("Content-type", "text/plain; charset=utf-8")
self.send_header("Content-Length", n*len(data))
self.end_headers()
for i in range(n):
self.wfile.write(data)
self.wfile.flush()
time.sleep(read_timeout)
return HTTPRequestHandler
if __name__ == "__main__":
import sys
read_timeout = int(sys.argv[1]) if len(sys.argv) > 1 else 5
test(HandlerClass=SlowRequestHandlerFactory(read_timeout),
ServerClass=HTTPServer)
私はテストしました 合計接続タイムアウトとhttp://google.com:22222
。
これは一般的な問題だと思いますが、それでも-どこにも答えが見つかりません...タイムアウト信号を使用してこれに対するソリューションを構築しただけです。
import urllib2
import socket
timeout = 10
socket.setdefaulttimeout(timeout)
import time
import signal
def timeout_catcher(signum, _):
raise urllib2.URLError("Read timeout")
signal.signal(signal.SIGALRM, timeout_catcher)
def safe_read(url, timeout_time):
signal.setitimer(signal.ITIMER_REAL, timeout_time)
url = 'http://uberdns.eu'
content = urllib2.urlopen(url, timeout=timeout_time).read()
signal.setitimer(signal.ITIMER_REAL, 0)
# you should also catch any exceptions going out of urlopen here,
# set the timer to 0, and pass the exceptions on.
ソリューションのシグナル部分のクレジットはここにあります: python timer mystery
これは私が見ている行動ではありません。通話がタイムアウトすると、URLError
が表示されます。
_from urllib2 import Request, urlopen
req = Request('http://www.google.com')
res = urlopen(req,timeout=0.000001)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# ...
# raise URLError(err)
# urllib2.URLError: <urlopen error timed out>
_
このエラーをキャッチして、res
を読み取ろうとしないようにすることはできませんか?この後、res.read()
を使おうとすると、_NameError: name 'res' is not defined.
_が表示されます。必要なものは次のようなものです。
_try:
res = urlopen(req,timeout=3.0)
except:
print 'Doh!'
finally:
print 'yay!'
print res.read()
_
タイムアウトを手動で実装する方法はmultiprocessing
経由だと思います、違いますか?ジョブが終了していない場合は、ジョブを終了できます。