Comparing version 6.8.0 to 6.9.0
@@ -32,3 +32,4 @@ # Class: Client | ||
* **strictContentLength** `Boolean` (optional) - Default: `true` - Whether to treat request content length mismatches as errors. If true, an error is thrown when the request content-length header doesn't match the length of the request body. | ||
* **interceptors** `{ Client: DispatchInterceptor[] }` - Default: `[RedirectInterceptor]` - A list of interceptors that are applied to the dispatch method. Additional logic can be applied (such as, but not limited to: 302 status code handling, authentication, cookies, compression and caching). Note that the behavior of interceptors is Experimental and might change at any given time. | ||
<!-- TODO: Remove once we drop its support --> | ||
* **interceptors** `{ Client: DispatchInterceptor[] }` - Default: `[RedirectInterceptor]` - A list of interceptors that are applied to the dispatch method. Additional logic can be applied (such as, but not limited to: 302 status code handling, authentication, cookies, compression and caching). Note that the behavior of interceptors is Experimental and might change at any given time. **Note: this is deprecated in favor of [Dispatcher#compose](./Dispatcher.md#dispatcher). Support will be droped in next major.** | ||
* **autoSelectFamily**: `boolean` (optional) - Default: depends on local Node version, on Node 18.13.0 and above is `false`. Enables a family autodetection algorithm that loosely implements section 5 of [RFC 8305](https://tools.ietf.org/html/rfc8305#section-5). See [here](https://nodejs.org/api/net.html#socketconnectoptions-connectlistener) for more details. This option is ignored if not supported by the current Node version. | ||
@@ -35,0 +36,0 @@ * **autoSelectFamilyAttemptTimeout**: `number` - Default: depends on local Node version, on Node 18.13.0 and above is `250`. The amount of time in milliseconds to wait for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option. See [here](https://nodejs.org/api/net.html#socketconnectoptions-connectlistener) for more details. |
@@ -820,2 +820,137 @@ # Dispatcher | ||
### `Dispatcher.compose(interceptors[, interceptor])` | ||
Compose a new dispatcher from the current dispatcher and the given interceptors. | ||
> _Notes_: | ||
> - The order of the interceptors matters. The first interceptor will be the first to be called. | ||
> - It is important to note that the `interceptor` function should return a function that follows the `Dispatcher.dispatch` signature. | ||
> - Any fork of the chain of `interceptors` can lead to unexpected results. | ||
Arguments: | ||
* **interceptors** `Interceptor[interceptor[]]`: It is an array of `Interceptor` functions passed as only argument, or several interceptors passed as separate arguments. | ||
Returns: `Dispatcher`. | ||
#### Parameter: `Interceptor` | ||
A function that takes a `dispatch` method and returns a `dispatch`-like function. | ||
#### Example 1 - Basic Compose | ||
```js | ||
const { Client, RedirectHandler } = require('undici') | ||
const redirectInterceptor = dispatch => { | ||
return (opts, handler) => { | ||
const { maxRedirections } = opts | ||
if (!maxRedirections) { | ||
return dispatch(opts, handler) | ||
} | ||
const redirectHandler = new RedirectHandler( | ||
dispatch, | ||
maxRedirections, | ||
opts, | ||
handler | ||
) | ||
opts = { ...opts, maxRedirections: 0 } // Stop sub dispatcher from also redirecting. | ||
return dispatch(opts, redirectHandler) | ||
} | ||
} | ||
const client = new Client('http://localhost:3000') | ||
.compose(redirectInterceptor) | ||
await client.request({ path: '/', method: 'GET' }) | ||
``` | ||
#### Example 2 - Chained Compose | ||
```js | ||
const { Client, RedirectHandler, RetryHandler } = require('undici') | ||
const redirectInterceptor = dispatch => { | ||
return (opts, handler) => { | ||
const { maxRedirections } = opts | ||
if (!maxRedirections) { | ||
return dispatch(opts, handler) | ||
} | ||
const redirectHandler = new RedirectHandler( | ||
dispatch, | ||
maxRedirections, | ||
opts, | ||
handler | ||
) | ||
opts = { ...opts, maxRedirections: 0 } | ||
return dispatch(opts, redirectHandler) | ||
} | ||
} | ||
const retryInterceptor = dispatch => { | ||
return function retryInterceptor (opts, handler) { | ||
return dispatch( | ||
opts, | ||
new RetryHandler(opts, { | ||
handler, | ||
dispatch | ||
}) | ||
) | ||
} | ||
} | ||
const client = new Client('http://localhost:3000') | ||
.compose(redirectInterceptor) | ||
.compose(retryInterceptor) | ||
await client.request({ path: '/', method: 'GET' }) | ||
``` | ||
#### Pre-built interceptors | ||
##### `redirect` | ||
The `redirect` interceptor allows you to customize the way your dispatcher handles redirects. | ||
It accepts the same arguments as the [`RedirectHandler` constructor](./RedirectHandler.md). | ||
**Example - Basic Redirect Interceptor** | ||
```js | ||
const { Client, interceptors } = require("undici"); | ||
const { redirect } = interceptors; | ||
const client = new Client("http://example.com").compose( | ||
redirect({ maxRedirections: 3, throwOnMaxRedirects: true }) | ||
); | ||
client.request({ path: "/" }) | ||
``` | ||
##### `retry` | ||
The `retry` interceptor allows you to customize the way your dispatcher handles retries. | ||
It accepts the same arguments as the [`RetryHandler` constructor](./RetryHandler.md). | ||
**Example - Basic Redirect Interceptor** | ||
```js | ||
const { Client, interceptors } = require("undici"); | ||
const { retry } = interceptors; | ||
const client = new Client("http://example.com").compose( | ||
retry({ | ||
maxRetries: 3, | ||
minTimeout: 1000, | ||
maxTimeout: 10000, | ||
timeoutFactor: 2, | ||
retryAfter: true, | ||
}) | ||
); | ||
``` | ||
## Instance Events | ||
@@ -822,0 +957,0 @@ |
@@ -39,2 +39,6 @@ 'use strict' | ||
module.exports.createRedirectInterceptor = createRedirectInterceptor | ||
module.exports.interceptors = { | ||
redirect: require('./lib/interceptor/redirect'), | ||
retry: require('./lib/interceptor/retry') | ||
} | ||
@@ -41,0 +45,0 @@ module.exports.buildConnector = buildConnector |
@@ -62,2 +62,3 @@ // @ts-check | ||
const connectH2 = require('./client-h2.js') | ||
let deprecatedInterceptorWarned = false | ||
@@ -211,5 +212,14 @@ const kClosedResolve = Symbol('kClosedResolve') | ||
this[kInterceptors] = interceptors?.Client && Array.isArray(interceptors.Client) | ||
? interceptors.Client | ||
: [createRedirectInterceptor({ maxRedirections })] | ||
if (interceptors?.Client && Array.isArray(interceptors.Client)) { | ||
this[kInterceptors] = interceptors.Client | ||
if (!deprecatedInterceptorWarned) { | ||
deprecatedInterceptorWarned = true | ||
process.emitWarning('Client.Options#interceptor is deprecated. Use Dispatcher#compose instead.', { | ||
code: 'UNDICI-CLIENT-INTERCEPTOR-DEPRECATED' | ||
}) | ||
} | ||
} else { | ||
this[kInterceptors] = [createRedirectInterceptor({ maxRedirections })] | ||
} | ||
this[kUrl] = util.parseOrigin(url) | ||
@@ -216,0 +226,0 @@ this[kConnector] = connect |
'use strict' | ||
const EventEmitter = require('node:events') | ||
@@ -17,4 +16,51 @@ | ||
} | ||
compose (...args) { | ||
// So we handle [interceptor1, interceptor2] or interceptor1, interceptor2, ... | ||
const interceptors = Array.isArray(args[0]) ? args[0] : args | ||
let dispatch = this.dispatch.bind(this) | ||
for (const interceptor of interceptors) { | ||
if (interceptor == null) { | ||
continue | ||
} | ||
if (typeof interceptor !== 'function') { | ||
throw new TypeError(`invalid interceptor, expected function received ${typeof interceptor}`) | ||
} | ||
dispatch = interceptor(dispatch) | ||
if (dispatch == null || typeof dispatch !== 'function' || dispatch.length !== 2) { | ||
throw new TypeError('invalid interceptor') | ||
} | ||
} | ||
return new ComposedDispatcher(this, dispatch) | ||
} | ||
} | ||
class ComposedDispatcher extends Dispatcher { | ||
#dispatcher = null | ||
#dispatch = null | ||
constructor (dispatcher, dispatch) { | ||
super() | ||
this.#dispatcher = dispatcher | ||
this.#dispatch = dispatch | ||
} | ||
dispatch (...args) { | ||
this.#dispatch(...args) | ||
} | ||
close (...args) { | ||
return this.#dispatcher.close(...args) | ||
} | ||
destroy (...args) { | ||
return this.#dispatcher.destroy(...args) | ||
} | ||
} | ||
module.exports = Dispatcher |
@@ -447,11 +447,9 @@ 'use strict' | ||
function collectASequenceOfBytes (condition, input, position) { | ||
const result = [] | ||
let start = position.position | ||
while (position.position < input.length && condition(input[position.position])) { | ||
result.push(input[position.position]) | ||
position.position++ | ||
while (start < input.length && condition(input[start])) { | ||
++start | ||
} | ||
return Buffer.from(result, result.length) | ||
return input.subarray(position.position, (position.position = start)) | ||
} | ||
@@ -458,0 +456,0 @@ |
@@ -9,2 +9,3 @@ 'use strict' | ||
const { File: NativeFile } = require('node:buffer') | ||
const nodeUtil = require('node:util') | ||
@@ -158,2 +159,11 @@ /** @type {globalThis['File']} */ | ||
} | ||
[nodeUtil.inspect.custom] (depth, options) { | ||
let output = 'FormData:\n' | ||
this[kState].forEach(entry => { | ||
output += `${entry.name}: ${entry.value}\n` | ||
}) | ||
return output | ||
} | ||
} | ||
@@ -160,0 +170,0 @@ |
@@ -9,2 +9,3 @@ /* globals AbortController */ | ||
const util = require('../../core/util') | ||
const nodeUtil = require('node:util') | ||
const { | ||
@@ -775,2 +776,28 @@ isValidHTTPToken, | ||
} | ||
[nodeUtil.inspect.custom] (depth, options) { | ||
if (options.depth === null) { | ||
options.depth = 2 | ||
} | ||
const properties = { | ||
method: this.method, | ||
url: this.url, | ||
headers: this.headers, | ||
destination: this.destination, | ||
referrer: this.referrer, | ||
referrerPolicy: this.referrerPolicy, | ||
mode: this.mode, | ||
credentials: this.credentials, | ||
cache: this.cache, | ||
redirect: this.redirect, | ||
integrity: this.integrity, | ||
keepalive: this.keepalive, | ||
isReloadNavigation: this.isReloadNavigation, | ||
isHistoryNavigation: this.isHistoryNavigation, | ||
signal: this.signal | ||
} | ||
return nodeUtil.formatWithOptions(options, { ...properties }) | ||
} | ||
} | ||
@@ -777,0 +804,0 @@ |
@@ -6,2 +6,3 @@ 'use strict' | ||
const util = require('../../core/util') | ||
const nodeUtil = require('node:util') | ||
const { kEnumerableProperty } = util | ||
@@ -256,2 +257,22 @@ const { | ||
} | ||
[nodeUtil.inspect.custom] (depth, options) { | ||
if (options.depth === null) { | ||
options.depth = 2 | ||
} | ||
const properties = { | ||
status: this.status, | ||
statusText: this.statusText, | ||
headers: this.headers, | ||
body: this.body, | ||
bodyUsed: this.bodyUsed, | ||
ok: this.ok, | ||
redirected: this.redirected, | ||
type: this.type, | ||
url: this.url | ||
} | ||
return nodeUtil.formatWithOptions(options, `Response ${nodeUtil.inspect(properties)}`) | ||
} | ||
} | ||
@@ -258,0 +279,0 @@ |
{ | ||
"name": "undici", | ||
"version": "6.8.0", | ||
"version": "6.9.0", | ||
"description": "An HTTP/1.1 client, written from scratch for Node.js", | ||
@@ -5,0 +5,0 @@ "homepage": "https://undici.nodejs.org", |
@@ -125,3 +125,3 @@ // based on https://github.com/Ethan-Arrowood/undici-fetch/blob/249269714db874351589d2d364a0645d5160ae71/index.d.ts (MIT license) | ||
headers?: HeadersInit | ||
body?: BodyInit | ||
body?: BodyInit | null | ||
redirect?: RequestRedirect | ||
@@ -128,0 +128,0 @@ integrity?: string |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1125121
171
23398