Add net socket address fields, write and end callbacks, and server connection limits (#1052)

This commit is contained in:
Chris Tate
2026-10-15 17:09:27 -05:00
committed by GitHub
24 changed files with 1317 additions and 67 deletions
+70
View File
@@ -0,0 +1,70 @@
// A socket's address fields, byte counts and state: the remote fields
// are cached at first read and stay readable after close, the local ones
// are undefined while connecting and after close, and readyState follows
// the open halves.
import * as net from "node:net";
// The server's 'connection' and the client's 'connect' race (either can
// run first), so each side records its line and both print once both
// have run.
let serverLine: string | null = null;
let clientLine: string | null = null;
let startClient: (() => void) | null = null;
function bothConnected(): void {
if (serverLine === null || clientLine === null || startClient === null) return;
console.log(serverLine);
console.log(clientLine);
startClient();
}
const server = net.createServer((sock) => {
const port = (server.address() as net.AddressInfo).port;
serverLine =
`server side ${sock.remoteAddress} ${sock.remoteFamily} ${typeof sock.remotePort} ` +
`${sock.localAddress} ${sock.localPort === port} ${sock.localFamily}`;
bothConnected();
sock.on("data", (d: Buffer) => {
console.log("server got", d.toString(), sock.bytesRead);
sock.end("bye");
});
});
server.listen(0, "127.0.0.1", () => {
const port = (server.address() as net.AddressInfo).port;
const c = net.connect(port, "127.0.0.1");
console.log(
"before connect",
c.readyState,
c.connecting,
c.pending,
c.remoteAddress,
c.remotePort,
c.localAddress,
c.localPort,
c.bytesRead,
);
c.on("connect", () => {
clientLine =
`connected ${c.readyState} ${c.connecting} ${c.pending} ${c.remotePort === port} ` +
`${c.remoteFamily} ${c.localAddress} ${typeof c.localPort} ${c.localFamily}`;
startClient = () => c.write("hello");
bothConnected();
});
c.on("data", (d: Buffer) => console.log("client got", d.toString(), c.bytesRead));
c.on("end", () => console.log("end", c.readyState, c.bytesRead, c.bytesWritten));
c.on("close", () => {
console.log(
"closed",
c.readyState,
c.pending,
c.remoteAddress,
c.remotePort === port,
c.localAddress,
c.localPort,
);
const d = net.connect(port, "127.0.0.1");
d.destroy();
console.log("destroyed before connect", d.readyState, d.connecting, d.pending, d.remoteAddress);
d.on("close", () => server.close());
});
});
+72
View File
@@ -0,0 +1,72 @@
// write and end callbacks, close listeners with hadError, the 'ready'
// event, string 'data' chunks after setEncoding, setTimeout with a callback (and its msecs checks) and
// setKeepAlive's chaining.
import * as net from "node:net";
// Each side keeps its own list: the client's callbacks and the server's
// events can interleave either way, so the lists print one after the
// other at the end.
const events: string[] = [];
const serverEvents: string[] = [];
let serverSockets = 0;
const server = net.createServer((sock) => {
sock.setEncoding("utf8");
sock.on("data", (d: string) => {
serverEvents.push(`server data ${typeof d} ${d}`);
sock.end("pong", () => serverEvents.push("server end callback"));
});
// the first connection's server side (the timeout connection's close
// order against the client's depends on the event loop phase)
if (serverSockets++ === 0) {
sock.on("close", (hadError: boolean) => serverEvents.push(`server close ${hadError}`));
}
});
function attempt(label: string, f: () => void): void {
try {
f();
events.push(`${label} ok`);
} catch (err) {
const e = err as NodeJS.ErrnoException;
events.push(`${label} ${e.name} ${e.code} ${e.message}`);
}
}
server.listen(0, "127.0.0.1", () => {
const port = (server.address() as net.AddressInfo).port;
const c = net.connect(port, "127.0.0.1");
c.on("connect", () => {
events.push("connect");
c.write("ping", (err?: Error | null) => events.push(`write callback ${err === null}`));
events.push(`setKeepAlive chains ${c.setKeepAlive(true, 1000) === c}`);
});
c.on("ready", () => events.push("ready"));
c.on("data", (d: Buffer) => events.push(`client data ${d.toString()}`));
c.on("end", () => {
events.push("client end");
c.end(() => events.push("client end callback"));
});
c.on("close", (hadError: boolean) => {
events.push(`client close ${hadError}`);
const t = net.connect(port, "127.0.0.1", () => {
attempt("negative", () => t.setTimeout(-1));
attempt("NaN", () => t.setTimeout(NaN));
attempt("Infinity", () => t.setTimeout(Infinity));
const never = () => events.push("removed callback ran");
t.setTimeout(20, never);
t.setTimeout(0, never);
t.setTimeout(30, () => {
events.push("timeout callback");
t.destroy();
});
});
t.on("timeout", () => events.push("timeout event"));
t.on("close", (hadError: boolean) => {
events.push(`t close ${hadError}`);
server.close(() => {
console.log(serverEvents.join("\n"));
console.log(events.join("\n"));
});
});
});
});
@@ -0,0 +1,44 @@
// server.maxConnections turns away connections past the cap with a
// 'drop' event carrying their addresses, and getConnections reports the
// open count on the next tick.
import * as net from "node:net";
const server = net.createServer((sock) => {
sock.on("data", () => {});
});
console.log("default", server.maxConnections);
server.maxConnections = 1;
console.log("set", server.maxConnections);
server.on("drop", (data?: net.DropArgument) => {
console.log(
"drop",
data?.localAddress,
typeof data?.localPort,
data?.localFamily,
data?.remoteAddress,
typeof data?.remotePort,
data?.remoteFamily,
);
});
server.listen(0, "127.0.0.1", () => {
const port = (server.address() as net.AddressInfo).port;
const first = net.connect(port, "127.0.0.1", () => {
const second = net.connect(port, "127.0.0.1");
second.resume();
second.on("close", () => {
console.log("second closed");
process.nextTick(() => console.log("tick before"));
const same = server.getConnections((err: Error | null, count: number) => {
console.log("connections", err, count);
});
console.log("getConnections chains", same === server);
process.nextTick(() => console.log("tick after"));
Promise.resolve().then(() => console.log("microtask"));
server.maxConnections = undefined;
console.log("unset", server.maxConnections);
first.destroy();
server.close(() => console.log("server closed"));
});
});
});
+34
View File
@@ -0,0 +1,34 @@
// net.isIP, isIPv4 and isIPv6 follow Node's address patterns: dotted
// decimal without leading zeros, and IPv6 text forms with one "::", an
// IPv4 tail and an optional %zone.
import * as net from "node:net";
const inputs = [
"1.2.3.4",
"255.255.255.255",
"01.2.3.4",
"256.1.1.1",
"1.2.3",
"1.2.3.4.5",
"::",
"::1",
"fe80::1%eth0",
"fe80::1%",
"1:2:3:4:5:6:7:8",
"1:2:3:4:5:6:7::",
"::1:2:3:4:5:6:7",
"1:2:3:4:5:6:7:8:9",
"1::2::3",
":1::",
"1:",
"::ffff:1.2.3.4",
"1:2:3:4:5:6:1.2.3.4",
"1:2:3:4:5:6:7:1.2.3.4",
"12345::1",
"abcd:EF01::",
"",
"localhost",
];
for (const input of inputs) {
console.log(JSON.stringify(input), net.isIP(input), net.isIPv4(input), net.isIPv6(input));
}