mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-08 08:44:37 +08:00
Follow a redirect whose chunked body has not ended (#2484)
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/http-client-response-aborted-unlistened.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -2376,7 +2376,9 @@ static void scr_http_proto_sweep(void) {
|
||||
}
|
||||
case SCR_HTTP_EMIT_REQ_ABORTED: {
|
||||
ScrHttpReq *req = (ScrHttpReq *)e.h;
|
||||
if (!req->close_emitted) {
|
||||
/* Node's IncomingMessage reports its destroy error only to 'error'
|
||||
* listeners: without one, 'aborted' and 'close' alone report it. */
|
||||
if (!req->close_emitted && req->err_ls.n != 0) {
|
||||
ScrStr *msg = scr_str_new("aborted", 7);
|
||||
scr_net_fire_err_this(&req->err_ls, msg, req, SCR_DYNH_HTTP_REQ);
|
||||
scr_str_release(msg);
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// A client response cut off mid-body reports 'aborted' and 'close'; its
|
||||
// 'error' reaches 'error' listeners only, so a response without one does not
|
||||
// end the process. fetch relies on this when it leaves a redirect whose
|
||||
// chunked body has not ended.
|
||||
import * as http from "node:http";
|
||||
import * as net from "node:net";
|
||||
|
||||
const server = net.createServer((socket) => {
|
||||
let buffered = "";
|
||||
socket.on("data", (chunk) => {
|
||||
buffered += chunk.toString("latin1");
|
||||
while (buffered.includes("\r\n\r\n")) {
|
||||
const end = buffered.indexOf("\r\n\r\n");
|
||||
const path = buffered.slice(0, end).split(" ")[1];
|
||||
buffered = buffered.slice(end + 4);
|
||||
if (path === "/final") {
|
||||
socket.write("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nfinal");
|
||||
continue;
|
||||
}
|
||||
socket.write("HTTP/1.1 303 See Other\r\nLocation: /final\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||
// The terminating chunk arrives in a later read.
|
||||
setTimeout(() => {
|
||||
if (!socket.destroyed) socket.write("0\r\n\r\n");
|
||||
}, 50);
|
||||
}
|
||||
});
|
||||
socket.on("error", () => {});
|
||||
});
|
||||
|
||||
function destroyMidBody(port: number, listen: boolean): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const req = http.get({ host: "127.0.0.1", port, path: "/start" }, (res) => {
|
||||
console.log("status", res.statusCode);
|
||||
res.on("aborted", () => console.log("aborted"));
|
||||
if (listen) res.on("error", (error: NodeJS.ErrnoException) => console.log("res error", error.message, error.code));
|
||||
res.on("close", () => {
|
||||
console.log("res close");
|
||||
resolve();
|
||||
});
|
||||
req.destroy();
|
||||
});
|
||||
req.on("close", () => console.log("req close"));
|
||||
});
|
||||
}
|
||||
|
||||
server.listen(0, "127.0.0.1", async () => {
|
||||
const port = (server.address() as net.AddressInfo).port;
|
||||
const response = await fetch(`http://127.0.0.1:${port}/start`);
|
||||
console.log(response.status, response.redirected, response.url.endsWith("/final"), await response.text());
|
||||
await destroyMidBody(port, false);
|
||||
await destroyMidBody(port, true);
|
||||
server.close();
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 15300, "san": 69300 }
|
||||
Reference in New Issue
Block a user