π Bug Report β fetch() can complete a chunked upload after its body stream errorsΒ #7983
Description
When an unknown-length ReadableStream used as a fetch() request body errors partway through an upload, workerd can still send the terminating HTTP/1.1 zero chunk. The receiver therefore sees the partial body as a complete request and may return a successful response.
For example, this Worker sends four 64 KiB chunks, then fails while producing the next chunk:
export default {
async fetch(request, env) {
let chunks = 0;
const body = new ReadableStream({
pull(controller) {
if (chunks++ < 4) {
controller.enqueue(new Uint8Array(64 * 1024));
} else {
controller.error(new Error("source failed partway"));
}
},
});
try {
const response = await fetch(env.UPLOAD_URL, {
method: "PUT",
body,
});
return new Response(
`fetch resolved ${response.status}: ${await response.text()}`
);
} catch (error) {
return new Response(`fetch rejected: ${error.message}`);
}
},
};Set UPLOAD_URL to an HTTP/1.1 receiver that reads the request body and responds with 200: stored when its request end event fires.
Observed:
worker: fetch resolved 200: stored
upstream: end after 262144 bytes (complete body)
Expected: the source error should abort the upload without sending the terminating chunk, so the receiver recognizes an incomplete request. A still-pending fetch() should reject. If response headers have already arrived, an ongoing response-body read should reject.
This affects streaming proxies that stop forwarding a body after validation fails or an upload limit is exceeded: the destination may accept the forwarded prefix as a complete upload.
Activity