Skip to content

HTTP and DNS

@fkn/lib/http is Node’s http API, written over the net.Socket from TCP and UDP sockets. @fkn/lib/dns is the one call from Node’s dns that the broker, the connection your page holds into FKN, answers. This page covers request(), get() and createServer(), the parser, the missing https entry point, and lookup() with its all and family options.

This page imports from @fkn/lib/http and @fkn/lib/dns. The root and cloud forms of both are the same implementation, so a request looks the same wherever you import it from: see entry points. A get() fetches the catalog and reads the reply:

app.ts
import * as
import http
http
from '@fkn/lib/http'
const
const request: http.ClientRequest
request
=
import http
http
.
function get(...args: [options: string | URL | http.ClientRequestArgs, callback?: (res: http.IncomingMessage) => void] | [url: string | URL, options: http.ClientRequestArgs, callback?: (res: http.IncomingMessage) => void]): http.ClientRequest
export get
get
('http://example.org/api/catalog.json', {
ClientRequestArgs.port?: string | number | undefined
port
: 80 },
response: http.IncomingMessage
response
=> {
response: http.IncomingMessage
response
.
IncomingMessage.statusCode?: number | undefined

response only

statusCode
// 200, from the status line
response: http.IncomingMessage
response
.
IncomingMessage.headers: IncomingHttpHeaders
headers
['content-type'] // 'application/json', names arrive lowercased
const
const chunks: Buffer<ArrayBufferLike>[]
chunks
:
interface Buffer<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
Buffer
[] = []
response: http.IncomingMessage
response
.
Stream.Readable.on<"data">(eventName: "data", listener: (chunk: any) => void): http.IncomingMessage (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('data', (
chunk: Buffer<ArrayBufferLike>
chunk
:
interface Buffer<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
Buffer
) =>
const chunks: Buffer<ArrayBufferLike>[]
chunks
.
Array<Buffer<ArrayBufferLike>>.push(...items: Buffer<ArrayBufferLike>[]): number

Appends new elements to the end of an array, and returns the new length of the array.

@paramitems New elements to add to the array.

push
(
chunk: Buffer<ArrayBufferLike>
chunk
))
response: http.IncomingMessage
response
.
Stream.Readable.on<"end">(eventName: "end", listener: () => void): http.IncomingMessage (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('end', () => {
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
JSON.parse(text: string, reviver?: (this: any, key: string, value: any) => any): any

Converts a JavaScript Object Notation (JSON) string into an object.

@paramtext A valid JSON string.

@paramreviver A function that transforms the results. This function is called for each member of the object. If a member contains nested objects, the nested objects are transformed before the parent object is.

@throws{SyntaxError} If text is not valid JSON.

parse
(
var Buffer: BufferConstructor
Buffer
.
BufferConstructor.concat(list: readonly Uint8Array[], totalLength?: number): Buffer<ArrayBuffer>

Returns a new Buffer which is the result of concatenating all the Buffer instances in the list together.

If the list has no items, or if the totalLength is 0, then a new zero-length Buffer is returned.

If totalLength is not provided, it is calculated from the Buffer instances in list by adding their lengths.

If totalLength is provided, it must be an unsigned integer. If the combined length of the Buffers in list exceeds totalLength, the result is truncated to totalLength. If the combined length of the Buffers in list is less than totalLength, the remaining space is filled with zeros.

import { Buffer } from 'node:buffer';
// Create a single `Buffer` from a list of three `Buffer` instances.
const buf1 = Buffer.alloc(10);
const buf2 = Buffer.alloc(14);
const buf3 = Buffer.alloc(18);
const totalLength = buf1.length + buf2.length + buf3.length;
console.log(totalLength);
// Prints: 42
const bufA = Buffer.concat([buf1, buf2, buf3], totalLength);
console.log(bufA);
// Prints: <Buffer 00 00 00 00 ...>
console.log(bufA.length);
// Prints: 42

Buffer.concat() may also use the internal Buffer pool like Buffer.allocUnsafe() does.

@sincev0.7.11

@paramlist List of Buffer or Uint8Array instances to concatenate.

@paramtotalLength Total length of the Buffer instances in list when concatenated.

concat
(
const chunks: Buffer<ArrayBufferLike>[]
chunks
).
Buffer<ArrayBuffer>.toString(encoding?: BufferEncoding, start?: number, end?: number): string

Decodes buf to a string according to the specified character encoding inencoding. start and end may be passed to decode only a subset of buf.

If encoding is 'utf8' and a byte sequence in the input is not valid UTF-8, then each invalid byte is replaced with the replacement character U+FFFD.

The maximum length of a string instance (in UTF-16 code units) is available as

constants.MAX_STRING_LENGTH

.

import { Buffer } from 'node:buffer';
const buf1 = Buffer.allocUnsafe(26);
for (let i = 0; i < 26; i++) {
// 97 is the decimal ASCII value for 'a'.
buf1[i] = i + 97;
}
console.log(buf1.toString('utf8'));
// Prints: abcdefghijklmnopqrstuvwxyz
console.log(buf1.toString('utf8', 0, 5));
// Prints: abcde
const buf2 = Buffer.from('tést');
console.log(buf2.toString('hex'));
// Prints: 74c3a97374
console.log(buf2.toString('utf8', 0, 3));
// Prints: té
console.log(buf2.toString(undefined, 0, 3));
// Prints: té

@sincev0.1.90

@paramencoding The character encoding to use.

@paramstart The byte offset to start decoding at.

@paramend The byte offset to stop decoding at (not inclusive).

toString
('utf8')) // the catalog, once the parser saw the whole body
const request: http.ClientRequest
request
.
ClientRequest.destroy(error?: Error): http.ClientRequest

Destroy the stream. Optionally emit an 'error' event, and emit a 'close' event (unless emitClose is set to false). After this call, the writable stream has ended and subsequent calls to write() or end() will result in an ERR_STREAM_DESTROYED error. This is a destructive and immediate way to destroy a stream. Previous calls to write() may not have drained, and may trigger an ERR_STREAM_DESTROYED error. Use end() instead of destroy if data should flush before close, or wait for the 'drain' event before destroying the stream.

Once destroy() has been called any further calls will be a no-op and no further errors except from _destroy() may be emitted as 'error'.

Implementors should not override this method, but instead implement writable._destroy().

@sincev8.0.0

@paramerror Optional, an error to emit with 'error' event.

destroy
() // the socket goes with the request, and its busy token with it
})
})
const request: http.ClientRequest
request
.
Stream.Writable.on<"error">(eventName: "error", listener: (err: Error) => void): http.ClientRequest (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('error', (
error: Error
error
:
interface Error
Error
) =>
error: Error
error
.
Error.message: string
message
) // a connect that fails lands here, and no response follows

The port is passed beside the URL, for a reason making a request explains. Nothing here uses the browser’s fetch. The request is written to a socket and the reply is parsed from the bytes, so a client library written for Node runs unchanged. fetch() is the shorter way to a resource, and the one that speaks HTTPS.

request(options, cb?), request(url, cb?) and request(url, options, cb?) build a ClientRequest. get() is request() followed by end(). A string or a URL fills protocol, hostname, host, port, path and auth. Explicit options override what the URL said.

port is copied from the URL exactly as the URL parser gives it, which is empty for the protocol’s default port. An empty port dials port 0 and sends Host: example.org:0 on the wire. So http://example.org:6881/ works as a string and http://example.org/ does not. A URL on port 80 or 443 needs the port beside it, { port: 80 } in the example above.

The constructor does the rest at once. It uppercases method, which defaults to GET, and defaults path to / and the host to localhost. It adds a Host header and an Authorization: Basic from auth, each unless you set one. Then it creates a net.Socket and calls connect on it right away.

localhost reaches a net.Server listening in the same data plane, the shared worker behind the broker that the pages of one origin share, as connecting explains. The callback you pass is a one-time response listener. The head goes out with the first body chunk or at end():

app.ts
import * as
import http
http
from '@fkn/lib/http'
const
const body: string
body
=
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

@paramvalue A JavaScript value, usually an object or array, to be converted.

@paramreplacer A function that transforms the results.

@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.

@throws{TypeError} If a circular reference or a BigInt value is found.

stringify
({
items: never[]
items
: [] })
const
const request: http.ClientRequest
request
=
import http
http
.
function request(...args: [options: string | URL | http.ClientRequestArgs, callback?: (res: http.IncomingMessage) => void] | [url: string | URL, options: http.ClientRequestArgs, callback?: (res: http.IncomingMessage) => void]): http.ClientRequest
export request
request
(
{
host: string
host
: 'example.org',
port: number
port
: 80,
ClientRequestArgs.method?: string | undefined
method
: 'PUT',
ClientRequestArgs.path?: string | undefined
path
: '/api/catalog.json',
ClientRequestArgs.headers?: OutgoingHttpHeaders | readonly string[] | undefined
headers
: { 'Content-Type': 'application/json' }
},
response: http.IncomingMessage
response
=> {
response: http.IncomingMessage
response
.
Stream.Readable.resume(): http.IncomingMessage

The readable.resume() method causes an explicitly paused Readable stream to resume emitting 'data' events, switching the stream into flowing mode.

The readable.resume() method can be used to fully consume the data from a stream without actually processing any of that data:

getReadableStreamSomehow()
.resume()
.on('end', () => {
console.log('Reached the end, but did not read anything.');
});

The readable.resume() method has no effect if there is a 'readable' event listener.

@sincev0.9.4

resume
() // drain the body, so that end fires
response: http.IncomingMessage
response
.
Stream.Readable.on<"end">(eventName: "end", listener: () => void): http.IncomingMessage (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('end', () =>
response: http.IncomingMessage
response
.
IncomingMessage.complete: boolean
complete
) // true, the parser saw the whole message
}
)
const request: http.ClientRequest
request
.
Stream.Writable.on<"error">(eventName: "error", listener: (err: Error) => void): http.ClientRequest (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('error', (
error: Error
error
:
interface Error
Error
) =>
error: Error
error
.
Error.message: string
message
)
const request: http.ClientRequest
request
.
ClientRequest.end(chunk?: unknown, encoding?: unknown, callback?: unknown): http.ClientRequest

Calling the writable.end() method signals that no more data will be written to the Writable. The optional chunk and encoding arguments allow one final additional chunk of data to be written immediately before closing the stream.

Calling the

write

method after calling

end

will raise an error.

// Write 'hello, ' and then end with 'world!'.
import fs from 'node:fs';
const file = fs.createWriteStream('example.txt');
file.write('hello, ');
file.end('world!');
// Writing more now is not allowed!

Signals that no more data will be written, with one final chunk of data.

end
(
const body: string
body
) // the head goes out now, with Content-Length set from the body

Four rules decide the framing of what you send, in this order.

A Content-Length or a Transfer-Encoding: chunked header you set yourself is kept. end(body) with nothing written before it sets Content-Length from the body. A request that ends with nothing written and no framing header of its own sends Content-Length: 0, whatever the method. A body you write() before end(), with no framing header of your own, goes out chunked on every method except GET, HEAD and DELETE.

The response arrives as an IncomingMessage, exposed as request.res and emitted as response. It carries statusCode, statusMessage, httpVersion, headers and rawHeaders from the head. The body is pushed as Buffers. A HEAD response skips the body.

complete is false inside the callback and true by the time end fires. A socket that ends first completes a body framed by end of stream.

socket is emitted inside the constructor, before any listener can attach, so request.on('socket', ...) never runs. Read request.socket or its alias request.connection instead. Both are set by the time request() returns.

A connect that fails surfaces as the socket’s own error on the request’s error event, getaddrinfo ENOTFOUND <hostname> when the name has no answer, or tcp connect to <address>:<port> timed out after 12000ms when the relay does not acknowledge the connect. A peer that refuses the connect answers with the upstream operating system text, such as Connection refused (os error 111). No response follows.

Every request opens its own net.Socket. Nothing reuses it afterwards. Agent and globalAgent carry Node’s options and nothing reads them, so agent in the request options changes nothing either:

app.ts
import * as
import http
http
from '@fkn/lib/http'
import http
http
.
const globalAgent: http.Agent
export globalAgent
globalAgent
.
Agent.keepAlive: boolean
keepAlive
// false, and nothing reads it
const
const request: http.ClientRequest
request
=
import http
http
.
function get(...args: [options: string | URL | http.ClientRequestArgs, callback?: (res: http.IncomingMessage) => void] | [url: string | URL, options: http.ClientRequestArgs, callback?: (res: http.IncomingMessage) => void]): http.ClientRequest
export get
get
({
host: string
host
: 'example.org',
port: number
port
: 80,
ClientRequestArgs.path?: string | undefined
path
: '/api/catalog.json',
ClientRequestArgs.agent?: boolean | Agent | undefined
agent
: false },
response: http.IncomingMessage
response
=> {
response: http.IncomingMessage
response
.
Stream.Readable.resume(): http.IncomingMessage

The readable.resume() method causes an explicitly paused Readable stream to resume emitting 'data' events, switching the stream into flowing mode.

The readable.resume() method can be used to fully consume the data from a stream without actually processing any of that data:

getReadableStreamSomehow()
.resume()
.on('end', () => {
console.log('Reached the end, but did not read anything.');
});

The readable.resume() method has no effect if there is a 'readable' event listener.

@sincev0.9.4

resume
() // drain the body, so that end fires
response: http.IncomingMessage
response
.
Stream.Readable.on<"end">(eventName: "end", listener: () => void): http.IncomingMessage (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('end', () =>
const request: http.ClientRequest
request
.
ClientRequest.destroy(error?: Error): http.ClientRequest

Destroy the stream. Optionally emit an 'error' event, and emit a 'close' event (unless emitClose is set to false). After this call, the writable stream has ended and subsequent calls to write() or end() will result in an ERR_STREAM_DESTROYED error. This is a destructive and immediate way to destroy a stream. Previous calls to write() may not have drained, and may trigger an ERR_STREAM_DESTROYED error. Use end() instead of destroy if data should flush before close, or wait for the 'drain' event before destroying the stream.

Once destroy() has been called any further calls will be a no-op and no further errors except from _destroy() may be emitted as 'error'.

Implementors should not override this method, but instead implement writable._destroy().

@sincev8.0.0

@paramerror Optional, an error to emit with 'error' event.

destroy
()) // the socket goes with the request, and its busy token with it
})
const request: http.ClientRequest
request
.
Stream.Writable.on<"error">(eventName: "error", listener: (err: Error) => void): http.ClientRequest (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('error', (
error: Error
error
:
interface Error
Error
) =>
error: Error
error
.
Error.message: string
message
)

Passing an Agent in the options is ignored. The constructor never reads it, and the request carries no agent field. Passing the package’s own Agent there is a compile error, because the agent option is typed as Node’s Agent.

A client that expects connection pooling pays a connect to the relay, the FKN server that holds the real socket, on every request. It also holds a tcp socket busy token, one of the reasons your app reports itself busy, until its socket is destroyed, so the examples on this page destroy the request on end: see busy tokens.

createServer(options?, listener?) returns a Server that extends net.Server and runs a request parser on every accepted connection. listen, address and close are the ones from listening, the tcp server busy token included. For every request head the server builds an IncomingMessage and a ServerResponse and emits request:

app.ts
import * as
import http
http
from '@fkn/lib/http'
const
const server: ServerImpl
server
=
import http
http
.
function createServer(options?: object | http.RequestListener, requestListener?: http.RequestListener): http.Server
export createServer
createServer
((
request: http.IncomingMessage
request
,
response: http.ServerResponse
response
) => {
request: http.IncomingMessage
request
.
IncomingMessage.method?: string | undefined

request only

method
// 'GET', from the request line
request: http.IncomingMessage
request
.
IncomingMessage.url?: string | undefined

request only

url
// '/api/catalog.json', exactly as the client sent it
response: http.ServerResponse
response
.
ServerResponse.writeHead(statusCode: number, statusMessage?: string | OutgoingHttpHeaders | OutgoingHttpHeaders[keyof OutgoingHttpHeaders][], headers?: OutgoingHttpHeaders): http.ServerResponse
writeHead
(200, { 'Content-Type': 'application/json' })
response: http.ServerResponse
response
.
OutgoingMessage.end(chunk?: unknown, encoding?: BufferEncoding | (() => void), callback?: () => void): http.ServerResponse

Calling the writable.end() method signals that no more data will be written to the Writable. The optional chunk and encoding arguments allow one final additional chunk of data to be written immediately before closing the stream.

Calling the

write

method after calling

end

will raise an error.

// Write 'hello, ' and then end with 'world!'.
import fs from 'node:fs';
const file = fs.createWriteStream('example.txt');
file.write('hello, ');
file.end('world!');
// Writing more now is not allowed!

Signals that no more data will be written, with one final chunk of data.

end
(
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

@paramvalue A JavaScript value, usually an object or array, to be converted.

@paramreplacer A function that transforms the results.

@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.

@throws{TypeError} If a circular reference or a BigInt value is found.

stringify
({
items: never[]
items
: [] })) // the head and the body go out now, with Content-Length set from the body
})
const server: ServerImpl
server
.
NodeJS.EventEmitter<any>.on<string | symbol>(eventName: string | symbol, listener: (...args: any[]) => void): ServerImpl

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('error', (
error: Error
error
:
interface Error
Error
) =>
error: Error
error
.
Error.message: string
message
) // a refused bind lands here, and no listening follows
const server: ServerImpl
server
.
Server$1.listen(port?: number | undefined, hostname?: string | undefined, listeningListener?: (() => void) | undefined): ServerImpl (+9 overloads)

Start a server listening for connections. A net.Server can be a TCP or an IPC server depending on what it listens to.

Possible signatures:

  • server.listen(handle[, backlog][, callback])
  • server.listen(options[, callback])
  • server.listen(path[, backlog][, callback]) for IPC servers
  • server.listen([port[, host[, backlog]]][, callback]) for TCP servers

This function is asynchronous. When the server starts listening, the 'listening' event will be emitted. The last parameter callbackwill be added as a listener for the 'listening' event.

All listen() methods can take a backlog parameter to specify the maximum length of the queue of pending connections. The actual length will be determined by the OS through sysctl settings such as tcp_max_syn_backlog and somaxconn on Linux. The default value of this parameter is 511 (not 512).

All

Socket

are set to SO_REUSEADDR (see socket(7) for details).

The server.listen() method can be called again if and only if there was an error during the first server.listen() call or server.close() has been called. Otherwise, an ERR_SERVER_ALREADY_LISTEN error will be thrown.

One of the most common errors raised when listening is EADDRINUSE. This happens when another server is already listening on the requestedport/path/handle. One way to handle this would be to retry after a certain amount of time:

server.on('error', (e) => {
if (e.code === 'EADDRINUSE') {
console.error('Address in use, retrying...');
setTimeout(() => {
server.close();
server.listen(PORT, HOST);
}, 1000);
}
});

listen
(6881, '0.0.0.0', () =>
const server: ServerImpl
server
.
Server$1.address(): string | AddressInfo | null

Returns the bound address, the address family name, and port of the server as reported by the operating system if listening on an IP socket (useful to find which port was assigned when getting an OS-assigned address):{ port: 12346, family: 'IPv4', address: '127.0.0.1' }.

For a server listening on a pipe or Unix domain socket, the name is returned as a string.

const server = net.createServer((socket) => {
socket.end('goodbye\n');
}).on('error', (err) => {
// Handle errors here.
throw err;
});
// Grab an arbitrary unused port.
server.listen(() => {
console.log('opened server on', server.address());
});

server.address() returns null before the 'listening' event has been emitted or after calling server.close().

@sincev0.1.90

address
()) // { address, family, port }, filled once the bind succeeded

ServerResponse starts with statusCode 200 and sendDate on. writeHead(code, message?, headers?) takes headers as an object, an array of [name, value] pairs or a flat list. The array forms keep duplicates, which is how you send two Set-Cookie lines.

The head carries the message you passed, or the one from STATUS_CODES, or unknown when the code is not in it. After it come Date, unless you set one or clear sendDate, and then Connection: keep-alive or Connection: close.

writeHead only records the head. The response writes it with the first body byte or at end(), so a setHeader after writeHead still lands.

A second writeHead, or a setHeader after the head went out, throws Cannot render headers after they are sent to the client or Cannot set headers after they are sent to the client with code: 'ERR_HTTP_HEADERS_SENT'. removeHeader after the head went out throws Cannot remove headers after they are sent to the client with no code. setHeader matches names case-insensitively and echoes them in the case you gave.

A HEAD request and a 1xx, 204 or 304 status only switch off the automatic Transfer-Encoding: chunked. Nothing suppresses the body itself.

end(body) on a 204 goes out with a Content-Length and the body. An empty 204 or 304 carries Content-Length: 0, by the same rule that frames an empty request. A body on a HEAD response is written where Node would discard it. Write no body on those.

The server keeps an HTTP/1.1 connection open unless the request says Connection: close. It keeps an HTTP/1.0 connection open only when the request says Connection: keep-alive. Otherwise it ends the socket after the response finishes.

A socket error on an accepted connection is emitted as clientError(error, socket). A socket that ends mid-request ends the request body.

HTTPParser is the incremental HTTP/1.1 parser that both the client and the server use, in request or response mode. It is exported so you can run it over bytes of your own. Fed from a socket, it hands you the head, the body chunks and a completion:

app.ts
import * as
import net
net
from '@fkn/lib/net'
import {
class HTTPParser
HTTPParser
} from '@fkn/lib/http'
const
const chunks: Uint8Array<ArrayBufferLike>[]
chunks
:
interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>

A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the requested number of bytes could not be allocated an exception is raised.

Uint8Array
[] = []
const
const parser: HTTPParser
parser
= new
new HTTPParser(mode: "request" | "response", handlers: ParserHandlers): HTTPParser
HTTPParser
('response', {
ParserHandlers.onHeaders: (head: ParsedHead) => void
onHeaders
:
head: ParsedHead
head
=>
head: ParsedHead
head
.
ParsedHead.statusCode?: number | undefined

response only

statusCode
, // 200, with the headers already folded and joined
ParserHandlers.onBody: (chunk: Uint8Array) => void
onBody
:
chunk: Uint8Array<ArrayBufferLike>
chunk
=>
const chunks: Uint8Array<ArrayBufferLike>[]
chunks
.
Array<Uint8Array<ArrayBufferLike>>.push(...items: Uint8Array<ArrayBufferLike>[]): number

Appends new elements to the end of an array, and returns the new length of the array.

@paramitems New elements to add to the array.

push
(
chunk: Uint8Array<ArrayBufferLike>
chunk
), // body bytes in whatever split the socket delivers
ParserHandlers.onComplete: () => void
onComplete
: () =>
const chunks: Uint8Array<ArrayBufferLike>[]
chunks
.
Array<Uint8Array<ArrayBufferLike>>.length: number

Gets or sets the length of the array. This is a number one higher than the highest index in the array.

length
// every chunk of one message, before the parser resets
})
const
const socket: net.Socket
socket
=
import net
net
.
function connect(options: SocketConnectOpts, connectionListener?: () => void): net.Socket
export connect
connect
({
TcpSocketConnectOpts.host?: string | undefined
host
: 'example.org',
TcpSocketConnectOpts.port: number
port
: 80 }, () => {
const socket: net.Socket
socket
.
Socket$2.write(buffer: Uint8Array | string, cb?: (err?: Error) => void): boolean (+1 overload)

The writable.write() method writes some data to the stream, and calls the supplied callback once the data has been fully handled. If an error occurs, the callback will be called with the error as its first argument. The callback is called asynchronously and before 'error' is emitted.

The return value is true if the internal buffer is less than the highWaterMark configured when the stream was created after admitting chunk. If false is returned, further attempts to write data to the stream should stop until the 'drain' event is emitted.

While a stream is not draining, calls to write() will buffer chunk, and return false. Once all currently buffered chunks are drained (accepted for delivery by the operating system), the 'drain' event will be emitted. Once write() returns false, do not write more chunks until the 'drain' event is emitted. While calling write() on a stream that is not draining is allowed, Node.js will buffer all written chunks until maximum memory usage occurs, at which point it will abort unconditionally. Even before it aborts, high memory usage will cause poor garbage collector performance and high RSS (which is not typically released back to the system, even after the memory is no longer required). Since TCP sockets may never drain if the remote peer does not read the data, writing a socket that is not draining may lead to a remotely exploitable vulnerability.

Writing data while the stream is not draining is particularly problematic for a Transform, because the Transform streams are paused by default until they are piped or a 'data' or 'readable' event handler is added.

If the data to be written can be generated or fetched on demand, it is recommended to encapsulate the logic into a Readable and use

pipe

. However, if calling write() is preferred, it is possible to respect backpressure and avoid memory issues using the 'drain' event:

function write(data, cb) {
if (!stream.write(data)) {
stream.once('drain', cb);
} else {
process.nextTick(cb);
}
}
// Wait for cb to be called before doing any other write.
write('hello', () => {
console.log('Write completed, do more writes now.');
});

A Writable stream in object mode will always ignore the encoding argument.

Writes data to the stream, with an explicit encoding for string data.

write
('GET /api/catalog.json HTTP/1.0\r\nHost: example.org\r\n\r\n')
})
const socket: net.Socket
socket
.
Stream.Duplex.on<"data">(eventName: "data", listener: (chunk: any) => void): net.Socket (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('data', (
chunk: Buffer<ArrayBufferLike>
chunk
:
interface Buffer<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
Buffer
) =>
const parser: HTTPParser
parser
.
HTTPParser.execute(chunk: Uint8Array): void
execute
(
chunk: Buffer<ArrayBufferLike>
chunk
))
const socket: net.Socket
socket
.
Stream.Duplex.on<"end">(eventName: "end", listener: () => void): net.Socket (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('end', () =>
const parser: HTTPParser
parser
.
HTTPParser.finish(): void
finish
()) // completes a body framed by end of stream
const socket: net.Socket
socket
.
Stream.Duplex.on<"error">(eventName: "error", listener: (err: Error) => void): net.Socket (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('error', (
error: Error
error
:
interface Error
Error
) =>
error: Error
error
.
Error.message: string
message
)

The parser decodes the head as latin1, folds a header line starting with a space or a tab onto the previous one, and skips a line without a colon. It turns set-cookie into an array, keeps the first value of the eighteen singleton headers, content-type and host among them, and joins every other duplicate with a comma. It reads the version from the start line and defaults to 1.1.

It decides the body framing in this order:

The head saysThe body is
skipBody was set on the parsernone
Transfer-Encoding containing chunkedchunked, with chunk extensions ignored and trailers skipped
a non-empty Content-Lengththat many bytes, and 0 means none
neither, on a responseeverything until the socket ends, except for 1xx, 204 and 304, which have none
neither, on a requestnone

The parser never looks at the method. The client sets skipBody for the response to a HEAD. The server sets it for a HEAD request. With bytes of your own, a HEAD carrying a Content-Length delivers that body unless you set skipBody too.

skipBody clears when a message completes, because the parser goes back to the head state so that one parser can serve a keep-alive stream.

A chunk-size line that does not parse as hex completes the message silently. trailers and rawTrailers on an IncomingMessage stay empty. finish() is what completes a body framed by end of stream.

The package has no https entry point. Nothing in it speaks TLS. An https: protocol only changes the default port, 443 instead of 80, and which port the Host header leaves off. The request goes out as plain HTTP on that socket:

app.ts
import * as
import http
http
from '@fkn/lib/http'
const
const request: http.ClientRequest
request
=
import http
http
.
function get(...args: [options: string | URL | http.ClientRequestArgs, callback?: (res: http.IncomingMessage) => void] | [url: string | URL, options: http.ClientRequestArgs, callback?: (res: http.IncomingMessage) => void]): http.ClientRequest
export get
get
({
protocol: string
protocol
: 'https:',
hostname: string
hostname
: 'example.org',
ClientRequestArgs.path?: string | undefined
path
: '/api/catalog.json' }) // port 443, Host: example.org, and plain HTTP on the wire
const request: http.ClientRequest
request
.
Stream.Writable.on<"error">(eventName: "error", listener: (err: Error) => void): http.ClientRequest (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('error', (
error: Error
error
:
interface Error
Error
) =>
error: Error
error
.
Error.message: string
message
)
const request: http.ClientRequest
request
.
ClientRequest.protocol: string
protocol
// 'https:', which changed nothing but the default port

A TLS server cannot answer an https: request made this way. When the resource is on HTTPS, cloud.fetch speaks HTTPS for you: see fetch(). When the protocol is your own, keep http for a plaintext service you control: see limitations.

lookup(hostname, options?) resolves a name through the broker and answers with { address, family }, one record by default or every record with all: true. It returns a promise rather than taking Node’s callback, and family accepts 0, 4 or 6:

app.ts
import {
const lookup: <T extends boolean = false>(hostname: string, options?: {
all?: T;
family?: 0 | 4 | 6;
}) => Promise<T extends true ? AddressLookupResult[] : AddressLookupResult | undefined>
lookup
} from '@fkn/lib/dns'
const
const first: AddressLookupResult | undefined
first
= await
lookup<false>(hostname: string, options?: {
all?: false | undefined;
family?: 0 | 4 | 6;
} | undefined): Promise<AddressLookupResult | undefined>
lookup
('example.org') // { address, family: 4 } or undefined, A answers come before AAAA
const
const every: AddressLookupResult[]
every
= await
lookup<true>(hostname: string, options?: {
all?: true | undefined;
family?: 0 | 4 | 6;
} | undefined): Promise<AddressLookupResult[]>
lookup
('example.org', {
all?: true | undefined
all
: true }) // every record, and [] when the name has no answer
const
const both: AddressLookupResult[]
both
= await
lookup<true>(hostname: string, options?: {
all?: true | undefined;
family?: 0 | 4 | 6;
} | undefined): Promise<AddressLookupResult[]>
lookup
('localhost', {
all?: true | undefined
all
: true,
family?: 0 | 4 | 6 | undefined
family
: 0 }) // [{ address: '127.0.0.1', family: 4 }, { address: '::1', family: 6 }]
const
const none: AddressLookupResult[]
none
= await
lookup<true>(hostname: string, options?: {
all?: true | undefined;
family?: 0 | 4 | 6;
} | undefined): Promise<AddressLookupResult[]>
lookup
('localhost', {
all?: true | undefined
all
: true }) // [], both records filtered by a family nobody gave

all is a generic, so the return type follows it: AddressLookupResult | undefined without it, AddressLookupResult[] with it. For a name, family: 4 asks for A records, family: 6 asks for AAAA records, and 0 or no family asks for both in parallel. The broker flattens the answers with the A records first.

The broker echoes an address literal such as 67.215.246.10 with its own family, whatever family asks for. It short-circuits localhost to 127.0.0.1, or to ::1 with family: 6. It sends any other name as a DNS over HTTPS query to https://1.1.1.1/dns-query.

lookup('localhost', { all: true }) answers [] unless you give a family. The filter compares each loopback record’s family to the one you asked for. family: 0 is what admits both.

There is no cache. A name with no answer resolves to undefined rather than rejecting: see limits and timeouts.

The sockets resolve names themselves, so net.connect needs no lookup first. A dgram.send to a name pays a lookup per datagram: see UDP.

Nothing else from Node’s dns exists. resolve*, reverse, promises and setServers are all absent. The exact shapes are in the generated API reference for @fkn/lib/dns and @fkn/lib/http.

The five errors a request or a server meets first, each linked to its row:

MessageWhat happened
getaddrinfo ENOTFOUND <hostname>The name has no answer, so the socket under the request never connected.
tcp connect to <address>:<port> timed out after 12000msThe relay did not acknowledge the connect within 12,000 ms.
@fkn/lib: no broker connection within <ms>ms, so <what> could not be requestedBrokerUnreachableError from the socket underneath, after 8,000 ms, then 1,000 ms once a call has missed. <what> is an outbound tcp socket for a request and a tcp listener for listen. In a worker, nobody ran relayWorker.
Cannot set headers after they are sent to the clientsetHeader after the head went out, code: 'ERR_HTTP_HEADERS_SENT', the code a second writeHead carries too.
Header name must be a valid HTTP token [<name>]A name outside the token set, code: 'ERR_INVALID_HTTP_TOKEN'.

The code of a socket failure does not survive the trip through the broker, so match on the message. See handling errors. Every other message, the relay’s refusals included, has its row on every error.