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

У меня есть код для чтения 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 может зависать почти всегда, ожидая данных с сервера.Единственное решение, которое я нашел, это использовать сигнал для прерывания 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 время ожидания полностью игнорируется.

Ответы [ 8 ]

5 голосов
/ 21 сентября 2015

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

5 голосов
/ 10 мая 2012

Я обнаружил в своих тестах (используя методику, описанную здесь ), что время ожидания, установленное в вызове 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

особенность более новых версий?Я использую Python 2.7 на 12.04 Ubuntu прямо из коробки.

4 голосов
/ 10 марта 2012

Одним из возможных (несовершенных) решений является установка глобального тайм-аута сокета, более подробно объясненного здесь :

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)

Однако это работает, только если вы готовы к глобальномуизменить время ожидания для всех пользователей модуля сокета.Я выполняю запрос из задачи Celery, поэтому выполнение этого может привести к путанице таймаутов для самого кода рабочего Celery.

Я был бы рад услышать любые другие решения ...

2 голосов
/ 21 сентября 2015

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.

2 голосов
/ 07 августа 2013

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

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

1 голос
/ 21 сентября 2015

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

А вот эквивалент 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)

Здесь определен тестовый http-сервер .

0 голосов
/ 12 октября 2013

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

0 голосов
/ 03 марта 2012

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

...