Skip to content
Closed
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
2 changes: 2 additions & 0 deletions docs/runtime/nodejs-compat.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ We update this page regularly. It reflects the latest version of Bun's compatibi

🟢 Fully implemented. `http.Server` does not extend `net.Server`. Bun ignores `listen(handle)` and the `fd`, `ipv6Only` and `signal` options of `listen()`. `keepAlive`/`keepAliveInitialDelay` on the server are no-ops.

While connected, server socket `bytesWritten` includes direct `socket.write()` calls, including CONNECT and Upgrade traffic. Headers and framing generated by `ServerResponse` are not included in this count.

### [`node:https`](https://nodejs.org/api/https.html)

🟡 `request`, `get`, `Agent` and `globalAgent` are implemented, including connection pooling. Client sockets are `tls.TLSSocket`s. `https.Server` is `http.Server` with TLS options rather than a `tls.Server`. Its request sockets (`req.socket`) are not `tls.TLSSocket`s: `encrypted`, `authorized` and `servername` work, but `getPeerCertificate()` and `getCipher()` are missing. `setSecureContext()`, `addContext()`, `SNICallback` and `handshakeTimeout` are not supported.
Expand Down
4 changes: 2 additions & 2 deletions src/js/node/_http_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1736,7 +1736,7 @@ function getNodeHTTPServerSocket() {
get bytesWritten() {
const handle = this[kHandle];
return handle
? (handle.response?.getBytesWritten?.() ?? handle.bytesWritten ?? this[kBytesWritten] ?? 0)
? (handle.response?.getBytesWritten?.() ?? 0) + (handle.bytesWritten ?? 0)
: (this[kBytesWritten] ?? 0);
}
set bytesWritten(value) {
Expand Down Expand Up @@ -1764,7 +1764,7 @@ function getNodeHTTPServerSocket() {
}
#onDrain() {
const handle = this[kHandle];
this[kBytesWritten] = handle ? (handle.response?.getBytesWritten?.() ?? handle.bytesWritten ?? 0) : 0;
this[kBytesWritten] = handle ? (handle.response?.getBytesWritten?.() ?? 0) + (handle.bytesWritten ?? 0) : 0;
const callback = this.#pendingCallback;
if (callback) {
this.#pendingCallback = null;
Expand Down
38 changes: 37 additions & 1 deletion test/js/node/http/node-http-connect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import http from "http";

import { once } from "node:events";
import https from "node:https";
import type { AddressInfo } from "node:net";
import type { AddressInfo, Socket } from "node:net";
import net from "node:net";
import { join } from "node:path";
import type { Duplex } from "node:stream";
Expand Down Expand Up @@ -60,6 +60,42 @@ async function writeInTwoReads(

const BIG_DATA = Buffer.alloc(1024 * 1024 * 64, "bun").toString();
describe("HTTP server CONNECT", () => {
test.each(["http", "https"])("%s: bytesWritten counts raw CONNECT writes", async proto => {
const server = proto === "https" ? https.createServer(tlsCert) : http.createServer();
const reply = "HTTP/1.1 200 Connection Established\r\n\r\n";
const payload = "héllo";
let accepted: Socket | undefined;
let written = 0;
server.on("connect", (_req, socket) => {
accepted = socket as Socket;
const before = accepted.bytesWritten;
socket.write(reply);
socket.write(payload);
written = accepted.bytesWritten - before;
socket.end();
});
await once(server.listen(0, "127.0.0.1"), "listening");
const port = (server.address() as AddressInfo).port;
const client =
proto === "https"
? tls.connect({ port, host: "127.0.0.1", rejectUnauthorized: false })
: net.connect(port, "127.0.0.1");
try {
const chunks: Buffer[] = [];
client.on("data", chunk => chunks.push(chunk));
const ended = once(client, "end");
client.write("CONNECT example.test:443 HTTP/1.1\r\nHost: example.test:443\r\n\r\n");
await ended;
expect(Buffer.concat(chunks).toString()).toBe(reply + payload);
expect(written).toBe(Buffer.byteLength(reply + payload));
} finally {
client.destroy();
accepted?.destroy();
server.closeAllConnections();
await new Promise<void>(resolve => server.close(() => resolve()));
}
});

test("should handle backpressure", async () => {
const responseHeader = "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n";
await using proxyServer = http.createServer((req, res) => {
Expand Down
3 changes: 3 additions & 0 deletions test/js/node/http/node-http-with-ws.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ describe.concurrent("npm ws on node:http upgrade sockets", () => {
"delivers the 101 and an immediate callback write on a %s socket",
async (_name, reuse, warmupRequests) => {
let connection!: import("ws").WebSocket;
let bytesWrittenBeforeSend = 0;
const message = Promise.withResolvers<Buffer>();
const fixture = await openWebSocketWithAgent({
ServerClass: NpmWebSocketServer,
Expand All @@ -242,13 +243,15 @@ describe.concurrent("npm ws on node:http upgrade sockets", () => {
},
onConnection(ws) {
connection = ws;
bytesWrittenBeforeSend = (ws as WsWebSocket & { _socket: Socket })._socket.bytesWritten;
ws.send("ready");
},
});
try {
expect(connection).toBe(fixture.accepted.ws);
expect(fixture.accepted.head).toEqual(Buffer.alloc(0));
expect((await message.promise).toString()).toBe("ready");
expect((fixture.accepted.socket as Socket).bytesWritten - bytesWrittenBeforeSend).toBe(7);
} finally {
await closeWebSocketFixture(fixture);
}
Expand Down
Loading