|
| 1 | +# undici |
| 2 | + |
| 3 | +[](https://github.com/nodejs/undici/actions/workflows/nodejs.yml) [](http://standardjs.com/) [](https://badge.fury.io/js/undici) [](https://codecov.io/gh/nodejs/undici) |
| 4 | + |
| 5 | +A HTTP/1.1 client, written from scratch for Node.js. |
| 6 | + |
| 7 | +> Undici means eleven in Italian. 1.1 -> 11 -> Eleven -> Undici. |
| 8 | +It is also a Stranger Things reference. |
| 9 | + |
| 10 | +Have a question about using Undici? Open a [Q&A Discussion](https://github.com/nodejs/undici/discussions/new) or join our official OpenJS [Slack](https://openjs-foundation.slack.com/archives/C01QF9Q31QD) channel. |
| 11 | + |
| 12 | +## Install |
| 13 | + |
| 14 | +``` |
| 15 | +npm i undici |
| 16 | +``` |
| 17 | + |
| 18 | +## Benchmarks |
| 19 | + |
| 20 | +The benchmark is a simple `hello world` [example](benchmarks/benchmark.js) using a |
| 21 | +number of unix sockets (connections) with a pipelining depth of 10 running on Node 16. |
| 22 | +The benchmarks below have the [simd](https://github.com/WebAssembly/simd) feature enabled. |
| 23 | + |
| 24 | +### Connections 1 |
| 25 | + |
| 26 | +| Tests | Samples | Result | Tolerance | Difference with slowest | |
| 27 | +|---------------------|---------|---------------|-----------|-------------------------| |
| 28 | +| http - no keepalive | 15 | 4.63 req/sec | ± 2.77 % | - | |
| 29 | +| http - keepalive | 10 | 4.81 req/sec | ± 2.16 % | + 3.94 % | |
| 30 | +| undici - stream | 25 | 62.22 req/sec | ± 2.67 % | + 1244.58 % | |
| 31 | +| undici - dispatch | 15 | 64.33 req/sec | ± 2.47 % | + 1290.24 % | |
| 32 | +| undici - request | 15 | 66.08 req/sec | ± 2.48 % | + 1327.88 % | |
| 33 | +| undici - pipeline | 10 | 66.13 req/sec | ± 1.39 % | + 1329.08 % | |
| 34 | + |
| 35 | +### Connections 50 |
| 36 | + |
| 37 | +| Tests | Samples | Result | Tolerance | Difference with slowest | |
| 38 | +|---------------------|---------|------------------|-----------|-------------------------| |
| 39 | +| http - no keepalive | 50 | 3546.49 req/sec | ± 2.90 % | - | |
| 40 | +| http - keepalive | 15 | 5692.67 req/sec | ± 2.48 % | + 60.52 % | |
| 41 | +| undici - pipeline | 25 | 8478.71 req/sec | ± 2.62 % | + 139.07 % | |
| 42 | +| undici - request | 20 | 9766.66 req/sec | ± 2.79 % | + 175.39 % | |
| 43 | +| undici - stream | 15 | 10109.74 req/sec | ± 2.94 % | + 185.06 % | |
| 44 | +| undici - dispatch | 25 | 10949.73 req/sec | ± 2.54 % | + 208.75 % | |
| 45 | + |
| 46 | +## Quick Start |
| 47 | + |
| 48 | +```js |
| 49 | +import { request } from 'undici' |
| 50 | + |
| 51 | +const { |
| 52 | + statusCode, |
| 53 | + headers, |
| 54 | + trailers, |
| 55 | + body |
| 56 | +} = await request('http://localhost:3000/foo') |
| 57 | + |
| 58 | +console.log('response received', statusCode) |
| 59 | +console.log('headers', headers) |
| 60 | + |
| 61 | +for await (const data of body) { |
| 62 | + console.log('data', data) |
| 63 | +} |
| 64 | + |
| 65 | +console.log('trailers', trailers) |
| 66 | +``` |
| 67 | + |
| 68 | +Using [the body mixin from the Fetch Standard](https://fetch.spec.whatwg.org/#body-mixin). |
| 69 | + |
| 70 | +```js |
| 71 | +import { request } from 'undici' |
| 72 | + |
| 73 | +const { |
| 74 | + statusCode, |
| 75 | + headers, |
| 76 | + trailers, |
| 77 | + body |
| 78 | +} = await request('http://localhost:3000/foo') |
| 79 | + |
| 80 | +console.log('response received', statusCode) |
| 81 | +console.log('headers', headers) |
| 82 | +console.log('data', await body.json()) |
| 83 | +console.log('trailers', trailers) |
| 84 | +``` |
| 85 | + |
| 86 | +## Common API Methods |
| 87 | + |
| 88 | +This section documents our most commonly used API methods. Additional APIs are documented in their own files within the [docs](./docs/) folder and are accessible via the navigation list on the left side of the docs site. |
| 89 | + |
| 90 | +### `undici.request([url, options]): Promise` |
| 91 | + |
| 92 | +Arguments: |
| 93 | + |
| 94 | +* **url** `string | URL | UrlObject` |
| 95 | +* **options** [`RequestOptions`](./docs/api/Dispatcher.md#parameter-requestoptions) |
| 96 | + * **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcher) |
| 97 | + * **method** `String` - Default: `PUT` if `options.body`, otherwise `GET` |
| 98 | + * **maxRedirections** `Integer` - Default: `0` |
| 99 | + |
| 100 | +Returns a promise with the result of the `Dispatcher.request` method. |
| 101 | + |
| 102 | +Calls `options.dispatcher.request(options)`. |
| 103 | + |
| 104 | +See [Dispatcher.request](./docs/api/Dispatcher.md#dispatcherrequestoptions-callback) for more details. |
| 105 | + |
| 106 | +### `undici.stream([url, options, ]factory): Promise` |
| 107 | + |
| 108 | +Arguments: |
| 109 | + |
| 110 | +* **url** `string | URL | UrlObject` |
| 111 | +* **options** [`StreamOptions`](./docs/api/Dispatcher.md#parameter-streamoptions) |
| 112 | + * **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcher) |
| 113 | + * **method** `String` - Default: `PUT` if `options.body`, otherwise `GET` |
| 114 | + * **maxRedirections** `Integer` - Default: `0` |
| 115 | +* **factory** `Dispatcher.stream.factory` |
| 116 | + |
| 117 | +Returns a promise with the result of the `Dispatcher.stream` method. |
| 118 | + |
| 119 | +Calls `options.dispatcher.stream(options, factory)`. |
| 120 | + |
| 121 | +See [Dispatcher.stream](docs/api/Dispatcher.md#dispatcherstreamoptions-factory-callback) for more details. |
| 122 | + |
| 123 | +### `undici.pipeline([url, options, ]handler): Duplex` |
| 124 | + |
| 125 | +Arguments: |
| 126 | + |
| 127 | +* **url** `string | URL | UrlObject` |
| 128 | +* **options** [`PipelineOptions`](docs/api/Dispatcher.md#parameter-pipelineoptions) |
| 129 | + * **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcher) |
| 130 | + * **method** `String` - Default: `PUT` if `options.body`, otherwise `GET` |
| 131 | + * **maxRedirections** `Integer` - Default: `0` |
| 132 | +* **handler** `Dispatcher.pipeline.handler` |
| 133 | + |
| 134 | +Returns: `stream.Duplex` |
| 135 | + |
| 136 | +Calls `options.dispatch.pipeline(options, handler)`. |
| 137 | + |
| 138 | +See [Dispatcher.pipeline](docs/api/Dispatcher.md#dispatcherpipelineoptions-handler) for more details. |
| 139 | + |
| 140 | +### `undici.connect([url, options]): Promise` |
| 141 | + |
| 142 | +Starts two-way communications with the requested resource using [HTTP CONNECT](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/CONNECT). |
| 143 | + |
| 144 | +Arguments: |
| 145 | + |
| 146 | +* **url** `string | URL | UrlObject` |
| 147 | +* **options** [`ConnectOptions`](docs/api/Dispatcher.md#parameter-connectoptions) |
| 148 | + * **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcher) |
| 149 | + * **maxRedirections** `Integer` - Default: `0` |
| 150 | +* **callback** `(err: Error | null, data: ConnectData | null) => void` (optional) |
| 151 | + |
| 152 | +Returns a promise with the result of the `Dispatcher.connect` method. |
| 153 | + |
| 154 | +Calls `options.dispatch.connect(options)`. |
| 155 | + |
| 156 | +See [Dispatcher.connect](docs/api/Dispatcher.md#dispatcherconnectoptions-callback) for more details. |
| 157 | + |
| 158 | +### `undici.fetch(input[, init]): Promise` |
| 159 | + |
| 160 | +Implements [fetch](https://fetch.spec.whatwg.org/#fetch-method). |
| 161 | + |
| 162 | +* https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch |
| 163 | +* https://fetch.spec.whatwg.org/#fetch-method |
| 164 | + |
| 165 | +Only supported on Node 16.5+. |
| 166 | + |
| 167 | +This is [experimental](https://nodejs.org/api/documentation.html#documentation_stability_index) and is not yet fully compliant with the Fetch Standard. |
| 168 | +We plan to ship breaking changes to this feature until it is out of experimental. |
| 169 | +Help us improve the test coverage by following instructions at [nodejs/undici/#951](https://github.com/nodejs/undici/issues/951). |
| 170 | + |
| 171 | +Basic usage example: |
| 172 | + |
| 173 | +```js |
| 174 | + import {fetch} from 'undici'; |
| 175 | + |
| 176 | + async function fetchJson() { |
| 177 | + const res = await fetch('https://example.com') |
| 178 | + const json = await res.json() |
| 179 | + console.log(json); |
| 180 | + } |
| 181 | +``` |
| 182 | + |
| 183 | + |
| 184 | +#### `request.body` |
| 185 | + |
| 186 | +A body can be of the following types: |
| 187 | + |
| 188 | +- ArrayBuffer |
| 189 | +- ArrayBufferView |
| 190 | +- AsyncIterables |
| 191 | +- Blob |
| 192 | +- Iterables |
| 193 | +- String |
| 194 | +- URLSearchParams |
| 195 | +- FormData |
| 196 | + |
| 197 | +In this implementation of fetch, ```request.body``` now accepts ```Async Iterables```. It is not present in the [Fetch Standard.](https://fetch.spec.whatwg.org) |
| 198 | + |
| 199 | +```js |
| 200 | +import { fetch } from "undici"; |
| 201 | + |
| 202 | +const data = { |
| 203 | + async *[Symbol.asyncIterator]() { |
| 204 | + yield "hello"; |
| 205 | + yield "world"; |
| 206 | + }, |
| 207 | +}; |
| 208 | + |
| 209 | +(async () => { |
| 210 | + await fetch("https://example.com", { body: data, method: 'POST' }); |
| 211 | +})(); |
| 212 | +``` |
| 213 | + |
| 214 | +#### `response.body` |
| 215 | + |
| 216 | +Nodejs has two kinds of streams: [web streams](https://nodejs.org/dist/latest-v16.x/docs/api/webstreams.html) which follow the API of the WHATWG web standard found in browsers, and an older Node-specific [streams API](https://nodejs.org/api/stream.html). `response.body` returns a readable web stream. If you would prefer to work with a Node stream you can convert a web stream using `.fromWeb()`. |
| 217 | + |
| 218 | +```js |
| 219 | + import {fetch} from 'undici'; |
| 220 | + import {Readable} from 'node:stream'; |
| 221 | + |
| 222 | + async function fetchStream() { |
| 223 | + const response = await fetch('https://example.com') |
| 224 | + const readableWebStream = response.body; |
| 225 | + const readableNodeStream = Readable.fromWeb(readableWebStream); |
| 226 | + } |
| 227 | +``` |
| 228 | + |
| 229 | +#### Specification Compliance |
| 230 | + |
| 231 | +This section documents parts of the [Fetch Standard](https://fetch.spec.whatwg.org) which Undici does |
| 232 | +not support or does not fully implement. |
| 233 | + |
| 234 | +##### Garbage Collection |
| 235 | + |
| 236 | +* https://fetch.spec.whatwg.org/#garbage-collection |
| 237 | + |
| 238 | +The [Fetch Standard](https://fetch.spec.whatwg.org) allows users to skip consuming the response body by relying on |
| 239 | +[garbage collection](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management#garbage_collection) to release connection resources. Undici does not do the same. Therefore, it is important to always either consume or cancel the response body. |
| 240 | + |
| 241 | +Garbage collection in Node is less aggressive and deterministic |
| 242 | +(due to the lack of clear idle periods that browser have through the rendering refresh rate) |
| 243 | +which means that leaving the release of connection resources to the garbage collector can lead |
| 244 | +to excessive connection usage, reduced performance (due to less connection re-use), and even |
| 245 | +stalls or deadlocks when running out of connections. |
| 246 | + |
| 247 | +```js |
| 248 | +// Do |
| 249 | +const headers = await fetch(url) |
| 250 | + .then(async res => { |
| 251 | + for await (const chunk of res.body) { |
| 252 | + // force consumption of body |
| 253 | + } |
| 254 | + return res.headers |
| 255 | + }) |
| 256 | + |
| 257 | +// Do not |
| 258 | +const headers = await fetch(url) |
| 259 | + .then(res => res.headers) |
| 260 | +``` |
| 261 | + |
| 262 | +### `undici.upgrade([url, options]): Promise` |
| 263 | + |
| 264 | +Upgrade to a different protocol. See [MDN - HTTP - Protocol upgrade mechanism](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism) for more details. |
| 265 | + |
| 266 | +Arguments: |
| 267 | + |
| 268 | +* **url** `string | URL | UrlObject` |
| 269 | +* **options** [`UpgradeOptions`](docs/api/Dispatcher.md#parameter-upgradeoptions) |
| 270 | + * **dispatcher** `Dispatcher` - Default: [getGlobalDispatcher](#undicigetglobaldispatcher) |
| 271 | + * **maxRedirections** `Integer` - Default: `0` |
| 272 | +* **callback** `(error: Error | null, data: UpgradeData) => void` (optional) |
| 273 | + |
| 274 | +Returns a promise with the result of the `Dispatcher.upgrade` method. |
| 275 | + |
| 276 | +Calls `options.dispatcher.upgrade(options)`. |
| 277 | + |
| 278 | +See [Dispatcher.upgrade](docs/api/Dispatcher.md#dispatcherupgradeoptions-callback) for more details. |
| 279 | + |
| 280 | +### `undici.setGlobalDispatcher(dispatcher)` |
| 281 | + |
| 282 | +* dispatcher `Dispatcher` |
| 283 | + |
| 284 | +Sets the global dispatcher used by Common API Methods. |
| 285 | + |
| 286 | +### `undici.getGlobalDispatcher()` |
| 287 | + |
| 288 | +Gets the global dispatcher used by Common API Methods. |
| 289 | + |
| 290 | +Returns: `Dispatcher` |
| 291 | + |
| 292 | +### `UrlObject` |
| 293 | + |
| 294 | +* **port** `string | number` (optional) |
| 295 | +* **path** `string` (optional) |
| 296 | +* **pathname** `string` (optional) |
| 297 | +* **hostname** `string` (optional) |
| 298 | +* **origin** `string` (optional) |
| 299 | +* **protocol** `string` (optional) |
| 300 | +* **search** `string` (optional) |
| 301 | + |
| 302 | +## Specification Compliance |
| 303 | + |
| 304 | +This section documents parts of the HTTP/1.1 specification which Undici does |
| 305 | +not support or does not fully implement. |
| 306 | + |
| 307 | +### Expect |
| 308 | + |
| 309 | +Undici does not support the `Expect` request header field. The request |
| 310 | +body is always immediately sent and the `100 Continue` response will be |
| 311 | +ignored. |
| 312 | + |
| 313 | +Refs: https://tools.ietf.org/html/rfc7231#section-5.1.1 |
| 314 | + |
| 315 | +### Pipelining |
| 316 | + |
| 317 | +Undici will only use pipelining if configured with a `pipelining` factor |
| 318 | +greater than `1`. |
| 319 | + |
| 320 | +Undici always assumes that connections are persistent and will immediately |
| 321 | +pipeline requests, without checking whether the connection is persistent. |
| 322 | +Hence, automatic fallback to HTTP/1.0 or HTTP/1.1 without pipelining is |
| 323 | +not supported. |
| 324 | + |
| 325 | +Undici will immediately pipeline when retrying requests after a failed |
| 326 | +connection. However, Undici will not retry the first remaining requests in |
| 327 | +the prior pipeline and instead error the corresponding callback/promise/stream. |
| 328 | + |
| 329 | +Undici will abort all running requests in the pipeline when any of them are |
| 330 | +aborted. |
| 331 | + |
| 332 | +* Refs: https://tools.ietf.org/html/rfc2616#section-8.1.2.2 |
| 333 | +* Refs: https://tools.ietf.org/html/rfc7230#section-6.3.2 |
| 334 | + |
| 335 | +### Manual Redirect |
| 336 | + |
| 337 | +Since it is not possible to manually follow an HTTP redirect on server-side, |
| 338 | +Undici returns the actual response instead of an `opaqueredirect` filtered one |
| 339 | +when invoked with a `manual` redirect. This aligns `fetch()` with the other |
| 340 | +implementations in Deno and Cloudflare Workers. |
| 341 | + |
| 342 | +Refs: https://fetch.spec.whatwg.org/#atomic-http-redirect-handling |
| 343 | + |
| 344 | +## Collaborators |
| 345 | + |
| 346 | +* [__Daniele Belardi__](https://github.com/dnlup), <https://www.npmjs.com/~dnlup> |
| 347 | +* [__Ethan Arrowood__](https://github.com/ethan-arrowood), <https://www.npmjs.com/~ethan_arrowood> |
| 348 | +* [__Matteo Collina__](https://github.com/mcollina), <https://www.npmjs.com/~matteo.collina> |
| 349 | +* [__Robert Nagy__](https://github.com/ronag), <https://www.npmjs.com/~ronag> |
| 350 | +* [__Szymon Marczak__](https://github.com/szmarczak), <https://www.npmjs.com/~szmarczak> |
| 351 | +* [__Tomas Della Vedova__](https://github.com/delvedor), <https://www.npmjs.com/~delvedor> |
| 352 | + |
| 353 | +### Releasers |
| 354 | + |
| 355 | +* [__Ethan Arrowood__](https://github.com/ethan-arrowood), <https://www.npmjs.com/~ethan_arrowood> |
| 356 | +* [__Matteo Collina__](https://github.com/mcollina), <https://www.npmjs.com/~matteo.collina> |
| 357 | +* [__Robert Nagy__](https://github.com/ronag), <https://www.npmjs.com/~ronag> |
| 358 | + |
| 359 | +## License |
| 360 | + |
| 361 | +MIT |
0 commit comments