DEBUG.WIKI
PanstarCloud:部署应用,接入 AI,云服务器与 AI 网关
雨云;给项目一台云服务器
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证

urllib3 文件上传跟随 307 后正文为空,检查重定向读取位置

PoolManager 跟随需要保留正文的跳转时,文件可能已在 EOF。优先使用确认过的最终地址;可重放输入需保留初始 body_pos,并核对接收方、完整内容及重复提交。

用 urllib3 的 PoolManager.urlopen() 上传文件,第一跳收到 307 或 308,最终接口返回成功,服务端保存的内容却为空。如果请求体是文件对象,先检查跳转时有没有恢复最初的读取位置。

urllib3 2.7.0 出现过这条路径,正式 2.8.0 的 PoolManager 仍没有在首次发送前自动保存文件位置并传过整个跳转链。首跳读完文件后,第二次连接池调用可能把 EOF 当成新的起点,于是发送格式正确、内容为空的请求。

只涉及需要重放的文件型正文

字节串正文与文件对象不同,不能把这条故障扩大成所有 urllib3 上传都失败。303 通常会改成 GET 并丢弃正文,也不能与要求保留方法和正文的 307、308 混作同一结果。

底层 HTTPConnectionPool 有记录与恢复 body_pos 的逻辑,但 PoolManager 自己处理跨连接池跳转。内层调用保存的局部位置不会自动成为外层下一跳的参数。即使两个地址落到同一主机,仍可能经过这条外层处理。

先对照原始 URL、响应 Location、状态码、请求体类型和每一跳接收的长度。若没有跳转,或首跳已经为空,应继续检查初始文件位置、文件生命周期及分块配置,不能套用这里的原因。

已知最终地址时,直接使用它

如果上传接口有明确且经过确认的最终 URL,优先直接请求该地址,避免这次不必要的重放。地址、方法、授权、签名和正文格式应由接口约定确认,不要把未知 Location 不加检查地保存成永久上传地址。

对预签名请求尤其要谨慎:签名可能绑定路径、方法、头部或正文摘要。手动换一个地址、复制认证头,并不保证签名仍有效,也可能把文件交给未经授权的接收方。

暂时保留这条调用链、且跳转目标均已获准接收该文件时,可以显式保存初始位置,通过公开 urlopen 参数传入 body_pos。下面只展示已有调用处需要保留的参数:

with open("upload.bin", "rb") as body:
    initial_position = body.tell()
    response = http.urlopen(
        "PUT",
        approved_upload_url,
        body=body,
        body_pos=initial_position,
    )

这里 http 是已配置认证、超时与重试策略的 PoolManager,approved_upload_url 及其跳转链事先经过授权核对。实际代码还要保留原有请求参数,并按原流程处理响应与连接释放。这个位置必须在首次发送前记录;若业务本来从文件中段上传,也不能固定写成 seek(0)。

该处理要求文件可定位、在整个请求期间保持打开且内容稳定。生成器、只读向前的流或不可重放的数据不能靠加一个整数变成可重放正文。遇到无法回退的输入,应按接口约定采用可重放存储或取消自动跟随,不要吞掉回退错误。

成功状态仍要配合对象完整性

在受控端点覆盖不跳转、单次及多次 307/308,并让接收端核对完整长度、摘要和业务对象。还要检查真正发送出去的认证与文件接收方,不能为了保住正文就放宽跨源凭据转发。

对于此前已经返回成功的上传,先查询远端对象和业务状态,再决定补传;原请求可能创建了空对象或完成部分业务步骤。直接重试整段上传可能重复写入,恢复读取位置也不会修正已保存的旧对象。