为何Python Requests中PUT请求超时而GET请求不超时?
Requests中PUT与GET超时行为不一致的原因及解决方法
问题核心原因
先明确Requests超时参数(connect, read)的实际作用:
connect:仅限制TCP连接建立阶段的耗时,正常建立连接后这个参数就失效了read:限制客户端等待服务器响应的间隔时间——也就是客户端发送完请求后,多久没收到服务器的回复就触发超时
你的PUT请求出现异常的根源在于:你把整个大文件读入内存后用data=dat一次性发送,此时Requests会把整个请求体作为一个连续的数据流发送。在低速网络下,这个发送过程会持续很久,但此时客户端还没完成请求发送,read超时(等待服务器响应)根本还没开始生效;而底层实现中,会错误地把整个请求发送时长和connect超时绑定,导致connect超时设得比总发送时长小就直接失败。
而GET请求的场景是:客户端发送完请求头后就开始接收服务器的响应数据,read超时监控的是两次接收数据包之间的间隔,只要每个间隔不超过设置的时长,哪怕总耗时很久也不会触发超时,这是符合设计预期的。
解决方案
1. 直接传入文件对象分块发送(推荐)
不要一次性把文件读入内存,直接传入文件对象让Requests自动分块发送:
with open(PATH_TO_1GB_FILE, "rb") as f: timeouts = (10, 1) # connect=10秒(足够建立连接),read=1秒(监控数据传输间隔) start = datetime.now() requests.put( f"{local}/puttest", headers={"User-Agent": "Mozilla/5.0"}, timeout=timeouts, data=f, # 直接传文件对象,而非读入内存的bytes verify=cert ) print(f"PUT: {datetime.now()-start}")
这样Requests会分块读取文件并发送,每块传输后会检查间隔超时:如果网络断开,超过1秒没数据传输就会触发超时;而正常低速传输时,只要每块之间的间隔不超过1秒,总时长再久也能完成上传。
2. 手动控制分块上传逻辑
如果需要更精细的控制,可以手动分块读取文件并发送,比如每次发送1MB:
CHUNK_SIZE = 1024 * 1024 # 1MB with open(PATH_TO_1GB_FILE, "rb") as f: timeouts = (10, 1) start = datetime.now() # 初始化流式请求 with requests.put( f"{local}/puttest", headers={"User-Agent": "Mozilla/5.0", "Transfer-Encoding": "chunked"}, timeout=timeouts, verify=cert, stream=True ) as req: while chunk := f.read(CHUNK_SIZE): req.raw.write(chunk) req.raw.flush() print(f"PUT: {datetime.now()-start}")
这种方式可以完全控制分块大小,同时利用stream=True让超时逻辑监控每个分块的传输间隔。
3. 调整urllib3的超时配置
如果不想改代码结构,可以通过HTTPAdapter配置urllib3的socket_timeout,这个参数会监控整个连接的任意阶段的无数据间隔,包括发送请求体的过程:
from requests.adapters import HTTPAdapter from urllib3.util.timeout import Timeout adapter = HTTPAdapter(max_retries=0) # total=None关闭总时长限制,只监控连接和传输间隔超时 adapter.timeout = Timeout(connect=10, read=1, total=None) session = requests.Session() session.mount("https://", adapter) session.mount("http://", adapter) with open(PATH_TO_1GB_FILE, "rb") as f: dat = f.read() start = datetime.now() session.put( f"{local}/puttest", headers={"User-Agent": "Mozilla/5.0"}, data=dat, verify=cert ) print(f"PUT: {datetime.now()-start}")
内容的提问来源于stack exchange,提问作者GarethC
相关产品推荐
相关产品推荐

