An HTTP/1.1 client, written from scratch for Node.js.
Undici means eleven in Italian. 1.1 -> 11 -> Eleven -> Undici. It is also a Stranger Things reference.
npm i undici
Machine: 2.7 GHz Quad-Core Intel Core i7 Configuration: Node v14.2, HTTP/1.1 without TLS, 100 connections
http - keepalive - pipe x 5,120 ops/sec ±10.80% (65 runs sampled)
undici - pipeline - pipe x 6,227 ops/sec ±11.44% (71 runs sampled)
undici - request - pipe x 8,685 ops/sec ±8.96% (67 runs sampled)
undici - stream - pipe x 11,453 ops/sec ±3.69% (79 runs sampled)
The benchmark is a simple hello world
example.
A basic HTTP/1.1 client, mapped on top a single TCP/TLS connection. Keepalive is enabled by default, and it cannot be turned off.
The url
will be used to extract the protocol and the domain/IP
address. The path is discarded.
Options:
-
timeout
, the timeout after which a request will time out, in milliseconds. Default:30e3
milliseconds (30s). -
maxAbortedPayload
, the maximum number of bytes read after which an aborted response will close the connection. Closing the connection will error other inflight requests in the pipeline. Default:1e6
bytes (1MiB). -
pipelining
, the amount of concurrent requests to be sent over the single TCP/TLS connection according to RFC7230. Default:1
.
Performs an HTTP request.
Options:
path
method
body
, it can be aString
, aBuffer
,Uint8Array
or astream.Readable
.headers
, an object with header-value pairs.idempotent
, whether the requests can be safely retried or not. Iffalse
the request won't be sent until all preceeding requests in the pipeline has completed. Default:true
ifmethod
isHEAD
orGET
.
Headers are represented by an object like this:
{
'content-length': '123',
'content-type': 'text/plain',
connection: 'keep-alive',
host: 'mysite.com',
accept: '*/*'
}
Keys are lowercased. Values are not modified.
If you don't specify a host
header, it will be derived from the url
of the client instance.
The data
parameter in callback
is defined as follow:
statusCode
headers
body
, astream.Readable
with the body to read. A user must either fully consume or destroy the body unless there is an error, or no further requests will be processed.
Returns a promise if no callback is provided.
Example:
const { Client } = require('undici')
const client = new Client(`http://localhost:3000`)
client.request({
path: '/',
method: 'GET'
}, function (err, data) {
if (err) {
// handle this in some way!
return
}
const {
statusCode,
headers,
body
} = data
console.log('response received', statusCode)
console.log('headers', headers)
body.setEncoding('utf8')
body.on('data', console.log)
client.close()
})
Abortion is supported by destroying the request or response body.
// Abort while sending request.
const body = new stream.Passthrough()
const promise = client.request({
path: '/',
method: 'POST',
body
})
body.destroy()
const { statusCode, headers } = await promise
// Abort while reading response.
const { statusCode, headers, body } = await client.request({
path: '/',
method: 'GET'
})
body.destroy()
Promises and async await are supported as well!
const { statusCode, headers, body } = await client.request({
path: '/',
method: 'GET'
})
Non-idempotent requests will not be pipelined in order to avoid indirect failures.
Idempotent requests will be automatically retried if they fail due to indirect failure from the request at the head of the pipeline. This does not apply to idempotent requests with a stream request body.
A faster version of request
.
Unlike request
this method expects factory
to return a Writable
which the response will be
written to. This improves performance by avoiding
creating an intermediate Readable
when the user
expects to directly pipe the response body to a
Writable
.
Options:
- ... same as
request
. opaque
, passed asopaque
tofactory
. Used to avoid creating a closure.
The data
parameter in factory
is defined as follow:
statusCode
headers
opaque
Returns a promise if no callback is provided.
const { Client } = require('undici')
const client = new Client(`http://localhost:3000`)
const fs = require('fs')
client.stream({
path: '/',
method: 'GET',
opaque: filename
}, ({ statusCode, headers, opaque: filename }) => {
console.log('response received', statusCode)
console.log('headers', headers)
return fs.createWriteStream(filename)
}, (err) => {
if (err) {
console.error('failure', err)
} else {
console.log('success')
}
})
opaque
makes it possible to avoid creating a closure
for the factory
method:
function (req, res) {
return client.stream({ ...opts, opaque: res }, proxy)
}
Instead of:
function (req, res) {
return client.stream(opts, (data) => {
// Creates closure to capture `res`.
proxy({ ...data, opaque: res })
}
}
For easy use with stream.pipeline
.
Options:
- ... same as
request
.
The data
parameter in handler
is defined as follow:
statusCode
headers
body
, astream.Readable
with the body to read. A user must either fully consume or destroy the body unless there is an error, or no further requests will be processed.
Unlike request
this method expects handler
to return a Writable
which the response will be
written to. Usually it should just return the body
argument unless some kind of transformation needs
to be performed based on e.g. headers
or statusCode
.
The handler
should validate the response and save any
required state. If there is an error it should be thrown.
const { Client } = require('undici')
const client = new Client(`http://localhost:3000`)
const fs = require('fs')
const stream = require('stream')
stream.pipeline(
fs.createReadStream('source.raw'),
client.pipeline({
path: '/',
method: 'PUT',
}, ({ statusCode, headers, body }) => {
if (statusCode !== 201) {
throw new Error('invalid response')
}
if (isZipped(headers)) {
return pipeline(body, unzip(), () => {})
}
return body
}),
fs.createReadStream('response.raw'),
(err) => {
if (err) {
console.error('failed')
} else {
console.log('succeeded')
}
}
)
Closes the client and gracefully waits fo enqueued requests to complete before invoking the callback.
Returns a promise if no callback is provided.
Destroy the client abruptly with the given err
. All the current and enqueued
requests will be aborted and error. Waits until socket is closed before
invoking the callback.
Returns a promise if no callback is provided.
Property to get and set the pipelining factor.
Number of queued requests.
Number of inflight requests.
Number of queued and inflight requests.
True if the client has an active connection. The client will lazily
create a connection when it receives a request and will destroy it
if there is no activity for the duration of the timeout
value.
True if client.size
is greater than the client.pipelining
factor.
Keeping a client full ensures that once a inflight requests finishes
the the pipeline will schedule new one and keep the pipeline saturated.
True after client.close()
has been called.
True after client.destroyed()
has been called or client.close()
has been
called and the client shutdown has completed.
-
'drain'
, emitted whenclient.size
decreases to0
and the client is not closed or destroyed. -
'connect'
, emitted when a socket has been created and connected. The client will connect onceclient.size > 0
. -
'reconnect'
, emitted when socket has disconnected. The client will reconnect if or onceclient.size > 0
.
A pool of Client
connected to the same upstream target.
A pool creates a fixed number of Client
Options:
- ... same as
new Client
. connections
, the number of clients to create. Default100
.
Calls client.request(req, callback)
on one of the clients.
Calls client.stream(req, factory, callback)
on one of the clients.
Calls client.pipeline(req, handler)
on one of the clients.
Calls client.close(callback)
on all the clients.
Calls client.destroy(err, callback)
on all the clients.
Undici exposes a variety of error objects that you can use to enhance your error handling.
You can find all the error objects inside the errors
key.
const { errors } = require('undici')
Error | Error Codes | Description |
---|---|---|
InvalidArgumentError |
UND_ERR_INVALID_ARG |
Generated when in case of a bad configuration. |
TimeoutError |
UND_ERR_TIMEOUT |
Generated when a request exceeds the timeout option. |
RequestAbortedError |
UND_ERR_ABORTED |
Generated if the request has been aborted by the user |
ClientDestroyedError |
UND_ERR_DESTROYED |
Generated when trying to use a destroyed client. |
ClientClosedError |
UND_ERR_CLOSED |
Generated when trying to use a closed client. |
SocketError |
UND_ERR_SOCKET |
Generated if there is an error with the socket. |
MIT