У меня есть такой код для чтения 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(), что мне не подходит, так как я использую потоки.
Какие еще есть варианты? Существует ли библиотека HTTP для Python, которая обрабатывает тайм-ауты чтения? Я просмотрел 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()
По крайней мере, в Windows с Python 2.7.3 тайм-ауты полностью игнорируются.
Ни одна библиотека не может сделать это без использования какого-либо асинхронного таймера через потоки или иным образом. Причина в том, что параметр timeout
, используемый в httplib
, urllib2
и других библиотеках, устанавливает timeout
в базовом socket
. И что это на самом деле делает, объясняется в документации .
SO_RCVTIMEO
Устанавливает значение тайм-аута, которое указывает максимальное количество времени, в течение которого функция ввода ожидает своего завершения. Он принимает временную структуру с количеством секунд и микросекунд, определяющих ограничение времени ожидания завершения операции ввода. Если операция приема заблокирована в течение этого времени без получения дополнительных данных , она должна вернуться с частичным счетчиком или ошибкой, установленной в [EAGAIN] или [EWOULDBLOCK], если данные не получены.
Полужирная часть является ключевой. socket.timeout
повышается, только если не было получено ни одного байта за время окна timeout
. Другими словами, это 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
.
Я ожидаю, что это будет распространенной проблемой, и все же - ответов нигде не найти ... Просто построил решение для этого, используя сигнал тайм-аута:
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 тайна
Любая асинхронная сетевая библиотека должна позволять устанавливать общее время ожидания для любой операции ввода / вывода, например, вот пример кода Gevent :
#!/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)
А вот эквивалент асинхронного кода :
#!/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)
Здесь определяется тестовый http-сервер .
Это не то поведение, которое я вижу. Я получаю 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
, нет? Если работа еще не завершена, вы можете прекратить ее.
Та же проблема была с таймаутом сокета в операторе чтения. То, что сработало для меня - это поместить urlopen и read в инструкцию try. Надеюсь, это поможет!