Тайм-аут чтения с использованием urllib2 или любой другой http-библиотеки

У меня есть такой код для чтения 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 тайм-ауты полностью игнорируются.

25
задан Björn Lindqvist 13 September 2013 в 09:09
поделиться

5 ответов

Ни одна библиотека не может сделать это без использования какого-либо асинхронного таймера через потоки или иным образом. Причина в том, что параметр 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.

6
ответ дан 28 November 2019 в 21:50
поделиться

Я ожидаю, что это будет распространенной проблемой, и все же - ответов нигде не найти ... Просто построил решение для этого, используя сигнал тайм-аута:

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 тайна

2
ответ дан 28 November 2019 в 21:50
поделиться

Любая асинхронная сетевая библиотека должна позволять устанавливать общее время ожидания для любой операции ввода / вывода, например, вот пример кода 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-сервер .

3
ответ дан 28 November 2019 в 21:50
поделиться

Это не то поведение, которое я вижу. Я получаю 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, нет? Если работа еще не завершена, вы можете прекратить ее.

0
ответ дан 28 November 2019 в 21:50
поделиться

Та же проблема была с таймаутом сокета в операторе чтения. То, что сработало для меня - это поместить urlopen и read в инструкцию try. Надеюсь, это поможет!

-1
ответ дан 28 November 2019 в 21:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: