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.
@@ -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** `PoolOptions` (optional)
14
+ * **options** `BalancedPoolOptions` (optional)
15
15
 
16
- ### Parameter: `PoolOptions`
16
+ ### Parameter: `BalancedPoolOptions`
17
17
 
18
- The `PoolOptions` are passed to each of the `Pool` instances being created.
18
+ Extends: [`PoolOptions`](Pool.md#parameter-pooloptions)
19
19
 
20
- See: [`PoolOptions`](Pool.md#parameter-pooloptions)
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)`
@@ -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 extentions:
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, init) {
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.call(dispatcher, resource, init)
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
@@ -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, headers, socket) {
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: util.parseHeaders(headers),
56
+ headers,
55
57
  socket,
56
58
  opaque,
57
59
  context
@@ -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, headers, resume) {
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.onInfo({ statusCode, headers: util.parseHeaders(headers) })
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: util.parseHeaders(headers),
179
+ headers,
177
180
  opaque,
178
181
  body: this.res,
179
182
  context
@@ -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, headers, resume) {
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.onInfo({ statusCode, headers: util.parseHeaders(headers) })
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(headers)
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: parsedHeaders,
93
+ headers,
91
94
  trailers: this.trailers,
92
95
  opaque,
93
96
  body,
@@ -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, headers, resume) {
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.onInfo({ statusCode, headers: util.parseHeaders(headers) })
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: util.parseHeaders(headers),
94
+ headers,
92
95
  opaque,
93
96
  context
94
97
  })
@@ -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, headers, socket) {
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: util.parseHeaders(headers),
59
+ headers,
58
60
  socket,
59
61
  opaque,
60
62
  context
@@ -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 === upstream &&
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](new Pool(upstream, Object.assign({}, this[kOptions])))
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 === upstream &&
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]
@@ -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,
@@ -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 ('window' in init && window != null) {
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 ('window' in init) {
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 ('referrer' in init) {
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 ('referrerPolicy' in init) {
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 ('mode' in init) {
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 ('credentials' in init) {
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 ('cache' in init) {
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 ('redirect' in init) {
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 ('integrity' in init && init.integrity != null) {
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 ('keepalive' in init) {
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 ('method' in init) {
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 ('signal' in init) {
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 ('headers' in init) {
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
- (('body' in init && init.body != null) || inputBody != null) &&
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 ('body' in init && init.body != null) {
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.14.1",
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 test/jest/test",
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
  }
@@ -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: Readable & BodyMixin;
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: Readable;
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> | Headers
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
+ }