Debug an HTTP Client That Hangs After a 307 Redirect With a Streamed Request Body
StripeSoftware Engineer · Technical ScreenLast updated
A debugging exercise on the Python HTTP library Requests: a POST whose body is a file-like stream hangs until it times out after the server answers 307 Temporary Redirect. It tests redirect semantics, Content-Length framing, how a stream behaves when a request is resent, and regression testing of the fix.
You are given a copy of Requests, the open-source Python HTTP client library, into which a defect has been introduced, along with its test suite. A user reports the following:
- They upload a payload by passing a file-like object, such as an
io.BytesIObuffer, as the body of a POST request. - The server answers with
307 Temporary Redirectand aLocationheader that points at another path. - The client follows the redirect, and the call then hangs until it times out. No response is returned.
A minimal reproduction looks like this (the URL and payload are illustrative):
import io
import requests
payload = io.BytesIO(b'{"amount": 100}')
resp = requests.post("http://localhost:8000/upload", data=payload, timeout=10)
# /upload answers 307 with Location: /v2/upload; this call raises a read timeout
Find the root cause, fix it in the library, and add tests that prove the fix.
Constraints and Clarifications
- Redirects are followed, which is the library's default for POST.
- Treat this as the only defect, but rerun the full test suite after your fix.
Clarifying Questions
- Does the problem reproduce when the body is a
bytesobject instead of a file-like object? - Which redirect status codes must repeat the same method and body, and which switch to GET and drop the body?
- If the caller passes a buffer that has already been partly read, should the redirected request resend from where the body started or from the beginning of the buffer?
- What should happen when the body cannot be rewound at all, such as a generator or a pipe: raise an error, or return the 307 response to the caller?
- May the fix read a file-like body fully into memory, or must large uploads keep streaming?
Hints
Hint 1
Follow the body object: Trace the one body object through the first send and the redirected send. What state is it in after the first send, and what do the redirected request's headers still promise the server?
Hint 2
A hang, not an error: Once the redirected request's headers are on the wire, work out who is waiting for whom.
Follow-up questions
- The `Location` header points at a different host. Which headers should change, and should the body still be sent?
- The upload is a multi-gigabyte file. How would you support this redirect without buffering it, and when would you rather not follow the redirect at all?
- How would you make calls that run into this kind of problem fail fast in production instead of waiting out a long timeout?
- How would the outcome change if the body were sent with chunked transfer encoding instead of a `Content-Length` header?
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.