Package Exports
- undici
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (undici) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
undici
A 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.
Install
npm i undiciBenchmarks
Machine: 2.8GHz AMD EPYC 7402P
Node 14
http - keepalive x 6,770 ops/sec ±7.70% (75 runs sampled)
undici - pipeline x 10,627 ops/sec ±5.88% (79 runs sampled)
undici - request x 10,902 ops/sec ±1.28% (85 runs sampled)
undici - stream x 20,144 ops/sec ±0.84% (86 runs sampled)
undici - dispatch x 20,295 ops/sec ±1.00% (83 runs sampled)Node 15
http - keepalive x 10,337 ops/sec ±6.17% (71 runs sampled)
undici - pipeline x 30,387 ops/sec ±1.37% (80 runs sampled)
undici - request x 40,117 ops/sec ±3.25% (77 runs sampled)
undici - stream x 40,543 ops/sec ±1.30% (80 runs sampled)
undici - dispatch x 50,434 ops/sec ±2.08% (77 runs sampled)The benchmark is a simple hello world example using a
single unix socket with pipelining.
API
new undici.Client(url, opts)
A basic HTTP/1.1 client, mapped on top a single TCP/TLS connection. Pipelining is disabled by default.
url can be a string or a URL object.
It should only include the protocol, hostname, and the port.
Options:
socketTimeout: Number, the timeout after which a socket with active requests will time out. Monitors time between activity on a connected socket. Use0to disable it entirely. Default:30e3milliseconds (30s).socketPath: String|Null, an IPC endpoint, either Unix domain socket or Windows named pipe. Default:null.idleTimeout: Number, the timeout after which a socket without active requests will time out. Monitors time between activity on a connected socket. This value may be overriden by keep-alive hints from the server. Default:4e3milliseconds (4s).keepAlive: Boolean, enable or disable keep alive connections. Default:true.keepAliveMaxTimeout: Number, the maximum allowedidleTimeoutwhen overriden by keep-alive hints from the server. Default:600e3milliseconds (10min).keepAliveTimeoutThreshold: Number, a number subtracted from server keep-alive hints when overridingidleTimeoutto account for timing inaccuries caused by e.g. transport latency. Default:1e3milliseconds (1s).pipelining: Number, the amount of concurrent requests to be sent over the single TCP/TLS connection according to RFC7230. Carefully consider your workload and environment before enabling concurrent requests as pipelining may reduce performance if used incorrectly. Pipelining is sensitive to network stack settings as well as head of line blocking caused by e.g. long running requests. Default:1.tls: Object|Null, an options object which in the case ofhttpswill be passed totls.connect. Default:null.maxHeaderSize: Number, the maximum length of request headers in bytes. Default:16384(16KiB).headersTimeout: Number, the amount of time the parser will wait to receive the complete HTTP headers (Node 14 and above only). Default:30e3milliseconds (30s).
client.request(opts[, callback(err, data)]): Promise|Void
Performs a HTTP request.
Options:
path: Stringmethod: Stringopaque: Anybody: String|Buffer|Uint8Array|stream.Readable|NullDefault:null.headers: Object|Null, an object with header-value pairs. Default:null.signal: AbortController|EventEmitter|NullDefault:null.requestTimeout: Number, the timeout after which a request will time out, in milliseconds. Monitors time between request being enqueued and receiving a response. Use0to disable it entirely. Default:30e3milliseconds (30s).idempotent: Boolean, whether the requests can be safely retried or not. Iffalsethe request won't be sent until all preceeding requests in the pipeline has completed. Default:trueifmethodisHEADorGET.
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: Numberopaque: Anyheaders: Object, an object where all keys have been lowercased.body: stream.Readableresponse payload. 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()
})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.
Aborting a request
A request can may be aborted using either an AbortController or an EventEmitter.
To use AbortController, you will need to npm i abort-controller.
const { AbortController } = require('abort-controller')
const { Client } = require('undici')
const client = new Client('http://localhost:3000')
const abortController = new AbortController()
client.request({
path: '/',
method: 'GET',
signal: abortController.signal
}, function (err, data) {
console.log(err) // RequestAbortedError
client.close()
})
abortController.abort()Alternatively, any EventEmitter that emits an 'abort' event may be used as an abort controller:
const EventEmitter = require('events')
const { Client } = require('undici')
const client = new Client('http://localhost:3000')
const ee = new EventEmitter()
client.request({
path: '/',
method: 'GET',
signal: ee
}, function (err, data) {
console.log(err) // RequestAbortedError
client.close()
})
ee.emit('abort')Destroying the request or response body will have the same effect.
client.stream(opts, factory(data)[, callback(err)]): Promise|Void
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
client.request(opts[, callback]).
The data parameter in factory is defined as follow:
statusCode: Numberheaders: Object, an object where all keys have been lowercased.opaque: Any
The data parameter in callback is defined as follow:
opaque: Anytrailers: Object, an object where all keys have been lowercased.
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 })
}
}client.pipeline(opts, handler(data)): Duplex
For easy use with stream.pipeline.
Options:
- ... same as
client.request(opts, callback). objectMode: Boolean,trueif thehandlerwill return an object stream. Default:false
The data parameter in handler is defined as follow:
statusCode: Numberheaders: Object, an object where all keys have been lowercased.opaque: Anybody: stream.Readableresponse payload. A user must either fully consume or destroy the body unless there is an error, or no further requests will be processed.
handler should return a Readable from which the result will be
read. 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.
Returns a Duplex which writes to the request and reads from
the response.
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.createWriteStream('response.raw'),
(err) => {
if (err) {
console.error('failed')
} else {
console.log('succeeded')
}
}
)client.upgrade(opts[, callback(err, data)]): Promise|Void
Upgrade to a different protocol.
Options:
path: Stringopaque: Anymethod: StringDefault:GETheaders: Object|Null, an object with header-value pairs. Default:nullsignal: AbortController|EventEmitter|Null. Default:nullrequestTimeout: Number, the timeout after which a request will time out, in milliseconds. Monitors time between request being enqueued and receiving a response. Use0to disable it entirely. Default:30e3milliseconds (30s).protocol: String, a string of comma separated protocols, in descending preference order. Default:Websocket.
The data parameter in callback is defined as follow:
headers: Object, an object where all keys have been lowercased.socket: Duplexopaque
Returns a promise if no callback is provided.
client.connect(opts[, callback(err, data)]): Promise|Void
Starts two-way communications with the requested resource.
Options:
path: Stringopaque: Anyheaders: Object|Null, an object with header-value pairs. Default:nullsignal: AbortController|EventEmitter|Null. Default:nullrequestTimeout: Number, the timeout after which a request will time out, in milliseconds. Monitors time between request being enqueued and receiving a response. Use0to disable it entirely. Default:30e3milliseconds (30s).
The data parameter in callback is defined as follow:
statusCode: Numberheaders: Object, an object where all keys have been lowercased.socket: Duplexopaque: Any
Returns a promise if no callback is provided.
client.dispatch(opts, handler): Promise|Void
This is the low level API which all the preceeding APIs are implemented on top of.
This API is expected to evolve through semver-major versions and is less stable than the preceeding higher level APIs. It is primarily intended for library developers who implement higher level APIs on top of this.
Options:
path: Stringmethod: Stringbody: String|Buffer|Uint8Array|stream.Readable|NullDefault:null.headers: Object|Null, an object with header-value pairs. Default:null.requestTimeout: Number, the timeout after which a request will time out, in milliseconds. Monitors time between request being enqueued and receiving a response. Use0to disable it entirely. Default:30e3milliseconds (30s).idempotent: Boolean, whether the requests can be safely retried or not. Iffalsethe request won't be sent until all preceeding requests in the pipeline has completed. Default:trueifmethodisHEADorGET.
The handler parameter is defined as follow:
onConnect(abort), invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails.abort(): Void, abort request.
onUpgrade(statusCode, headers, socket): Void, invoked when request is upgraded either due to aUpgradeheader orCONNECTmethod.statusCode: Numberheaders: Array|Nullsocket: Duplex
onHeaders(statusCode, headers, resume): Void, invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers.statusCode: Numberheaders: Array|Null, an array of key-value pairs. Keys are not automatically lowercased.resume(): Void, resumeonDataafter returningfalse.
onData(chunk): Boolean, invoked when response payload data is received.chunk: Buffer
onComplete(trailers): Void, invoked when response payload and trailers have been received and the request has completed.trailers: Array|Null
onError(err): Void, invoked when an error has occured.err: Error
The caller is responsible for handling the body argument, in terms of 'error' events and destroy():ing up until
the onConnect handler has been invoked.
client.close([callback]): Promise|Void
Closes the client and gracefully waits fo enqueued requests to complete before invoking the callback.
Returns a promise if no callback is provided.
client.destroy([err][, callback]): Promise|Void
Destroy the client abruptly with the given err. All the pending and running
requests will be asynchronously aborted and error. Waits until socket is closed
before invoking the callback. Since this operation is asynchronously dispatched
there might still be some progress on dispatched requests.
Returns a promise if no callback is provided.
client.pipelining: Number
Property to get and set the pipelining factor.
client.pending: Number
Number of queued requests.
client.running: Number
Number of inflight requests.
client.size: Number
Number of pending and running requests.
client.connected: Boolean
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.
client.busy: Boolean
True if pipeline is saturated or blocked. Indicicates whether dispatching further requests is meaningful.
client.closed: Boolean
True after client.close() has been called.
client.destroyed: Boolean
True after client.destroyed() has been called or client.close() has been
called and the client shutdown has completed.
Events
'drain', emitted when pipeline is no longer fully saturated.'connect', emitted when a socket has been created and connected. The client will connect onceclient.size > 0.'disconnect', emitted when socket has disconnected. The first argument of the event is the error which caused the socket to disconnect. The client will reconnect if or onceclient.size > 0.
new undici.Pool(url, opts)
A pool of Client connected to the same upstream target.
Options:
- ... same as
Client. connections, the number of clients to create. Default10.
Pool does not guarantee that requests are dispatched in
order of invocation.
pool.request(opts[, callback]): Promise|Void
Calls client.request(opts, callback) on one of the clients.
pool.stream(opts, factory[, callback]): Promise|Void
Calls client.stream(opts, factory, callback) on one of the clients.
pool.pipeline(opts, handler): Duplex
Calls client.pipeline(opts, handler) on one of the clients.
pool.upgrade(opts[, callback]): Promise|Void
Calls client.upgrade(opts, callback) on one of the clients.
pool.connect(opts[, callback]): Promise|Void
Calls client.connect(opts, callback) on one of the clients.
pool.dispatch(opts, handler): Void
Calls client.dispatch(opts, handler) on one of the clients.
pool.close([callback]): Promise|Void
Calls client.close(callback) on all the clients.
pool.destroy([err][, callback]): Promise|Void
Calls client.destroy(err, callback) on all the clients.
undici.errors
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 |
passed an invalid argument. |
InvalidReturnValueError |
UND_ERR_INVALID_RETURN_VALUE |
returned an invalid value. |
SocketTimeoutError |
UND_ERR_SOCKET_TIMEOUT |
a socket exceeds the socketTimeout option. |
RequestTimeoutError |
UND_ERR_REQUEST_TIMEOUT |
a request exceeds the requestTimeout option. |
RequestAbortedError |
UND_ERR_ABORTED |
the request has been aborted by the user |
ClientDestroyedError |
UND_ERR_DESTROYED |
trying to use a destroyed client. |
ClientClosedError |
UND_ERR_CLOSED |
trying to use a closed client. |
SocketError |
UND_ERR_SOCKET |
there is an error with the socket. |
NotSupportedError |
UND_ERR_NOT_SUPPORTED |
encountered unsupported functionality. |
ContentLengthMismatchError |
UND_ERR_CONTENT_LENGTH_MISMATCH |
body does not match content-length header |
InformationalError |
UND_ERR_INFO |
expected error with reason |
Specification Compliance
This section documents parts of the HTTP/1.1 specification which Undici does not support or does not fully implement.
Expect
Undici does not support the Expect request header field. The request
body is always immediately sent and the 100 Continue response will be
ignored.
Refs: https://tools.ietf.org/html/rfc7231#section-5.1.1
Pipelining
Uncidi will only use pipelining if configured with a pipelining factor
greater than 1.
Undici always assumes that connections are persistent and will immediatly pipeline requests, without checking whether the connection is persistent. Hence, automatic fallback to HTTP/1.0 or HTTP/1.1 without pipelining is not supported.
Undici will immediately pipeline when retrying requests afters a failed connection. However, Undici will not retry the first remaining requests in the prior pipeline and instead error the corresponding callback/promise/stream.
Refs: https://tools.ietf.org/html/rfc2616#section-8.1.2.2
Refs: https://tools.ietf.org/html/rfc7230#section-6.3.2
Collaborators
License
MIT