题目重点:定位 307/308 需要重发请求体时的流位置问题,区分可回退流、不可重放流与不同版本实现。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 讨论的是需要保留方法与 body 的重定向,例如 307/308;对改成 GET 并丢 body 的 redirect 情况不能一概 rewind。
- body 是文件样流对象,支持 tell/seek 才可能按位置回退;generator 或不可 seek 流要重建或明确失败。
- 调试时锁定给定依赖版本,因为不同 Requests 版本的 prepare_body、redirect 逻辑会变化;不把当前 main 分支函数行号当成面试 repo 版本。
解题路线
- 做最小复现:本地测试端点接收上传体并返回 307 到另一个端点,检查第二次收到的 body 长度/hash;对照无重定向、重定向和不可 seek 流。先确认真正的失败是空 body,不是 URL、认证或 Content-Length。
- 在准备请求前记录 body 的当前起始 offset(不一定是 0),让发送和 redirect 处理共享该 offset;需要重发时 seek 回原 offset,若 seek 失败立即抛明确错误。不能只修改 Content-Length 伪造数据,也不能盲目从 0 重放原本只想上传的后半段。
- 检查在什么 redirect 状态与库策略下应重放 body。Requests 官方当前源码在准备流式 body 时尝试保存 tell(),redirect 路径在满足条件时调用 rewind_body;还会根据目标调整 Authorization。修复必须与实际给定版本一致。
- 测试已读过一部分的流、非零起始位置、空文件、多级 307/308、不可 seek 对象、不同主机重定向与无 redirect;跨域凭证不能因修流顺手泄露。若上传操作有外部副作用,重发是否安全也应由服务器/幂等策略决定。
手动走一遍
训练例一份 10 字节文件流起始 offset=2,预期上传末尾 8 字节。第一次发送后指针到 10;收到 307 时必须 seek(2),第二个端点才会再次收到同样 8 字节。seek(0) 会多发 2 字节,直接重用当前流会发空 body。
规模与取舍
记录和 seek 位置为 O(1) 元数据操作,实际重发 body 的 I/O 为 O(B) 字节;若只能缓存不可 seek 流,额外内存或磁盘空间 O(B),应按流量上限选择。
易错点与追问
容易失分的边界
- 在每次重定向时 seek(0),忽略用户提供的非零起始位置。
- 不可 seek 时继续发送空 body,造成静默数据损坏。
- 对所有 3xx 都保留上传 body,忽略实际库的 method 重写策略。
- 未核对当前 Requests 版本就按别版源码改函数。
- 只断言最终状态码 200,不检查第二个端点真实收到的字节。
继续深挖
- 流包装器提供动态委托的 tell/seek,却未被库识别为可迭代流时怎样定位?
- 多级 redirect 每一步如何确保同一初始 offset?
- 文件流重发导致目标服务重复处理时,客户端和服务端分别如何保证幂等?
技术参考
- Requests 官方 sessions.py:当前源码的 redirect 与 rewind_body 分支,仅作通用参考。
- Requests 官方 models.py:当前源码 prepare_body 记录初始 body position 的逻辑。
