Katkat

Debug a POST With a Stream Body That Hangs After a 307 Redirect in Python Requests

StripeSoftware Engineer · Technical Screen

Last 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 POST by 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-Length header.

Clarifying Questions

  • Does the same hang occur for 308 Permanent Redirect, and for 301, 302 and 303?
  • 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?