undici 5.9.1 → 5.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -11
- package/docs/api/Agent.md +2 -1
- package/docs/api/Client.md +1 -0
- package/docs/api/DispatchInterceptor.md +60 -0
- package/docs/api/MockPool.md +1 -1
- package/docs/api/Pool.md +1 -0
- package/index-fetch.js +7 -2
- package/index.d.ts +7 -3
- package/index.js +19 -2
- package/lib/agent.js +9 -8
- package/lib/api/readable.js +1 -1
- package/lib/balanced-pool.js +4 -1
- package/lib/client.js +7 -7
- package/lib/core/symbols.js +2 -1
- package/lib/core/util.js +4 -34
- package/lib/dispatcher-base.js +34 -2
- package/lib/fetch/body.js +93 -14
- package/lib/fetch/dataURL.js +50 -5
- package/lib/fetch/file.js +13 -1
- package/lib/fetch/formdata.js +2 -2
- package/lib/fetch/global.js +48 -0
- package/lib/fetch/headers.js +3 -1
- package/lib/fetch/index.js +19 -26
- package/lib/fetch/request.js +11 -2
- package/lib/fetch/response.js +5 -4
- package/lib/fetch/util.js +289 -22
- package/lib/handler/DecoratorHandler.js +35 -0
- package/lib/handler/{redirect.js → RedirectHandler.js} +3 -3
- package/lib/interceptor/redirectInterceptor.js +21 -0
- package/lib/mock/mock-utils.js +21 -68
- package/lib/pool.js +7 -1
- package/lib/proxy-agent.js +27 -5
- package/package.json +13 -8
- package/types/agent.d.ts +3 -0
- package/types/client.d.ts +7 -3
- package/types/connector.d.ts +12 -3
- package/types/diagnostics-channel.d.ts +1 -1
- package/types/dispatcher.d.ts +61 -3
- package/types/global-origin.d.ts +7 -0
- package/types/handlers.d.ts +9 -0
- package/types/interceptors.d.ts +5 -0
- package/types/pool.d.ts +3 -0
package/README.md
CHANGED
|
@@ -185,12 +185,12 @@ Help us improve the test coverage by following instructions at [nodejs/undici/#9
|
|
|
185
185
|
Basic usage example:
|
|
186
186
|
|
|
187
187
|
```js
|
|
188
|
-
import { fetch } from 'undici'
|
|
188
|
+
import { fetch } from 'undici'
|
|
189
189
|
|
|
190
190
|
|
|
191
191
|
const res = await fetch('https://example.com')
|
|
192
192
|
const json = await res.json()
|
|
193
|
-
console.log(json)
|
|
193
|
+
console.log(json)
|
|
194
194
|
```
|
|
195
195
|
|
|
196
196
|
You can pass an optional dispatcher to `fetch` as:
|
|
@@ -225,16 +225,16 @@ A body can be of the following types:
|
|
|
225
225
|
In this implementation of fetch, ```request.body``` now accepts ```Async Iterables```. It is not present in the [Fetch Standard.](https://fetch.spec.whatwg.org)
|
|
226
226
|
|
|
227
227
|
```js
|
|
228
|
-
import { fetch } from
|
|
228
|
+
import { fetch } from 'undici'
|
|
229
229
|
|
|
230
230
|
const data = {
|
|
231
231
|
async *[Symbol.asyncIterator]() {
|
|
232
|
-
yield
|
|
233
|
-
yield
|
|
232
|
+
yield 'hello'
|
|
233
|
+
yield 'world'
|
|
234
234
|
},
|
|
235
|
-
}
|
|
235
|
+
}
|
|
236
236
|
|
|
237
|
-
await fetch(
|
|
237
|
+
await fetch('https://example.com', { body: data, method: 'POST' })
|
|
238
238
|
```
|
|
239
239
|
|
|
240
240
|
#### `response.body`
|
|
@@ -242,12 +242,12 @@ await fetch("https://example.com", { body: data, method: 'POST' });
|
|
|
242
242
|
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()`.
|
|
243
243
|
|
|
244
244
|
```js
|
|
245
|
-
import { fetch } from 'undici'
|
|
246
|
-
import { Readable } from 'node:stream'
|
|
245
|
+
import { fetch } from 'undici'
|
|
246
|
+
import { Readable } from 'node:stream'
|
|
247
247
|
|
|
248
248
|
const response = await fetch('https://example.com')
|
|
249
|
-
const readableWebStream = response.body
|
|
250
|
-
const readableNodeStream = Readable.fromWeb(readableWebStream)
|
|
249
|
+
const readableWebStream = response.body
|
|
250
|
+
const readableNodeStream = Readable.fromWeb(readableWebStream)
|
|
251
251
|
```
|
|
252
252
|
|
|
253
253
|
#### Specification Compliance
|
|
@@ -329,6 +329,28 @@ Gets the global dispatcher used by Common API Methods.
|
|
|
329
329
|
|
|
330
330
|
Returns: `Dispatcher`
|
|
331
331
|
|
|
332
|
+
### `undici.setGlobalOrigin(origin)`
|
|
333
|
+
|
|
334
|
+
* origin `string | URL | undefined`
|
|
335
|
+
|
|
336
|
+
Sets the global origin used in `fetch`.
|
|
337
|
+
|
|
338
|
+
If `undefined` is passed, the global origin will be reset. This will cause `Response.redirect`, `new Request()`, and `fetch` to throw an error when a relative path is passed.
|
|
339
|
+
|
|
340
|
+
```js
|
|
341
|
+
setGlobalOrigin('http://localhost:3000')
|
|
342
|
+
|
|
343
|
+
const response = await fetch('/api/ping')
|
|
344
|
+
|
|
345
|
+
console.log(response.url) // http://localhost:3000/api/ping
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### `undici.getGlobalOrigin()`
|
|
349
|
+
|
|
350
|
+
Gets the global origin used in `fetch`.
|
|
351
|
+
|
|
352
|
+
Returns: `URL`
|
|
353
|
+
|
|
332
354
|
### `UrlObject`
|
|
333
355
|
|
|
334
356
|
* **port** `string | number` (optional)
|
package/docs/api/Agent.md
CHANGED
|
@@ -16,10 +16,11 @@ Returns: `Agent`
|
|
|
16
16
|
|
|
17
17
|
### Parameter: `AgentOptions`
|
|
18
18
|
|
|
19
|
-
Extends: [`
|
|
19
|
+
Extends: [`PoolOptions`](Pool.md#parameter-pooloptions)
|
|
20
20
|
|
|
21
21
|
* **factory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Pool(origin, opts)`
|
|
22
22
|
* **maxRedirections** `Integer` - Default: `0`. The number of HTTP redirection to follow unless otherwise specified in `DispatchOptions`.
|
|
23
|
+
* **interceptors** `{ Agent: 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.
|
|
23
24
|
|
|
24
25
|
## Instance Properties
|
|
25
26
|
|
package/docs/api/Client.md
CHANGED
|
@@ -26,6 +26,7 @@ Returns: `Client`
|
|
|
26
26
|
* **pipelining** `number | null` (optional) - Default: `1` - The amount of concurrent requests to be sent over the single TCP/TLS connection according to [RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2). 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. Set to `0` to disable keep-alive connections.
|
|
27
27
|
* **connect** `ConnectOptions | Function | null` (optional) - Default: `null`.
|
|
28
28
|
* **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.
|
|
29
|
+
* **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.
|
|
29
30
|
|
|
30
31
|
#### Parameter: `ConnectOptions`
|
|
31
32
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#Interface: DispatchInterceptor
|
|
2
|
+
|
|
3
|
+
Extends: `Function`
|
|
4
|
+
|
|
5
|
+
A function that can be applied to the `Dispatcher.Dispatch` function before it is invoked with a dispatch request.
|
|
6
|
+
|
|
7
|
+
This allows one to write logic to intercept both the outgoing request, and the incoming response.
|
|
8
|
+
|
|
9
|
+
### Parameter: `Dispatcher.Dispatch`
|
|
10
|
+
|
|
11
|
+
The base dispatch function you are decorating.
|
|
12
|
+
|
|
13
|
+
### ReturnType: `Dispatcher.Dispatch`
|
|
14
|
+
|
|
15
|
+
A dispatch function that has been altered to provide additional logic
|
|
16
|
+
|
|
17
|
+
### Basic Example
|
|
18
|
+
|
|
19
|
+
Here is an example of an interceptor being used to provide a JWT bearer token
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
'use strict'
|
|
23
|
+
|
|
24
|
+
const insertHeaderInterceptor = dispatch => {
|
|
25
|
+
return function InterceptedDispatch(opts, handler){
|
|
26
|
+
opts.headers.push('Authorization', 'Bearer [Some token]')
|
|
27
|
+
return dispatch(opts, handler)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const client = new Client('https://localhost:3000', {
|
|
32
|
+
interceptors: { Client: [insertHeaderInterceptor] }
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Basic Example 2
|
|
38
|
+
|
|
39
|
+
Here is a contrived example of an interceptor stripping the headers from a response.
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
'use strict'
|
|
43
|
+
|
|
44
|
+
const clearHeadersInterceptor = dispatch => {
|
|
45
|
+
const { DecoratorHandler } = require('undici')
|
|
46
|
+
class ResultInterceptor extends DecoratorHandler {
|
|
47
|
+
onHeaders (statusCode, headers, resume) {
|
|
48
|
+
return super.onHeaders(statusCode, [], resume)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return function InterceptedDispatch(opts, handler){
|
|
52
|
+
return dispatch(opts, new ResultInterceptor(handler))
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const client = new Client('https://localhost:3000', {
|
|
57
|
+
interceptors: { Client: [clearHeadersInterceptor] }
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
```
|
package/docs/api/MockPool.md
CHANGED
|
@@ -54,7 +54,7 @@ Returns: `MockInterceptor` corresponding to the input options.
|
|
|
54
54
|
### Parameter: `MockPoolInterceptOptions`
|
|
55
55
|
|
|
56
56
|
* **path** `string | RegExp | (path: string) => boolean` - a matcher for the HTTP request path.
|
|
57
|
-
* **method** `string | RegExp | (method: string) => boolean` - a matcher for the HTTP request method.
|
|
57
|
+
* **method** `string | RegExp | (method: string) => boolean` - (optional) - a matcher for the HTTP request method. Defaults to `GET`.
|
|
58
58
|
* **body** `string | RegExp | (body: string) => boolean` - (optional) - a matcher for the HTTP request body.
|
|
59
59
|
* **headers** `Record<string, string | RegExp | (body: string) => boolean`> - (optional) - a matcher for the HTTP request headers. To be intercepted, a request must match all defined headers. Extra headers not defined here may (or may not) be included in the request and do not affect the interception in any way.
|
|
60
60
|
* **query** `Record<string, any> | null` - (optional) - a matcher for the HTTP request query string params.
|
package/docs/api/Pool.md
CHANGED
|
@@ -19,6 +19,7 @@ Extends: [`ClientOptions`](Client.md#parameter-clientoptions)
|
|
|
19
19
|
|
|
20
20
|
* **factory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Client(origin, opts)`
|
|
21
21
|
* **connections** `number | null` (optional) - Default: `null` - The number of `Client` instances to create. When set to `null`, the `Pool` instance will create an unlimited amount of `Client` instances.
|
|
22
|
+
* **interceptors** `{ Pool: DispatchInterceptor[] } }` - Default: `{ Pool: [] }` - 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).
|
|
22
23
|
|
|
23
24
|
## Instance Properties
|
|
24
25
|
|
package/index-fetch.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { getGlobalDispatcher } = require('./lib/global')
|
|
4
|
-
const fetchImpl = require('./lib/fetch')
|
|
4
|
+
const fetchImpl = require('./lib/fetch').fetch
|
|
5
5
|
|
|
6
6
|
module.exports.fetch = async function fetch (resource) {
|
|
7
7
|
const dispatcher = (arguments[1] && arguments[1].dispatcher) || getGlobalDispatcher()
|
|
8
|
-
|
|
8
|
+
try {
|
|
9
|
+
return await fetchImpl.apply(dispatcher, arguments)
|
|
10
|
+
} catch (err) {
|
|
11
|
+
Error.captureStackTrace(err, this)
|
|
12
|
+
throw err
|
|
13
|
+
}
|
|
9
14
|
}
|
|
10
15
|
module.exports.FormData = require('./lib/fetch/formdata').FormData
|
|
11
16
|
module.exports.Headers = require('./lib/fetch/headers').Headers
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import Dispatcher = require('./types/dispatcher')
|
|
2
2
|
import { setGlobalDispatcher, getGlobalDispatcher } from './types/global-dispatcher'
|
|
3
|
+
import { setGlobalOrigin, getGlobalOrigin } from './types/global-origin'
|
|
3
4
|
import Pool = require('./types/pool')
|
|
5
|
+
import { RedirectHandler, DecoratorHandler } from './types/handlers'
|
|
6
|
+
|
|
4
7
|
import BalancedPool = require('./types/balanced-pool')
|
|
5
8
|
import Client = require('./types/client')
|
|
6
9
|
import buildConnector = require('./types/connector')
|
|
@@ -19,14 +22,15 @@ export * from './types/formdata'
|
|
|
19
22
|
export * from './types/diagnostics-channel'
|
|
20
23
|
export { Interceptable } from './types/mock-interceptor'
|
|
21
24
|
|
|
22
|
-
export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent }
|
|
25
|
+
export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, setGlobalOrigin, getGlobalOrigin, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent, RedirectHandler, DecoratorHandler }
|
|
23
26
|
export default Undici
|
|
24
27
|
|
|
25
|
-
declare function Undici(url: string, opts: Pool.Options): Pool
|
|
26
|
-
|
|
27
28
|
declare namespace Undici {
|
|
28
29
|
var Dispatcher: typeof import('./types/dispatcher')
|
|
29
30
|
var Pool: typeof import('./types/pool');
|
|
31
|
+
var RedirectHandler: typeof import ('./types/handlers').RedirectHandler
|
|
32
|
+
var DecoratorHandler: typeof import ('./types/handlers').DecoratorHandler
|
|
33
|
+
var createRedirectInterceptor: typeof import ('./types/interceptors').createRedirectInterceptor
|
|
30
34
|
var BalancedPool: typeof import('./types/balanced-pool');
|
|
31
35
|
var Client: typeof import('./types/client');
|
|
32
36
|
var buildConnector: typeof import('./types/connector');
|
package/index.js
CHANGED
|
@@ -16,6 +16,9 @@ const MockPool = require('./lib/mock/mock-pool')
|
|
|
16
16
|
const mockErrors = require('./lib/mock/mock-errors')
|
|
17
17
|
const ProxyAgent = require('./lib/proxy-agent')
|
|
18
18
|
const { getGlobalDispatcher, setGlobalDispatcher } = require('./lib/global')
|
|
19
|
+
const DecoratorHandler = require('./lib/handler/DecoratorHandler')
|
|
20
|
+
const RedirectHandler = require('./lib/handler/RedirectHandler')
|
|
21
|
+
const createRedirectInterceptor = require('./lib/interceptor/redirectInterceptor')
|
|
19
22
|
|
|
20
23
|
const nodeVersion = process.versions.node.split('.')
|
|
21
24
|
const nodeMajor = Number(nodeVersion[0])
|
|
@@ -30,6 +33,10 @@ module.exports.BalancedPool = BalancedPool
|
|
|
30
33
|
module.exports.Agent = Agent
|
|
31
34
|
module.exports.ProxyAgent = ProxyAgent
|
|
32
35
|
|
|
36
|
+
module.exports.DecoratorHandler = DecoratorHandler
|
|
37
|
+
module.exports.RedirectHandler = RedirectHandler
|
|
38
|
+
module.exports.createRedirectInterceptor = createRedirectInterceptor
|
|
39
|
+
|
|
33
40
|
module.exports.buildConnector = buildConnector
|
|
34
41
|
module.exports.errors = errors
|
|
35
42
|
|
|
@@ -89,16 +96,26 @@ if (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >= 8)) {
|
|
|
89
96
|
let fetchImpl = null
|
|
90
97
|
module.exports.fetch = async function fetch (resource) {
|
|
91
98
|
if (!fetchImpl) {
|
|
92
|
-
fetchImpl = require('./lib/fetch')
|
|
99
|
+
fetchImpl = require('./lib/fetch').fetch
|
|
93
100
|
}
|
|
94
101
|
const dispatcher = (arguments[1] && arguments[1].dispatcher) || getGlobalDispatcher()
|
|
95
|
-
|
|
102
|
+
try {
|
|
103
|
+
return await fetchImpl.apply(dispatcher, arguments)
|
|
104
|
+
} catch (err) {
|
|
105
|
+
Error.captureStackTrace(err, this)
|
|
106
|
+
throw err
|
|
107
|
+
}
|
|
96
108
|
}
|
|
97
109
|
module.exports.Headers = require('./lib/fetch/headers').Headers
|
|
98
110
|
module.exports.Response = require('./lib/fetch/response').Response
|
|
99
111
|
module.exports.Request = require('./lib/fetch/request').Request
|
|
100
112
|
module.exports.FormData = require('./lib/fetch/formdata').FormData
|
|
101
113
|
module.exports.File = require('./lib/fetch/file').File
|
|
114
|
+
|
|
115
|
+
const { setGlobalOrigin, getGlobalOrigin } = require('./lib/fetch/global')
|
|
116
|
+
|
|
117
|
+
module.exports.setGlobalOrigin = setGlobalOrigin
|
|
118
|
+
module.exports.getGlobalOrigin = getGlobalOrigin
|
|
102
119
|
}
|
|
103
120
|
|
|
104
121
|
module.exports.request = makeDispatcher(api.request)
|
package/lib/agent.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { InvalidArgumentError } = require('./core/errors')
|
|
4
|
-
const { kClients, kRunning, kClose, kDestroy, kDispatch } = require('./core/symbols')
|
|
4
|
+
const { kClients, kRunning, kClose, kDestroy, kDispatch, kInterceptors } = require('./core/symbols')
|
|
5
5
|
const DispatcherBase = require('./dispatcher-base')
|
|
6
6
|
const Pool = require('./pool')
|
|
7
7
|
const Client = require('./client')
|
|
8
8
|
const util = require('./core/util')
|
|
9
|
-
const
|
|
9
|
+
const createRedirectInterceptor = require('./interceptor/redirectInterceptor')
|
|
10
10
|
const { WeakRef, FinalizationRegistry } = require('./compat/dispatcher-weakref')()
|
|
11
11
|
|
|
12
12
|
const kOnConnect = Symbol('onConnect')
|
|
@@ -44,7 +44,14 @@ class Agent extends DispatcherBase {
|
|
|
44
44
|
connect = { ...connect }
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
this[kInterceptors] = options.interceptors && options.interceptors.Agent && Array.isArray(options.interceptors.Agent)
|
|
48
|
+
? options.interceptors.Agent
|
|
49
|
+
: [createRedirectInterceptor({ maxRedirections })]
|
|
50
|
+
|
|
47
51
|
this[kOptions] = { ...util.deepClone(options), connect }
|
|
52
|
+
this[kOptions].interceptors = options.interceptors
|
|
53
|
+
? { ...options.interceptors }
|
|
54
|
+
: undefined
|
|
48
55
|
this[kMaxRedirections] = maxRedirections
|
|
49
56
|
this[kFactory] = factory
|
|
50
57
|
this[kClients] = new Map()
|
|
@@ -108,12 +115,6 @@ class Agent extends DispatcherBase {
|
|
|
108
115
|
this[kFinalizer].register(dispatcher, key)
|
|
109
116
|
}
|
|
110
117
|
|
|
111
|
-
const { maxRedirections = this[kMaxRedirections] } = opts
|
|
112
|
-
if (maxRedirections != null && maxRedirections !== 0) {
|
|
113
|
-
opts = { ...opts, maxRedirections: 0 } // Stop sub dispatcher from also redirecting.
|
|
114
|
-
handler = new RedirectHandler(this, maxRedirections, opts, handler)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
118
|
return dispatcher.dispatch(opts, handler)
|
|
118
119
|
}
|
|
119
120
|
|
package/lib/api/readable.js
CHANGED
|
@@ -93,7 +93,7 @@ module.exports = class BodyReadable extends Readable {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
push (chunk) {
|
|
96
|
-
if (this[kConsume] && chunk !== null) {
|
|
96
|
+
if (this[kConsume] && chunk !== null && this.readableLength === 0) {
|
|
97
97
|
consumePush(this[kConsume], chunk)
|
|
98
98
|
return this[kReading] ? super.push(chunk) : true
|
|
99
99
|
}
|
package/lib/balanced-pool.js
CHANGED
|
@@ -13,7 +13,7 @@ const {
|
|
|
13
13
|
kGetDispatcher
|
|
14
14
|
} = require('./pool-base')
|
|
15
15
|
const Pool = require('./pool')
|
|
16
|
-
const { kUrl } = require('./core/symbols')
|
|
16
|
+
const { kUrl, kInterceptors } = require('./core/symbols')
|
|
17
17
|
const { parseOrigin } = require('./core/util')
|
|
18
18
|
const kFactory = Symbol('factory')
|
|
19
19
|
|
|
@@ -53,6 +53,9 @@ class BalancedPool extends PoolBase {
|
|
|
53
53
|
throw new InvalidArgumentError('factory must be a function.')
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
this[kInterceptors] = opts.interceptors && opts.interceptors.BalancedPool && Array.isArray(opts.interceptors.BalancedPool)
|
|
57
|
+
? opts.interceptors.BalancedPool
|
|
58
|
+
: []
|
|
56
59
|
this[kFactory] = factory
|
|
57
60
|
|
|
58
61
|
for (const upstream of upstreams) {
|
package/lib/client.js
CHANGED
|
@@ -7,7 +7,6 @@ const net = require('net')
|
|
|
7
7
|
const util = require('./core/util')
|
|
8
8
|
const Request = require('./core/request')
|
|
9
9
|
const DispatcherBase = require('./dispatcher-base')
|
|
10
|
-
const RedirectHandler = require('./handler/redirect')
|
|
11
10
|
const {
|
|
12
11
|
RequestContentLengthMismatchError,
|
|
13
12
|
ResponseContentLengthMismatchError,
|
|
@@ -60,7 +59,8 @@ const {
|
|
|
60
59
|
kCounter,
|
|
61
60
|
kClose,
|
|
62
61
|
kDestroy,
|
|
63
|
-
kDispatch
|
|
62
|
+
kDispatch,
|
|
63
|
+
kInterceptors
|
|
64
64
|
} = require('./core/symbols')
|
|
65
65
|
|
|
66
66
|
const kClosedResolve = Symbol('kClosedResolve')
|
|
@@ -82,6 +82,7 @@ try {
|
|
|
82
82
|
|
|
83
83
|
class Client extends DispatcherBase {
|
|
84
84
|
constructor (url, {
|
|
85
|
+
interceptors,
|
|
85
86
|
maxHeaderSize,
|
|
86
87
|
headersTimeout,
|
|
87
88
|
socketTimeout,
|
|
@@ -179,6 +180,9 @@ class Client extends DispatcherBase {
|
|
|
179
180
|
})
|
|
180
181
|
}
|
|
181
182
|
|
|
183
|
+
this[kInterceptors] = interceptors && interceptors.Client && Array.isArray(interceptors.Client)
|
|
184
|
+
? interceptors.Client
|
|
185
|
+
: [createRedirectInterceptor({ maxRedirections })]
|
|
182
186
|
this[kUrl] = util.parseOrigin(url)
|
|
183
187
|
this[kConnector] = connect
|
|
184
188
|
this[kSocket] = null
|
|
@@ -254,11 +258,6 @@ class Client extends DispatcherBase {
|
|
|
254
258
|
}
|
|
255
259
|
|
|
256
260
|
[kDispatch] (opts, handler) {
|
|
257
|
-
const { maxRedirections = this[kMaxRedirections] } = opts
|
|
258
|
-
if (maxRedirections) {
|
|
259
|
-
handler = new RedirectHandler(this, maxRedirections, opts, handler)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
261
|
const origin = opts.origin || this[kUrl].origin
|
|
263
262
|
|
|
264
263
|
const request = new Request(origin, opts, handler)
|
|
@@ -319,6 +318,7 @@ class Client extends DispatcherBase {
|
|
|
319
318
|
}
|
|
320
319
|
|
|
321
320
|
const constants = require('./llhttp/constants')
|
|
321
|
+
const createRedirectInterceptor = require('./interceptor/redirectInterceptor')
|
|
322
322
|
const EMPTY_BUF = Buffer.alloc(0)
|
|
323
323
|
|
|
324
324
|
async function lazyllhttp () {
|
package/lib/core/symbols.js
CHANGED
|
@@ -48,5 +48,6 @@ module.exports = {
|
|
|
48
48
|
kMaxRedirections: Symbol('maxRedirections'),
|
|
49
49
|
kMaxRequests: Symbol('maxRequestsPerClient'),
|
|
50
50
|
kProxy: Symbol('proxy agent options'),
|
|
51
|
-
kCounter: Symbol('socket request counter')
|
|
51
|
+
kCounter: Symbol('socket request counter'),
|
|
52
|
+
kInterceptors: Symbol('dispatch interceptors')
|
|
52
53
|
}
|
package/lib/core/util.js
CHANGED
|
@@ -8,6 +8,7 @@ const net = require('net')
|
|
|
8
8
|
const { InvalidArgumentError } = require('./errors')
|
|
9
9
|
const { Blob } = require('buffer')
|
|
10
10
|
const nodeUtil = require('util')
|
|
11
|
+
const { stringify } = require('querystring')
|
|
11
12
|
|
|
12
13
|
function nop () {}
|
|
13
14
|
|
|
@@ -26,46 +27,15 @@ function isBlobLike (object) {
|
|
|
26
27
|
)
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
function isObject (val) {
|
|
30
|
-
return val !== null && typeof val === 'object'
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// this escapes all non-uri friendly characters
|
|
34
|
-
function encode (val) {
|
|
35
|
-
return encodeURIComponent(val)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// based on https://github.com/axios/axios/blob/63e559fa609c40a0a460ae5d5a18c3470ffc6c9e/lib/helpers/buildURL.js (MIT license)
|
|
39
30
|
function buildURL (url, queryParams) {
|
|
40
31
|
if (url.includes('?') || url.includes('#')) {
|
|
41
32
|
throw new Error('Query params cannot be passed when url already contains "?" or "#".')
|
|
42
33
|
}
|
|
43
|
-
if (!isObject(queryParams)) {
|
|
44
|
-
throw new Error('Query params must be an object')
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const parts = []
|
|
48
|
-
for (let [key, val] of Object.entries(queryParams)) {
|
|
49
|
-
if (val === null || typeof val === 'undefined') {
|
|
50
|
-
continue
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (!Array.isArray(val)) {
|
|
54
|
-
val = [val]
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
for (const v of val) {
|
|
58
|
-
if (isObject(v)) {
|
|
59
|
-
throw new Error('Passing object as a query param is not supported, please serialize to string up-front')
|
|
60
|
-
}
|
|
61
|
-
parts.push(encode(key) + '=' + encode(v))
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
34
|
|
|
65
|
-
const
|
|
35
|
+
const stringified = stringify(queryParams)
|
|
66
36
|
|
|
67
|
-
if (
|
|
68
|
-
url += '?' +
|
|
37
|
+
if (stringified) {
|
|
38
|
+
url += '?' + stringified
|
|
69
39
|
}
|
|
70
40
|
|
|
71
41
|
return url
|
package/lib/dispatcher-base.js
CHANGED
|
@@ -6,12 +6,13 @@ const {
|
|
|
6
6
|
ClientClosedError,
|
|
7
7
|
InvalidArgumentError
|
|
8
8
|
} = require('./core/errors')
|
|
9
|
-
const { kDestroy, kClose, kDispatch } = require('./core/symbols')
|
|
9
|
+
const { kDestroy, kClose, kDispatch, kInterceptors } = require('./core/symbols')
|
|
10
10
|
|
|
11
11
|
const kDestroyed = Symbol('destroyed')
|
|
12
12
|
const kClosed = Symbol('closed')
|
|
13
13
|
const kOnDestroyed = Symbol('onDestroyed')
|
|
14
14
|
const kOnClosed = Symbol('onClosed')
|
|
15
|
+
const kInterceptedDispatch = Symbol('Intercepted Dispatch')
|
|
15
16
|
|
|
16
17
|
class DispatcherBase extends Dispatcher {
|
|
17
18
|
constructor () {
|
|
@@ -31,6 +32,23 @@ class DispatcherBase extends Dispatcher {
|
|
|
31
32
|
return this[kClosed]
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
get interceptors () {
|
|
36
|
+
return this[kInterceptors]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
set interceptors (newInterceptors) {
|
|
40
|
+
if (newInterceptors) {
|
|
41
|
+
for (let i = newInterceptors.length - 1; i >= 0; i--) {
|
|
42
|
+
const interceptor = this[kInterceptors][i]
|
|
43
|
+
if (typeof interceptor !== 'function') {
|
|
44
|
+
throw new InvalidArgumentError('interceptor must be an function')
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this[kInterceptors] = newInterceptors
|
|
50
|
+
}
|
|
51
|
+
|
|
34
52
|
close (callback) {
|
|
35
53
|
if (callback === undefined) {
|
|
36
54
|
return new Promise((resolve, reject) => {
|
|
@@ -125,6 +143,20 @@ class DispatcherBase extends Dispatcher {
|
|
|
125
143
|
})
|
|
126
144
|
}
|
|
127
145
|
|
|
146
|
+
[kInterceptedDispatch] (opts, handler) {
|
|
147
|
+
if (!this[kInterceptors] || this[kInterceptors].length === 0) {
|
|
148
|
+
this[kInterceptedDispatch] = this[kDispatch]
|
|
149
|
+
return this[kDispatch](opts, handler)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let dispatch = this[kDispatch].bind(this)
|
|
153
|
+
for (let i = this[kInterceptors].length - 1; i >= 0; i--) {
|
|
154
|
+
dispatch = this[kInterceptors][i](dispatch)
|
|
155
|
+
}
|
|
156
|
+
this[kInterceptedDispatch] = dispatch
|
|
157
|
+
return dispatch(opts, handler)
|
|
158
|
+
}
|
|
159
|
+
|
|
128
160
|
dispatch (opts, handler) {
|
|
129
161
|
if (!handler || typeof handler !== 'object') {
|
|
130
162
|
throw new InvalidArgumentError('handler must be an object')
|
|
@@ -143,7 +175,7 @@ class DispatcherBase extends Dispatcher {
|
|
|
143
175
|
throw new ClientClosedError()
|
|
144
176
|
}
|
|
145
177
|
|
|
146
|
-
return this[
|
|
178
|
+
return this[kInterceptedDispatch](opts, handler)
|
|
147
179
|
} catch (err) {
|
|
148
180
|
if (typeof handler.onError !== 'function') {
|
|
149
181
|
throw new InvalidArgumentError('invalid onError method')
|