Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions packages/bun-uws/src/HttpResponse.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,19 @@ struct HttpResponse : public AsyncSocket<SSL> {
}
} else {
this->uncork();
/* After uncorking, check if we should close this connection.
* This handles the case where writeHead corked the socket outside
* the uWS request handler (async response), so the close check
* in HttpContext's onData handler never runs for this response. */
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) {
if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) == 0) {
if (((AsyncSocket<SSL> *) this)->getBufferedAmount() == 0) {
((AsyncSocket<SSL> *) this)->shutdown();
((AsyncSocket<SSL> *) this)->close();
return true;
}
}
}
}

/* tryEnd can never fail when in chunked mode, since we do not have tryWrite (yet), only write */
Expand Down Expand Up @@ -219,6 +232,16 @@ struct HttpResponse : public AsyncSocket<SSL> {
}
} else {
this->uncork();
/* After uncorking, check if we should close this connection.
* Same fix as the chunked path above. */
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) {
if ((httpResponseData->state & HttpResponseData<SSL>::HTTP_RESPONSE_PENDING) == 0) {
if (((AsyncSocket<SSL> *) this)->getBufferedAmount() == 0) {
((AsyncSocket<SSL> *) this)->shutdown();
((AsyncSocket<SSL> *) this)->close();
}
}
}
}
}

Expand Down
44 changes: 44 additions & 0 deletions test/js/node/http/node-http-async-chunked-close.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { test, expect } from "bun:test";
import * as http from "node:http";
import * as net from "node:net";

test("http.Server closes connection after async chunked response with Connection: close", async () => {
// Server that responds asynchronously with chunked encoding
const server = http.createServer((req, res) => {
setTimeout(() => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.write("chunk1");
res.write("chunk2");
res.write("chunk3");
res.end();
}, 10);
});

await new Promise<void>(resolve => server.listen(0, "127.0.0.1", resolve));
const port = (server.address() as net.AddressInfo).port;

try {
const result = await new Promise<{ pass: boolean; body: string }>(resolve => {
const socket = net.createConnection(port, "127.0.0.1", () => {
socket.write("GET / HTTP/1.1\r\nHost: test\r\nConnection: close\r\n\r\n");
});

const chunks: Buffer[] = [];
socket.on("data", c => chunks.push(c));
socket.on("end", () => {
resolve({ pass: true, body: Buffer.concat(chunks).toString() });
});
socket.setTimeout(3000, () => {
resolve({ pass: false, body: Buffer.concat(chunks).toString() });
socket.destroy();
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});

expect(result.body).toContain("chunk1");
expect(result.body).toContain("chunk2");
expect(result.body).toContain("chunk3");
expect(result.pass).toBe(true);
} finally {
server.close();
}
});