Debug a POST With a Stream Body That Hangs After a 307 Redirect in Python Requests
StripeSoftware Engineer · Technical ScreenLast updated
A debugging exercise in the Python Requests library: a POST with a file-like body hangs after a 307 Temporary Redirect until the client times out. It tests reasoning about the bytes on the wire, HTTP redirect semantics, stream state across a re-sent request, and writing a deterministic regression test.
You are given a working copy of Requests, the widely used Python HTTP client library, into which a bug has been planted. One bug is reported for this repository, though the report is not certain it is the only one. Assume a reproduction shows the following.
A client sends a POST whose body is a file-like object, such as io.BytesIO(b"amount=100"), to a URL that answers 307 Temporary Redirect with a Location header. Requests follows the redirect automatically, but the request to the new location never completes: it waits until the client's read timeout fires, or forever if no timeout is set. The same POST with a plain bytes body completes normally, and so does the file-like body when the server does not redirect.
Explain exactly why the redirected request hangs, find the root cause in the library, fix it, and prove the fix with a regression test.
Constraints and Clarifications
- Change only the library and its tests; the redirecting server behaves correctly.
- Requests follows redirects for
POSTby default; keep that behavior. - The body in the reproduction is seekable and its length is known up front, so it is sent with a
Content-Lengthheader.
Clarifying Questions
- Does the same hang occur for
308 Permanent Redirect, and for301,302and303? - What should happen on a redirect when the body cannot be rewound, for example a generator or a pipe?
- If the stream was not at its start when the request was made, which bytes should the redirected request send?
- Should a redirect to a different host re-send the body at all?
Hints
Hint 1
Look at what goes over the wire: Compare what the redirected request's headers promise with the bytes the client actually sends after them, and ask what each side is waiting for.
Hint 2
Same request, second send: Ask what a file-like body remembers between two sends that a bytes body does not, and which part of the library is responsible for it before the request goes out again.
Follow-up questions
- The body is a generator that yields chunks. What should Requests do when the server answers `307`?
- A teammate proposes recomputing `Content-Length` from the stream before re-sending. It stops the hang. Why is it still wrong?
- On a `307` to a different host, which headers should Requests keep, and which must it drop?
- How would you test this without opening sockets, and what would that test no longer prove?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.