Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions src/js/internal/http1_server_fallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,10 @@ function connectionListenerHTTP1(server, socket, options) {
parser.close();
} catch {}
});
// Node's HTTP parser consumes an injected socket's native handle directly,
// so a pause from its previous owner does not prevent request parsing. The
// JS fallback reads through the stream and must resume it explicitly.
socket.resume();
}

function closeIdleHttp1Connections(server) {
Expand Down
7 changes: 7 additions & 0 deletions src/js/node/net.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2607,6 +2607,10 @@ Socket.prototype[Symbol.for("::bunUpgradeServerTLS::")] = function (connection,
connection.on("drain", events[2]);
connection.on("close", events[3]);
this._handle = result;
// Node starts the TLSWrap read side even when the injected transport was
// paused by its previous owner. The TLS handshake must not inherit that
// application-level pause.
this.read(0);
this.emit(kUpgradeAttached);
return;
}
Expand All @@ -2633,6 +2637,9 @@ Socket.prototype[Symbol.for("::bunUpgradeServerTLS::")] = function (connection,
this.once("end", this[kCloseRawConnection]);
raw.connecting = false;
this._handle = tlsHandle;
// Match Node's initRead(): an injected socket may be paused, but TLS still
// needs to consume the ClientHello before exposing its readable stream.
this.read(0);
this.emit(kUpgradeAttached);
});
};
Expand Down
35 changes: 35 additions & 0 deletions test/js/node/http/node-http.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4510,6 +4510,40 @@ it("connectionListener hands off Upgrade and CONNECT like Node", async () => {
}
});

it("connectionListener consumes an injected socket paused by its previous owner", async () => {
const server = createServer((_req, res) => res.end("injected-ok"));
const front = createNetServer(socket => {
socket.pause();
server.emit("connection", socket);
});

try {
await once(front.listen(0, "127.0.0.1"), "listening");
const response = await new Promise<{ statusCode: number | undefined; body: string }>((resolve, reject) => {
const request = get(
{
host: "127.0.0.1",
port: (front.address() as AddressInfo).port,
agent: false,
},
response => {
const chunks: Buffer[] = [];
response.on("data", chunk => chunks.push(chunk));
response.on("end", () =>
resolve({ statusCode: response.statusCode, body: Buffer.concat(chunks).toString("utf8") }),
);
},
);
request.on("error", reject);
});

expect(response).toEqual({ statusCode: 200, body: "injected-ok" });
} finally {
front.close();
server.close();
}
});

it("https wraps a raw socket injected through the connection event", async () => {
const server = createHttpsServer(tlsCert, (req, res) => {
expect((req.socket as any).encrypted).toBe(true);
Expand All @@ -4519,6 +4553,7 @@ it("https wraps a raw socket injected through the connection event", async () =>
const rawClosed = Promise.withResolvers<void>();
const front = createNetServer(socket => {
socket.once("close", () => rawClosed.resolve());
socket.pause();
server.emit("connection", socket);
});

Expand Down
Loading