undici 4.14.1 → 4.15.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/docs/api/BalancedPool.md +9 -4
- package/docs/api/Dispatcher.md +54 -1
- package/docs/api/Pool.md +4 -0
- package/docs/api/PoolStats.md +35 -0
- package/index.js +2 -2
- package/lib/api/api-connect.js +5 -3
- package/lib/api/api-pipeline.js +7 -4
- package/lib/api/api-request.js +8 -5
- package/lib/api/api-stream.js +7 -4
- package/lib/api/api-upgrade.js +5 -3
- package/lib/balanced-pool.js +22 -5
- package/lib/client.js +4 -2
- package/lib/core/symbols.js +2 -0
- package/lib/core/util.js +5 -0
- package/lib/fetch/request.js +15 -15
- package/lib/pool-base.js +17 -2
- package/lib/pool-stats.js +34 -0
- package/package.json +5 -2
- package/types/dispatcher.d.ts +10 -3
- package/types/fetch.d.ts +2 -2
- package/types/readable.d.ts +61 -0
package/docs/api/BalancedPool.md
CHANGED
|
@@ -11,14 +11,15 @@ Requests are not guaranteed to be dispatched in order of invocation.
|
|
|
11
11
|
Arguments:
|
|
12
12
|
|
|
13
13
|
* **upstreams** `URL | string | string[]` - It should only include the **protocol, hostname, and port**.
|
|
14
|
-
* **options** `
|
|
14
|
+
* **options** `BalancedPoolOptions` (optional)
|
|
15
15
|
|
|
16
|
-
### Parameter: `
|
|
16
|
+
### Parameter: `BalancedPoolOptions`
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Extends: [`PoolOptions`](Pool.md#parameter-pooloptions)
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
* **factory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Pool(origin, opts)`
|
|
21
21
|
|
|
22
|
+
The `PoolOptions` are passed to each of the `Pool` instances being created.
|
|
22
23
|
## Instance Properties
|
|
23
24
|
|
|
24
25
|
### `BalancedPool.upstreams`
|
|
@@ -33,6 +34,10 @@ Implements [Client.closed](Client.md#clientclosed)
|
|
|
33
34
|
|
|
34
35
|
Implements [Client.destroyed](Client.md#clientdestroyed)
|
|
35
36
|
|
|
37
|
+
### `Pool.stats`
|
|
38
|
+
|
|
39
|
+
Returns [`PoolStats`](PoolStats.md) instance for this pool.
|
|
40
|
+
|
|
36
41
|
## Instance Methods
|
|
37
42
|
|
|
38
43
|
### `BalancedPool.addUpstream(upstream)`
|
package/docs/api/Dispatcher.md
CHANGED
|
@@ -307,6 +307,59 @@ client.dispatch({
|
|
|
307
307
|
})
|
|
308
308
|
```
|
|
309
309
|
|
|
310
|
+
#### Example 3 - Dispatch POST request
|
|
311
|
+
|
|
312
|
+
```js
|
|
313
|
+
import { createServer } from 'http'
|
|
314
|
+
import { Client } from 'undici'
|
|
315
|
+
import { once } from 'events'
|
|
316
|
+
|
|
317
|
+
const server = createServer((request, response) => {
|
|
318
|
+
request.on('data', (data) => {
|
|
319
|
+
console.log(`Request Data: ${data.toString('utf8')}`)
|
|
320
|
+
const body = JSON.parse(data)
|
|
321
|
+
body.message = 'World'
|
|
322
|
+
response.end(JSON.stringify(body))
|
|
323
|
+
})
|
|
324
|
+
}).listen()
|
|
325
|
+
|
|
326
|
+
await once(server, 'listening')
|
|
327
|
+
|
|
328
|
+
const client = new Client(`http://localhost:${server.address().port}`)
|
|
329
|
+
|
|
330
|
+
const data = []
|
|
331
|
+
|
|
332
|
+
client.dispatch({
|
|
333
|
+
path: '/',
|
|
334
|
+
method: 'POST',
|
|
335
|
+
headers: {
|
|
336
|
+
'content-type': 'application/json'
|
|
337
|
+
},
|
|
338
|
+
body: JSON.stringify({ message: 'Hello' })
|
|
339
|
+
}, {
|
|
340
|
+
onConnect: () => {
|
|
341
|
+
console.log('Connected!')
|
|
342
|
+
},
|
|
343
|
+
onError: (error) => {
|
|
344
|
+
console.error(error)
|
|
345
|
+
},
|
|
346
|
+
onHeaders: (statusCode, headers) => {
|
|
347
|
+
console.log(`onHeaders | statusCode: ${statusCode} | headers: ${headers}`)
|
|
348
|
+
},
|
|
349
|
+
onData: (chunk) => {
|
|
350
|
+
console.log('onData: chunk received')
|
|
351
|
+
data.push(chunk)
|
|
352
|
+
},
|
|
353
|
+
onComplete: (trailers) => {
|
|
354
|
+
console.log(`onComplete | trailers: ${trailers}`)
|
|
355
|
+
const res = Buffer.concat(data).toString('utf8')
|
|
356
|
+
console.log(`Response Data: ${res}`)
|
|
357
|
+
client.close()
|
|
358
|
+
server.close()
|
|
359
|
+
}
|
|
360
|
+
})
|
|
361
|
+
```
|
|
362
|
+
|
|
310
363
|
### `Dispatcher.pipeline(options, handler)`
|
|
311
364
|
|
|
312
365
|
For easy use with [stream.pipeline](https://nodejs.org/api/stream.html#stream_stream_pipeline_source_transforms_destination_callback). The `handler` argument 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. The function returns a `Duplex` which writes to the request and reads from the response.
|
|
@@ -436,7 +489,7 @@ The `RequestOptions.method` property should not be value `'CONNECT'`.
|
|
|
436
489
|
- `body`
|
|
437
490
|
- `bodyUsed`
|
|
438
491
|
|
|
439
|
-
`body` contains the following additional
|
|
492
|
+
`body` contains the following additional extensions:
|
|
440
493
|
|
|
441
494
|
- `dump({ limit: Integer })`, dump the response by reading up to `limit` bytes without killing the socket (optional) - Default: 262144.
|
|
442
495
|
|
package/docs/api/Pool.md
CHANGED
|
@@ -30,6 +30,10 @@ Implements [Client.closed](Client.md#clientclosed)
|
|
|
30
30
|
|
|
31
31
|
Implements [Client.destroyed](Client.md#clientdestroyed)
|
|
32
32
|
|
|
33
|
+
### `Pool.stats`
|
|
34
|
+
|
|
35
|
+
Returns [`PoolStats`](PoolStats.md) instance for this pool.
|
|
36
|
+
|
|
33
37
|
## Instance Methods
|
|
34
38
|
|
|
35
39
|
### `Pool.close([callback])`
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Class: PoolStats
|
|
2
|
+
|
|
3
|
+
Aggregate stats for a [Pool](Pool.md) or [BalancedPool](BalancedPool.md).
|
|
4
|
+
|
|
5
|
+
## `new PoolStats(pool)`
|
|
6
|
+
|
|
7
|
+
Arguments:
|
|
8
|
+
|
|
9
|
+
* **pool** `Pool` - Pool or BalancedPool from which to return stats.
|
|
10
|
+
|
|
11
|
+
## Instance Properties
|
|
12
|
+
|
|
13
|
+
### `PoolStats.connected`
|
|
14
|
+
|
|
15
|
+
Number of open socket connections in this pool.
|
|
16
|
+
|
|
17
|
+
### `PoolStats.free`
|
|
18
|
+
|
|
19
|
+
Number of open socket connections in this pool that do not have an active request.
|
|
20
|
+
|
|
21
|
+
### `PoolStats.pending`
|
|
22
|
+
|
|
23
|
+
Number of pending requests across all clients in this pool.
|
|
24
|
+
|
|
25
|
+
### `PoolStats.queued`
|
|
26
|
+
|
|
27
|
+
Number of queued requests across all clients in this pool.
|
|
28
|
+
|
|
29
|
+
### `PoolStats.running`
|
|
30
|
+
|
|
31
|
+
Number of currently active requests across all clients in this pool.
|
|
32
|
+
|
|
33
|
+
### `PoolStats.size`
|
|
34
|
+
|
|
35
|
+
Number of active, pending, or queued requests across all clients in this pool.
|
package/index.js
CHANGED
|
@@ -94,12 +94,12 @@ module.exports.getGlobalDispatcher = getGlobalDispatcher
|
|
|
94
94
|
|
|
95
95
|
if (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >= 5)) {
|
|
96
96
|
let fetchImpl = null
|
|
97
|
-
module.exports.fetch = async function fetch (resource
|
|
97
|
+
module.exports.fetch = async function fetch (resource) {
|
|
98
98
|
if (!fetchImpl) {
|
|
99
99
|
fetchImpl = require('./lib/fetch')
|
|
100
100
|
}
|
|
101
101
|
const dispatcher = getGlobalDispatcher()
|
|
102
|
-
return fetchImpl.
|
|
102
|
+
return fetchImpl.apply(dispatcher, arguments)
|
|
103
103
|
}
|
|
104
104
|
module.exports.Headers = require('./lib/fetch/headers').Headers
|
|
105
105
|
module.exports.Response = require('./lib/fetch/response').Response
|
package/lib/api/api-connect.js
CHANGED
|
@@ -15,7 +15,7 @@ class ConnectHandler extends AsyncResource {
|
|
|
15
15
|
throw new InvalidArgumentError('invalid callback')
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const { signal, opaque } = opts
|
|
18
|
+
const { signal, opaque, responseHeaders } = opts
|
|
19
19
|
|
|
20
20
|
if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') {
|
|
21
21
|
throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget')
|
|
@@ -24,6 +24,7 @@ class ConnectHandler extends AsyncResource {
|
|
|
24
24
|
super('UNDICI_CONNECT')
|
|
25
25
|
|
|
26
26
|
this.opaque = opaque || null
|
|
27
|
+
this.responseHeaders = responseHeaders || null
|
|
27
28
|
this.callback = callback
|
|
28
29
|
this.abort = null
|
|
29
30
|
|
|
@@ -43,15 +44,16 @@ class ConnectHandler extends AsyncResource {
|
|
|
43
44
|
throw new SocketError('bad connect', null)
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
onUpgrade (statusCode,
|
|
47
|
+
onUpgrade (statusCode, rawHeaders, socket) {
|
|
47
48
|
const { callback, opaque, context } = this
|
|
48
49
|
|
|
49
50
|
removeSignal(this)
|
|
50
51
|
|
|
51
52
|
this.callback = null
|
|
53
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
52
54
|
this.runInAsyncScope(callback, null, null, {
|
|
53
55
|
statusCode,
|
|
54
|
-
headers
|
|
56
|
+
headers,
|
|
55
57
|
socket,
|
|
56
58
|
opaque,
|
|
57
59
|
context
|
package/lib/api/api-pipeline.js
CHANGED
|
@@ -69,7 +69,7 @@ class PipelineHandler extends AsyncResource {
|
|
|
69
69
|
throw new InvalidArgumentError('invalid handler')
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
const { signal, method, opaque, onInfo } = opts
|
|
72
|
+
const { signal, method, opaque, onInfo, responseHeaders } = opts
|
|
73
73
|
|
|
74
74
|
if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') {
|
|
75
75
|
throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget')
|
|
@@ -86,6 +86,7 @@ class PipelineHandler extends AsyncResource {
|
|
|
86
86
|
super('UNDICI_PIPELINE')
|
|
87
87
|
|
|
88
88
|
this.opaque = opaque || null
|
|
89
|
+
this.responseHeaders = responseHeaders || null
|
|
89
90
|
this.handler = handler
|
|
90
91
|
this.abort = null
|
|
91
92
|
this.context = null
|
|
@@ -156,12 +157,13 @@ class PipelineHandler extends AsyncResource {
|
|
|
156
157
|
this.context = context
|
|
157
158
|
}
|
|
158
159
|
|
|
159
|
-
onHeaders (statusCode,
|
|
160
|
+
onHeaders (statusCode, rawHeaders, resume) {
|
|
160
161
|
const { opaque, handler, context } = this
|
|
161
162
|
|
|
162
163
|
if (statusCode < 200) {
|
|
163
164
|
if (this.onInfo) {
|
|
164
|
-
this.
|
|
165
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
166
|
+
this.onInfo({ statusCode, headers })
|
|
165
167
|
}
|
|
166
168
|
return
|
|
167
169
|
}
|
|
@@ -171,9 +173,10 @@ class PipelineHandler extends AsyncResource {
|
|
|
171
173
|
let body
|
|
172
174
|
try {
|
|
173
175
|
this.handler = null
|
|
176
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
174
177
|
body = this.runInAsyncScope(handler, null, {
|
|
175
178
|
statusCode,
|
|
176
|
-
headers
|
|
179
|
+
headers,
|
|
177
180
|
opaque,
|
|
178
181
|
body: this.res,
|
|
179
182
|
context
|
package/lib/api/api-request.js
CHANGED
|
@@ -15,7 +15,7 @@ class RequestHandler extends AsyncResource {
|
|
|
15
15
|
throw new InvalidArgumentError('invalid opts')
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const { signal, method, opaque, body, onInfo } = opts
|
|
18
|
+
const { signal, method, opaque, body, onInfo, responseHeaders } = opts
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
21
|
if (typeof callback !== 'function') {
|
|
@@ -42,6 +42,7 @@ class RequestHandler extends AsyncResource {
|
|
|
42
42
|
throw err
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
this.responseHeaders = responseHeaders || null
|
|
45
46
|
this.opaque = opaque || null
|
|
46
47
|
this.callback = callback
|
|
47
48
|
this.res = null
|
|
@@ -69,25 +70,27 @@ class RequestHandler extends AsyncResource {
|
|
|
69
70
|
this.context = context
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
onHeaders (statusCode,
|
|
73
|
+
onHeaders (statusCode, rawHeaders, resume) {
|
|
73
74
|
const { callback, opaque, abort, context } = this
|
|
74
75
|
|
|
75
76
|
if (statusCode < 200) {
|
|
76
77
|
if (this.onInfo) {
|
|
77
|
-
this.
|
|
78
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
79
|
+
this.onInfo({ statusCode, headers })
|
|
78
80
|
}
|
|
79
81
|
return
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
const parsedHeaders = util.parseHeaders(
|
|
84
|
+
const parsedHeaders = util.parseHeaders(rawHeaders)
|
|
83
85
|
const body = new Readable(resume, abort, parsedHeaders['content-type'])
|
|
84
86
|
|
|
85
87
|
this.callback = null
|
|
86
88
|
this.res = body
|
|
89
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
87
90
|
|
|
88
91
|
this.runInAsyncScope(callback, null, null, {
|
|
89
92
|
statusCode,
|
|
90
|
-
headers
|
|
93
|
+
headers,
|
|
91
94
|
trailers: this.trailers,
|
|
92
95
|
opaque,
|
|
93
96
|
body,
|
package/lib/api/api-stream.js
CHANGED
|
@@ -16,7 +16,7 @@ class StreamHandler extends AsyncResource {
|
|
|
16
16
|
throw new InvalidArgumentError('invalid opts')
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const { signal, method, opaque, body, onInfo } = opts
|
|
19
|
+
const { signal, method, opaque, body, onInfo, responseHeaders } = opts
|
|
20
20
|
|
|
21
21
|
try {
|
|
22
22
|
if (typeof callback !== 'function') {
|
|
@@ -47,6 +47,7 @@ class StreamHandler extends AsyncResource {
|
|
|
47
47
|
throw err
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
this.responseHeaders = responseHeaders || null
|
|
50
51
|
this.opaque = opaque || null
|
|
51
52
|
this.factory = factory
|
|
52
53
|
this.callback = callback
|
|
@@ -75,20 +76,22 @@ class StreamHandler extends AsyncResource {
|
|
|
75
76
|
this.context = context
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
onHeaders (statusCode,
|
|
79
|
+
onHeaders (statusCode, rawHeaders, resume) {
|
|
79
80
|
const { factory, opaque, context } = this
|
|
80
81
|
|
|
81
82
|
if (statusCode < 200) {
|
|
82
83
|
if (this.onInfo) {
|
|
83
|
-
this.
|
|
84
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
85
|
+
this.onInfo({ statusCode, headers })
|
|
84
86
|
}
|
|
85
87
|
return
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
this.factory = null
|
|
91
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
89
92
|
const res = this.runInAsyncScope(factory, null, {
|
|
90
93
|
statusCode,
|
|
91
|
-
headers
|
|
94
|
+
headers,
|
|
92
95
|
opaque,
|
|
93
96
|
context
|
|
94
97
|
})
|
package/lib/api/api-upgrade.js
CHANGED
|
@@ -16,7 +16,7 @@ class UpgradeHandler extends AsyncResource {
|
|
|
16
16
|
throw new InvalidArgumentError('invalid callback')
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const { signal, opaque } = opts
|
|
19
|
+
const { signal, opaque, responseHeaders } = opts
|
|
20
20
|
|
|
21
21
|
if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') {
|
|
22
22
|
throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget')
|
|
@@ -24,6 +24,7 @@ class UpgradeHandler extends AsyncResource {
|
|
|
24
24
|
|
|
25
25
|
super('UNDICI_UPGRADE')
|
|
26
26
|
|
|
27
|
+
this.responseHeaders = responseHeaders || null
|
|
27
28
|
this.opaque = opaque || null
|
|
28
29
|
this.callback = callback
|
|
29
30
|
this.abort = null
|
|
@@ -45,7 +46,7 @@ class UpgradeHandler extends AsyncResource {
|
|
|
45
46
|
throw new SocketError('bad upgrade', null)
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
onUpgrade (statusCode,
|
|
49
|
+
onUpgrade (statusCode, rawHeaders, socket) {
|
|
49
50
|
const { callback, opaque, context } = this
|
|
50
51
|
|
|
51
52
|
assert.strictEqual(statusCode, 101)
|
|
@@ -53,8 +54,9 @@ class UpgradeHandler extends AsyncResource {
|
|
|
53
54
|
removeSignal(this)
|
|
54
55
|
|
|
55
56
|
this.callback = null
|
|
57
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
56
58
|
this.runInAsyncScope(callback, null, null, {
|
|
57
|
-
headers
|
|
59
|
+
headers,
|
|
58
60
|
socket,
|
|
59
61
|
opaque,
|
|
60
62
|
context
|
package/lib/balanced-pool.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
BalancedPoolMissingUpstreamError
|
|
4
|
+
BalancedPoolMissingUpstreamError,
|
|
5
|
+
InvalidArgumentError
|
|
5
6
|
} = require('./core/errors')
|
|
6
7
|
const {
|
|
7
8
|
PoolBase,
|
|
@@ -13,11 +14,17 @@ const {
|
|
|
13
14
|
} = require('./pool-base')
|
|
14
15
|
const Pool = require('./pool')
|
|
15
16
|
const { kUrl } = require('./core/symbols')
|
|
17
|
+
const { parseOrigin } = require('./core/util')
|
|
18
|
+
const kFactory = Symbol('factory')
|
|
16
19
|
|
|
17
20
|
const kOptions = Symbol('options')
|
|
18
21
|
|
|
22
|
+
function defaultFactory (origin, opts) {
|
|
23
|
+
return new Pool(origin, opts);
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
class BalancedPool extends PoolBase {
|
|
20
|
-
constructor (upstreams = [], opts = {}) {
|
|
27
|
+
constructor (upstreams = [], { factory = defaultFactory, ...opts } = {}) {
|
|
21
28
|
super()
|
|
22
29
|
|
|
23
30
|
this[kOptions] = opts
|
|
@@ -26,28 +33,38 @@ class BalancedPool extends PoolBase {
|
|
|
26
33
|
upstreams = [upstreams]
|
|
27
34
|
}
|
|
28
35
|
|
|
36
|
+
if (typeof factory !== 'function') {
|
|
37
|
+
throw new InvalidArgumentError('factory must be a function.')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this[kFactory] = factory
|
|
41
|
+
|
|
29
42
|
for (const upstream of upstreams) {
|
|
30
43
|
this.addUpstream(upstream)
|
|
31
44
|
}
|
|
32
45
|
}
|
|
33
46
|
|
|
34
47
|
addUpstream (upstream) {
|
|
48
|
+
const upstreamOrigin = parseOrigin(upstream).origin
|
|
49
|
+
|
|
35
50
|
if (this[kClients].find((pool) => (
|
|
36
|
-
pool[kUrl].origin ===
|
|
51
|
+
pool[kUrl].origin === upstreamOrigin &&
|
|
37
52
|
pool.closed !== true &&
|
|
38
53
|
pool.destroyed !== true
|
|
39
54
|
))) {
|
|
40
55
|
return this
|
|
41
56
|
}
|
|
42
57
|
|
|
43
|
-
this[kAddClient](
|
|
58
|
+
this[kAddClient](this[kFactory](upstreamOrigin, Object.assign({}, this[kOptions])))
|
|
44
59
|
|
|
45
60
|
return this
|
|
46
61
|
}
|
|
47
62
|
|
|
48
63
|
removeUpstream (upstream) {
|
|
64
|
+
const upstreamOrigin = parseOrigin(upstream).origin
|
|
65
|
+
|
|
49
66
|
const pool = this[kClients].find((pool) => (
|
|
50
|
-
pool[kUrl].origin ===
|
|
67
|
+
pool[kUrl].origin === upstreamOrigin &&
|
|
51
68
|
pool.closed !== true &&
|
|
52
69
|
pool.destroyed !== true
|
|
53
70
|
))
|
package/lib/client.js
CHANGED
|
@@ -408,6 +408,8 @@ const constants = require('./llhttp/constants')
|
|
|
408
408
|
const EMPTY_BUF = Buffer.alloc(0)
|
|
409
409
|
|
|
410
410
|
async function lazyllhttp () {
|
|
411
|
+
const llhttpWasmData = process.env.JEST_WORKER_ID ? require('./llhttp/llhttp.wasm.js') : undefined
|
|
412
|
+
|
|
411
413
|
let mod
|
|
412
414
|
try {
|
|
413
415
|
mod = await WebAssembly.compile(Buffer.from(require('./llhttp/llhttp_simd.wasm.js'), 'base64'))
|
|
@@ -418,7 +420,7 @@ async function lazyllhttp () {
|
|
|
418
420
|
// being enabled, but the occurring of this other error
|
|
419
421
|
// * https://github.com/emscripten-core/emscripten/issues/11495
|
|
420
422
|
// got me to remove that check to avoid breaking Node 12.
|
|
421
|
-
mod = await WebAssembly.compile(Buffer.from(require('./llhttp/llhttp.wasm.js'), 'base64'))
|
|
423
|
+
mod = await WebAssembly.compile(Buffer.from(llhttpWasmData || require('./llhttp/llhttp.wasm.js'), 'base64'))
|
|
422
424
|
}
|
|
423
425
|
|
|
424
426
|
return await WebAssembly.instantiate(mod, {
|
|
@@ -1481,7 +1483,7 @@ function write (client, request) {
|
|
|
1481
1483
|
|
|
1482
1484
|
let header = `${method} ${path} HTTP/1.1\r\n`
|
|
1483
1485
|
|
|
1484
|
-
if (host) {
|
|
1486
|
+
if (typeof host === 'string') {
|
|
1485
1487
|
header += `host: ${host}\r\n`
|
|
1486
1488
|
} else {
|
|
1487
1489
|
header += client[kHostHeader]
|
package/lib/core/symbols.js
CHANGED
|
@@ -22,6 +22,8 @@ module.exports = {
|
|
|
22
22
|
kPending: Symbol('pending'),
|
|
23
23
|
kSize: Symbol('size'),
|
|
24
24
|
kBusy: Symbol('busy'),
|
|
25
|
+
kQueued: Symbol('queued'),
|
|
26
|
+
kFree: Symbol('free'),
|
|
25
27
|
kConnected: Symbol('connected'),
|
|
26
28
|
kClosed: Symbol('closed'),
|
|
27
29
|
kNeedDrain: Symbol('need drain'),
|
package/lib/core/util.js
CHANGED
|
@@ -200,6 +200,10 @@ function parseHeaders (headers, obj = {}) {
|
|
|
200
200
|
return obj
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
function parseRawHeaders (headers) {
|
|
204
|
+
return headers.map(header => header.toString());
|
|
205
|
+
}
|
|
206
|
+
|
|
203
207
|
function isBuffer (buffer) {
|
|
204
208
|
// See, https://github.com/mcollina/undici/pull/319
|
|
205
209
|
return buffer instanceof Uint8Array || Buffer.isBuffer(buffer)
|
|
@@ -339,6 +343,7 @@ module.exports = {
|
|
|
339
343
|
isIterable,
|
|
340
344
|
isAsyncIterable,
|
|
341
345
|
isDestroyed,
|
|
346
|
+
parseRawHeaders,
|
|
342
347
|
parseHeaders,
|
|
343
348
|
parseKeepAliveTimeout,
|
|
344
349
|
destroy,
|
package/lib/fetch/request.js
CHANGED
|
@@ -127,12 +127,12 @@ class Request {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// 10. If init["window"] exists and is non-null, then throw a TypeError.
|
|
130
|
-
if (
|
|
130
|
+
if (init.window !== undefined && init.window != null) {
|
|
131
131
|
throw new TypeError(`'window' option '${window}' must be null`)
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
// 11. If init["window"] exists, then set window to "no-window".
|
|
135
|
-
if (
|
|
135
|
+
if (init.window !== undefined) {
|
|
136
136
|
window = 'no-window'
|
|
137
137
|
}
|
|
138
138
|
|
|
@@ -212,7 +212,7 @@ class Request {
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
// 14. If init["referrer"] exists, then:
|
|
215
|
-
if (
|
|
215
|
+
if (init.referrer !== undefined) {
|
|
216
216
|
// 1. Let referrer be init["referrer"].
|
|
217
217
|
const referrer = init.referrer
|
|
218
218
|
|
|
@@ -248,7 +248,7 @@ class Request {
|
|
|
248
248
|
|
|
249
249
|
// 15. If init["referrerPolicy"] exists, then set request’s referrer policy
|
|
250
250
|
// to it.
|
|
251
|
-
if (
|
|
251
|
+
if (init.referrerPolicy !== undefined) {
|
|
252
252
|
request.referrerPolicy = init.referrerPolicy
|
|
253
253
|
if (!referrerPolicy.includes(request.referrerPolicy)) {
|
|
254
254
|
throw new TypeError(
|
|
@@ -259,7 +259,7 @@ class Request {
|
|
|
259
259
|
|
|
260
260
|
// 16. Let mode be init["mode"] if it exists, and fallbackMode otherwise.
|
|
261
261
|
let mode
|
|
262
|
-
if (
|
|
262
|
+
if (init.mode !== undefined) {
|
|
263
263
|
mode = init.mode
|
|
264
264
|
if (!requestMode.includes(mode)) {
|
|
265
265
|
throw new TypeError(
|
|
@@ -282,7 +282,7 @@ class Request {
|
|
|
282
282
|
|
|
283
283
|
// 19. If init["credentials"] exists, then set request’s credentials mode
|
|
284
284
|
// to it.
|
|
285
|
-
if (
|
|
285
|
+
if (init.credentials !== undefined) {
|
|
286
286
|
request.credentials = init.credentials
|
|
287
287
|
if (!requestCredentials.includes(request.credentials)) {
|
|
288
288
|
throw new TypeError(
|
|
@@ -292,7 +292,7 @@ class Request {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
// 18. If init["cache"] exists, then set request’s cache mode to it.
|
|
295
|
-
if (
|
|
295
|
+
if (init.cache !== undefined) {
|
|
296
296
|
request.cache = init.cache
|
|
297
297
|
if (!requestCache.includes(request.cache)) {
|
|
298
298
|
throw new TypeError(
|
|
@@ -310,7 +310,7 @@ class Request {
|
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
// 22. If init["redirect"] exists, then set request’s redirect mode to it.
|
|
313
|
-
if (
|
|
313
|
+
if (init.redirect !== undefined) {
|
|
314
314
|
request.redirect = init.redirect
|
|
315
315
|
if (!requestRedirect.includes(request.redirect)) {
|
|
316
316
|
throw new TypeError(
|
|
@@ -320,17 +320,17 @@ class Request {
|
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
// 23. If init["integrity"] exists, then set request’s integrity metadata to it.
|
|
323
|
-
if (
|
|
323
|
+
if (init.integrity !== undefined && init.integrity != null) {
|
|
324
324
|
request.integrity = String(init.integrity)
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
// 24. If init["keepalive"] exists, then set request’s keepalive to it.
|
|
328
|
-
if (
|
|
328
|
+
if (init.keepalive !== undefined) {
|
|
329
329
|
request.keepalive = Boolean(init.keepalive)
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
// 25. If init["method"] exists, then:
|
|
333
|
-
if (
|
|
333
|
+
if (init.method !== undefined) {
|
|
334
334
|
// 1. Let method be init["method"].
|
|
335
335
|
let method = init.method
|
|
336
336
|
|
|
@@ -353,7 +353,7 @@ class Request {
|
|
|
353
353
|
}
|
|
354
354
|
|
|
355
355
|
// 26. If init["signal"] exists, then set signal to it.
|
|
356
|
-
if (
|
|
356
|
+
if (init.signal !== undefined) {
|
|
357
357
|
signal = init.signal
|
|
358
358
|
}
|
|
359
359
|
|
|
@@ -416,7 +416,7 @@ class Request {
|
|
|
416
416
|
let headers = new Headers(this.headers)
|
|
417
417
|
|
|
418
418
|
// 2. If init["headers"] exists, then set headers to init["headers"].
|
|
419
|
-
if (
|
|
419
|
+
if (init.headers !== undefined) {
|
|
420
420
|
headers = init.headers
|
|
421
421
|
}
|
|
422
422
|
|
|
@@ -442,7 +442,7 @@ class Request {
|
|
|
442
442
|
// non-null, and request’s method is `GET` or `HEAD`, then throw a
|
|
443
443
|
// TypeError.
|
|
444
444
|
if (
|
|
445
|
-
((
|
|
445
|
+
((init.body !== undefined && init.body != null) || inputBody != null) &&
|
|
446
446
|
(request.method === 'GET' || request.method === 'HEAD')
|
|
447
447
|
) {
|
|
448
448
|
throw new TypeError('Request with GET/HEAD method cannot have body.')
|
|
@@ -452,7 +452,7 @@ class Request {
|
|
|
452
452
|
let initBody = null
|
|
453
453
|
|
|
454
454
|
// 36. If init["body"] exists and is non-null, then:
|
|
455
|
-
if (
|
|
455
|
+
if (init.body !== undefined && init.body != null) {
|
|
456
456
|
// 1. Let Content-Type be null.
|
|
457
457
|
// 2. Set initBody and Content-Type to the result of extracting
|
|
458
458
|
// init["body"], with keepalive set to request’s keepalive.
|
package/lib/pool-base.js
CHANGED
|
@@ -7,7 +7,8 @@ const {
|
|
|
7
7
|
InvalidArgumentError
|
|
8
8
|
} = require('./core/errors')
|
|
9
9
|
const FixedQueue = require('./node/fixed-queue')
|
|
10
|
-
const { kSize, kRunning, kPending, kBusy, kUrl } = require('./core/symbols')
|
|
10
|
+
const { kConnected, kSize, kRunning, kPending, kQueued, kBusy, kFree, kUrl } = require('./core/symbols')
|
|
11
|
+
const PoolStats = require('./pool-stats')
|
|
11
12
|
|
|
12
13
|
const kClients = Symbol('clients')
|
|
13
14
|
const kNeedDrain = Symbol('needDrain')
|
|
@@ -19,10 +20,10 @@ const kOnDrain = Symbol('onDrain')
|
|
|
19
20
|
const kOnConnect = Symbol('onConnect')
|
|
20
21
|
const kOnDisconnect = Symbol('onDisconnect')
|
|
21
22
|
const kOnConnectionError = Symbol('onConnectionError')
|
|
22
|
-
const kQueued = Symbol('queued')
|
|
23
23
|
const kGetDispatcher = Symbol('get dispatcher')
|
|
24
24
|
const kAddClient = Symbol('add client')
|
|
25
25
|
const kRemoveClient = Symbol('remove client')
|
|
26
|
+
const kStats = Symbol('stats')
|
|
26
27
|
|
|
27
28
|
class PoolBase extends Dispatcher {
|
|
28
29
|
constructor () {
|
|
@@ -77,12 +78,22 @@ class PoolBase extends Dispatcher {
|
|
|
77
78
|
this[kOnConnectionError] = (origin, targets, err) => {
|
|
78
79
|
pool.emit('connectionError', origin, [pool, ...targets], err)
|
|
79
80
|
}
|
|
81
|
+
|
|
82
|
+
this[kStats] = new PoolStats(this)
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
get [kBusy] () {
|
|
83
86
|
return this[kNeedDrain]
|
|
84
87
|
}
|
|
85
88
|
|
|
89
|
+
get [kConnected] () {
|
|
90
|
+
return this[kClients].filter(client => client[kConnected]).length
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get [kFree] () {
|
|
94
|
+
return this[kClients].filter(client => client[kConnected] && !client[kNeedDrain]).length
|
|
95
|
+
}
|
|
96
|
+
|
|
86
97
|
get [kPending] () {
|
|
87
98
|
let ret = this[kQueued]
|
|
88
99
|
for (const { [kPending]: pending } of this[kClients]) {
|
|
@@ -107,6 +118,10 @@ class PoolBase extends Dispatcher {
|
|
|
107
118
|
return ret
|
|
108
119
|
}
|
|
109
120
|
|
|
121
|
+
get stats() {
|
|
122
|
+
return this[kStats];
|
|
123
|
+
}
|
|
124
|
+
|
|
110
125
|
get destroyed () {
|
|
111
126
|
return this[kDestroyed]
|
|
112
127
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const { kFree, kConnected, kPending, kQueued, kRunning, kSize } = require('./core/symbols')
|
|
2
|
+
const kPool = Symbol('pool')
|
|
3
|
+
|
|
4
|
+
class PoolStats {
|
|
5
|
+
constructor(pool) {
|
|
6
|
+
this[kPool] = pool
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get connected() {
|
|
10
|
+
return this[kPool][kConnected]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get free() {
|
|
14
|
+
return this[kPool][kFree]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get pending() {
|
|
18
|
+
return this[kPool][kPending]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get queued() {
|
|
22
|
+
return this[kPool][kQueued]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get running() {
|
|
26
|
+
return this[kPool][kRunning]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get size() {
|
|
30
|
+
return this[kPool][kSize]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = PoolStats
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "undici",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.15.0",
|
|
4
4
|
"description": "An HTTP/1.1 client, written from scratch for Node.js",
|
|
5
5
|
"homepage": "https://undici.nodejs.org",
|
|
6
6
|
"bugs": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"test": "npm run test:tap && npm run test:node-fetch && npm run test:fetch && npm run test:jest && tsd",
|
|
49
49
|
"test:node-fetch": "node scripts/verifyVersion.js 16 || mocha test/node-fetch",
|
|
50
50
|
"test:fetch": "node scripts/verifyVersion.js 16 || tap test/fetch/*.js",
|
|
51
|
-
"test:jest": "jest
|
|
51
|
+
"test:jest": "jest",
|
|
52
52
|
"test:tap": "tap test/*.js test/diagnostics-channel/*.js",
|
|
53
53
|
"test:tdd": "tap test/*.js test/diagnostics-channel/*.js -w",
|
|
54
54
|
"test:typescript": "tsd",
|
|
@@ -114,5 +114,8 @@
|
|
|
114
114
|
"esnext"
|
|
115
115
|
]
|
|
116
116
|
}
|
|
117
|
+
},
|
|
118
|
+
"jest": {
|
|
119
|
+
"testMatch": ["<rootDir>/test/jest/**"]
|
|
117
120
|
}
|
|
118
121
|
}
|
package/types/dispatcher.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Duplex, Readable, Writable } from 'stream'
|
|
|
3
3
|
import { EventEmitter } from 'events'
|
|
4
4
|
import { IncomingHttpHeaders } from 'http'
|
|
5
5
|
import { Blob } from 'buffer'
|
|
6
|
+
import BodyReadable from './readable'
|
|
6
7
|
|
|
7
8
|
type AbortSignal = unknown;
|
|
8
9
|
|
|
@@ -64,6 +65,8 @@ declare namespace Dispatcher {
|
|
|
64
65
|
opaque?: unknown;
|
|
65
66
|
/** Default: 0 */
|
|
66
67
|
maxRedirections?: number;
|
|
68
|
+
/** Default: `null` */
|
|
69
|
+
responseHeader?: 'raw' | null;
|
|
67
70
|
}
|
|
68
71
|
export interface RequestOptions extends DispatchOptions {
|
|
69
72
|
/** Default: `null` */
|
|
@@ -73,7 +76,9 @@ declare namespace Dispatcher {
|
|
|
73
76
|
/** Default: 0 */
|
|
74
77
|
maxRedirections?: number;
|
|
75
78
|
/** Default: `null` */
|
|
76
|
-
onInfo?: (info: {statusCode: number, headers: Record<string, string | string[]>}) => void;
|
|
79
|
+
onInfo?: (info: { statusCode: number, headers: Record<string, string | string[]> }) => void;
|
|
80
|
+
/** Default: `null` */
|
|
81
|
+
responseHeader?: 'raw' | null;
|
|
77
82
|
}
|
|
78
83
|
export interface PipelineOptions extends RequestOptions {
|
|
79
84
|
/** `true` if the `handler` will return an object stream. Default: `false` */
|
|
@@ -91,6 +96,8 @@ declare namespace Dispatcher {
|
|
|
91
96
|
signal?: AbortSignal | EventEmitter | null;
|
|
92
97
|
/** Default: 0 */
|
|
93
98
|
maxRedirections?: number;
|
|
99
|
+
/** Default: `null` */
|
|
100
|
+
responseHeader?: 'raw' | null;
|
|
94
101
|
}
|
|
95
102
|
export interface ConnectData {
|
|
96
103
|
statusCode: number;
|
|
@@ -101,7 +108,7 @@ declare namespace Dispatcher {
|
|
|
101
108
|
export interface ResponseData {
|
|
102
109
|
statusCode: number;
|
|
103
110
|
headers: IncomingHttpHeaders;
|
|
104
|
-
body:
|
|
111
|
+
body: BodyReadable & BodyMixin;
|
|
105
112
|
trailers: Record<string, string>;
|
|
106
113
|
opaque: unknown;
|
|
107
114
|
context: object;
|
|
@@ -110,7 +117,7 @@ declare namespace Dispatcher {
|
|
|
110
117
|
statusCode: number;
|
|
111
118
|
headers: IncomingHttpHeaders;
|
|
112
119
|
opaque: unknown;
|
|
113
|
-
body:
|
|
120
|
+
body: BodyReadable;
|
|
114
121
|
context: object;
|
|
115
122
|
}
|
|
116
123
|
export interface StreamData {
|
package/types/fetch.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export interface BodyMixin {
|
|
|
42
42
|
readonly text: () => Promise<string>
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export type HeadersInit = string[][] | Record<string, string
|
|
45
|
+
export type HeadersInit = string[][] | Record<string, string | ReadonlyArray<string>> | Headers
|
|
46
46
|
|
|
47
47
|
export declare class Headers implements Iterable<[string, string]> {
|
|
48
48
|
constructor (init?: HeadersInit)
|
|
@@ -107,7 +107,7 @@ export interface RequestInit {
|
|
|
107
107
|
readonly window?: null
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
export type ReferrerPolicy =
|
|
110
|
+
export type ReferrerPolicy =
|
|
111
111
|
| ''
|
|
112
112
|
| 'no-referrer'
|
|
113
113
|
| 'no-referrer-when-downgrade'
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Readable } from "stream";
|
|
2
|
+
import { Blob } from 'buffer'
|
|
3
|
+
|
|
4
|
+
export = BodyReadable
|
|
5
|
+
|
|
6
|
+
declare class BodyReadable extends Readable {
|
|
7
|
+
constructor(
|
|
8
|
+
resume?: (this: Readable, size: number) => void | null,
|
|
9
|
+
abort?: () => void | null,
|
|
10
|
+
contentType?: string
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
/** Consumes and returns the body as a string
|
|
14
|
+
* https://fetch.spec.whatwg.org/#dom-body-text
|
|
15
|
+
*/
|
|
16
|
+
text(): Promise<string>
|
|
17
|
+
|
|
18
|
+
/** Consumes and returns the body as a JavaScript Object
|
|
19
|
+
* https://fetch.spec.whatwg.org/#dom-body-json
|
|
20
|
+
*/
|
|
21
|
+
json(): Promise<any>
|
|
22
|
+
|
|
23
|
+
/** Consumes and returns the body as a Blob
|
|
24
|
+
* https://fetch.spec.whatwg.org/#dom-body-blob
|
|
25
|
+
*/
|
|
26
|
+
blob(): Promise<Blob>
|
|
27
|
+
|
|
28
|
+
/** Consumes and returns the body as an ArrayBuffer
|
|
29
|
+
* https://fetch.spec.whatwg.org/#dom-body-arraybuffer
|
|
30
|
+
*/
|
|
31
|
+
arrayBuffer(): Promise<ArrayBuffer>
|
|
32
|
+
|
|
33
|
+
/** Not implemented
|
|
34
|
+
*
|
|
35
|
+
* https://fetch.spec.whatwg.org/#dom-body-formdata
|
|
36
|
+
*/
|
|
37
|
+
formData(): Promise<never>
|
|
38
|
+
|
|
39
|
+
/** Returns true if the body is not null and the body has been consumed
|
|
40
|
+
*
|
|
41
|
+
* Otherwise, returns false
|
|
42
|
+
*
|
|
43
|
+
* https://fetch.spec.whatwg.org/#dom-body-bodyused
|
|
44
|
+
*/
|
|
45
|
+
readonly bodyUsed: boolean
|
|
46
|
+
|
|
47
|
+
/** Throws on node 16.6.0
|
|
48
|
+
*
|
|
49
|
+
* If body is null, it should return null as the body
|
|
50
|
+
*
|
|
51
|
+
* If body is not null, should return the body as a ReadableStream
|
|
52
|
+
*
|
|
53
|
+
* https://fetch.spec.whatwg.org/#dom-body-body
|
|
54
|
+
*/
|
|
55
|
+
readonly body: never | undefined
|
|
56
|
+
|
|
57
|
+
/** Dumps the response body by reading `limit` number of bytes.
|
|
58
|
+
* @param opts.limit Number of bytes to read (optional) - Default: 262144
|
|
59
|
+
*/
|
|
60
|
+
dump(opts?: { limit: number }): Promise<void>
|
|
61
|
+
}
|