到2023年,大多数其他答案都是错误的。你将无法实现你想要的。
简短概述-正确的解决方案
import requests, sys, time
TOTAL_TIMEOUT = 10
def trace_function(frame, event, arg):
if time.time() - start > TOTAL_TIMEOUT:
raise Exception('Timed out!')
return trace_function
start = time.time()
sys.settrace(trace_function)
try:
res = requests.get('http://localhost:8080', timeout=(3, 6))
except:
raise
finally:
sys.settrace(None)
阅读解释以了解原因!
尽管有很多答案,但我认为这个主题仍然缺乏一个合适的解决方案,没有现有的答案提供一种简单明显的方法。
首先要说的是,截至2023年,仅使用requests
是绝对无法正确执行它的。 这是库开发人员的有意设计决策。
使用timeout
参数的解决方案根本无法实现其预期目的。事实上,它在第一眼看起来“似乎”可以工作纯属偶然:
timeout
参数与请求的总执行时间毫不相关。它仅仅控制了底层套接字在接收到任何数据之前可以经过的最长时间。例如,设定5秒的超时时间,服务器也可以每4秒发送1字节的数据,这完全没有问题,但并不能帮助你太多。
使用stream
和iter_content
的答案要好一些,但它们仍然不能涵盖请求中的所有内容。直到响应头被发送后,才会从iter_content
实际接收到任何内容,这也属于同样的问题 - 即使你使用1字节作为iter_content
的块大小,读取完整的响应头可能需要完全随意的时间,你永远无法真正到达从iter_content
读取任何响应正文的点。
以下是一些完全破坏timeout
和基于stream
的方法的示例。尝试它们所有。无论你使用哪种方法,它们都会无限期地挂起。
server.py
import socket
import time
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
server.bind(('127.0.0.1', 8080))
server.listen()
while True:
try:
sock, addr = server.accept()
print('Connection from', addr)
sock.send(b'HTTP/1.1 200 OK\r\n')
while True:
sock.send(b'a')
time.sleep(1)
except:
pass
demo1.py
import requests
requests.get('http://localhost:8080')
demo2.py
import requests
requests.get('http://localhost:8080', timeout=5)
demo3.py
import requests
requests.get('http://localhost:8080', timeout=(5, 5))
demo4.py
import requests
with requests.get('http://localhost:8080', timeout=(5, 5), stream=True) as res:
for chunk in res.iter_content(1):
break
正确的解决方案
我的方法利用Python的sys.settrace
函数。它非常简单。你不需要使用任何外部库或改变你的代码结构。与大多数其他答案不同的是,这实际上保证了代码在指定时间内执行。请注意,你仍然需要指定timeout
参数,因为settrace
只涉及Python代码。实际的套接字读取是外部系统调用,不受settrace
覆盖,但受timeout
参数覆盖。由于这个事实,确切的时间限制不是TOTAL_TIMEOUT
,而是一个在下面的注释中解释的值。
import requests
import sys
import time
def trace_function(frame, event, arg):
if time.time() - start > TOTAL_TIMEOUT:
raise Exception('Timed out!')
return trace_function
TOTAL_TIMEOUT = 10
start = time.time()
sys.settrace(trace_function)
try:
res = requests.get('http://localhost:8080', timeout=(3, 6))
except:
raise
finally:
sys.settrace(None)
就是这样!