@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:
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.
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.
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.
@param ― text A valid JSON string.
@param ― reviver 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.
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.
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().
@since ― v8.0.0
@param ― error Optional, an error to emit with 'error' event.
destroy() // the socket goes with the request, and its busy token with it
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.
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():
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.
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.
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';
constfile= 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.
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.
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:
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.
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().
@since ― v8.0.0
@param ― error Optional, an error to emit with 'error' event.
destroy()) // the socket goes with the request, and its busy token with it
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.
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:
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';
constfile= 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(
varJSON:JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
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.
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:
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.
constserver= 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().
@since ― v0.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 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:
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.
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:
functionwrite(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.
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.
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.
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.
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 says
The body is
skipBody was set on the parser
none
Transfer-Encoding containing chunked
chunked, with chunk extensions ignored and trailers skipped
a non-empty Content-Length
that many bytes, and 0 means none
neither, on a response
everything until the socket ends, except for 1xx, 204 and 304, which have none
neither, on a request
none
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:
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.
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:
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.
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.