undici 4.7.2 → 4.8.2

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.
@@ -0,0 +1,98 @@
1
+ # Class: BalancedPool
2
+
3
+ Extends: `undici.Dispatcher`
4
+
5
+ A pool of [Pool](docs/api/Pool.md) instances connected to multiple upstreams.
6
+
7
+ Requests are not guaranteed to be dispatched in order of invocation.
8
+
9
+ ## `new BalancedPool(upstreams [, options])`
10
+
11
+ Arguments:
12
+
13
+ * **upstreams** `URL | string | string[]` - It should only include the **protocol, hostname, and port**.
14
+ * **options** `PoolOptions` (optional)
15
+
16
+ ### Parameter: `PoolOptions`
17
+
18
+ The `PoolOptions` are passed to each of the `Pool` instances being created.
19
+
20
+ See: [`PoolOptions`](docs/api/Pool.md#parameter-pooloptions)
21
+
22
+ ## Instance Properties
23
+
24
+ ### `BalancedPool.upstreams`
25
+
26
+ Returns an array of upstreams that were previously added.
27
+
28
+ ### `BalancedPool.busy`
29
+
30
+ Implements [Client.busy](docs/api/Client.md#clientbusy)
31
+
32
+ ### `BalancedPool.closed`
33
+
34
+ Implements [Client.closed](docs/api/Client.md#clientclosed)
35
+
36
+ ### `BalancedPool.destroyed`
37
+
38
+ Implements [Client.destroyed](docs/api/Client.md#clientdestroyed)
39
+
40
+ ## Instance Methods
41
+
42
+ ### `BalancedPool.addUpstream(upstream)`
43
+
44
+ Add an upstream.
45
+
46
+ Arguments:
47
+
48
+ * **upstream** `string` - It should only include the **protocol, hostname, and port**.
49
+
50
+ ### `BalancedPool.removeUpstream(upstream)`
51
+
52
+ Removes an upstream that was previously addded.
53
+
54
+ ### `BalancedPool.close([callback])`
55
+
56
+ Implements [`Dispatcher.close([callback])`](docs/api/Dispatcher.md#clientclose-callback-).
57
+
58
+ ### `BalancedPool.destroy([error, callback])`
59
+
60
+ Implements [`Dispatcher.destroy([error, callback])`](docs/api/Dispatcher.md#dispatcher-callback-).
61
+
62
+ ### `BalancedPool.connect(options[, callback])`
63
+
64
+ See [`Dispatcher.connect(options[, callback])`](docs/api/Dispatcher.md#clientconnectoptions--callback).
65
+
66
+ ### `BalancedPool.dispatch(options, handlers)`
67
+
68
+ Implements [`Dispatcher.dispatch(options, handlers)`](docs/api/Dispatcher.md#clientdispatchoptions-handlers).
69
+
70
+ ### `BalancedPool.pipeline(options, handler)`
71
+
72
+ See [`Dispatcher.pipeline(options, handler)`](docs/api/Dispatcher.md#clientpipelineoptions-handler).
73
+
74
+ ### `BalancedPool.request(options[, callback])`
75
+
76
+ See [`Dispatcher.request(options [, callback])`](docs/api/Dispatcher.md#clientrequestoptions--callback).
77
+
78
+ ### `BalancedPool.stream(options, factory[, callback])`
79
+
80
+ See [`Dispatcher.stream(options, factory[, callback])`](docs/api/Dispatcher.md#clientstreamoptions-factory--callback).
81
+
82
+ ### `BalancedPool.upgrade(options[, callback])`
83
+
84
+ See [`Dispatcher.upgrade(options[, callback])`](docs/api/Dispatcher.md#clientupgradeoptions-callback).
85
+
86
+ ## Instance Events
87
+
88
+ ### Event: `'connect'`
89
+
90
+ See [Dispatcher Event: `'connect'`](docs/api/Dispatcher.md#event-connect).
91
+
92
+ ### Event: `'disconnect'`
93
+
94
+ See [Dispatcher Event: `'disconnect'`](docs/api/Dispatcher.md#event-connect).
95
+
96
+ ### Event: `'drain'`
97
+
98
+ See [Dispatcher Event: `'drain'`](docs/api/Dispatcher.md#event-connect).
@@ -0,0 +1,100 @@
1
+ # Class: ProxyAgent
2
+
3
+ Extends: `undici.Dispatcher`
4
+
5
+ A Proxy Agent class that implements the Agent API. It allows the connection through proxy in a simple way.
6
+
7
+ ## `new ProxyAgent([options])`
8
+
9
+ Arguments:
10
+
11
+ * **options** `ProxyAgentOptions` (required) - It extends the `Agent` options.
12
+
13
+ Returns: `ProxyAgent`
14
+
15
+ ### Parameter: `ProxyAgentOptions`
16
+
17
+ Extends: [`AgentOptions`](docs/api/Agent.md#parameter-agentoptions)
18
+
19
+ * **uri** `string` (required) - It can be passed either by a string or a object containing `uri` as string.
20
+
21
+ Examples:
22
+
23
+ ```js
24
+ import { ProxyAgent } from 'undici'
25
+
26
+ const proxyAgent = new ProxyAgent('my.proxy.server')
27
+ // or
28
+ const proxyAgent = new ProxyAgent({ uri: 'my.proxy.server' })
29
+ ```
30
+
31
+ #### Example - Basic ProxyAgent instantiation
32
+
33
+ This will instantiate the ProxyAgent. It will not do anything until registered as the agent to use with requests.
34
+
35
+ ```js
36
+ import { ProxyAgent } from 'undici'
37
+
38
+ const proxyAgent = new ProxyAgent('my.proxy.server')
39
+ ```
40
+
41
+ #### Example - Basic Proxy Request with global agent dispatcher
42
+
43
+ ```js
44
+ import { setGlobalDispatcher, request, ProxyAgent } from 'undici'
45
+
46
+ const proxyAgent = new ProxyAgent('my.proxy.server')
47
+ setGlobalDispatcher(proxyAgent)
48
+
49
+ const { statusCode, body } = await request('http://localhost:3000/foo')
50
+
51
+ console.log('response received', statusCode) // response received 200
52
+
53
+ for await (const data of body) {
54
+ console.log('data', data.toString('utf8')) // data foo
55
+ }
56
+ ```
57
+
58
+ #### Example - Basic Proxy Request with local agent dispatcher
59
+
60
+ ```js
61
+ import { ProxyAgent, request } from 'undici'
62
+
63
+ const proxyAgent = new ProxyAgent('my.proxy.server')
64
+
65
+ const {
66
+ statusCode,
67
+ body
68
+ } = await request('http://localhost:3000/foo', { dispatcher: proxyAgent })
69
+
70
+ console.log('response received', statusCode) // response received 200
71
+
72
+ for await (const data of body) {
73
+ console.log('data', data.toString('utf8')) // data foo
74
+ }
75
+ ```
76
+
77
+ ### `ProxyAgent.close()`
78
+
79
+ Closes the proxy agent and waits for registered pools and clients to also close before resolving.
80
+
81
+ Returns: `Promise<void>`
82
+
83
+ #### Example - clean up after tests are complete
84
+
85
+ ```js
86
+ import { ProxyAgent, setGlobalDispatcher } from 'undici'
87
+
88
+ const proxyAgent = new ProxyAgent('my.proxy.server')
89
+ setGlobalDispatcher(proxyAgent)
90
+
91
+ await proxyAgent.close()
92
+ ```
93
+
94
+ ### `ProxyAgent.dispatch(options, handlers)`
95
+
96
+ Implements [`Agent.dispatch(options, handlers)`](docs/api/Agent.md#parameter-agentdispatchoptions).
97
+
98
+ ### `ProxyAgent.request(options[, callback])`
99
+
100
+ See [`Dispatcher.request(options [, callback])`](docs/api/Dispatcher.md#clientrequestoptions--callback).
@@ -1,6 +1,9 @@
1
1
  # Connecting through a proxy
2
2
 
3
- Connecting through a proxy is possible by properly configuring the `Client` or `Pool` constructor and request.
3
+ Connecting through a proxy is possible by:
4
+
5
+ - Using [AgentProxy](docs/api/ProxyAgent.md).
6
+ - Configuring `Client` or `Pool` constructor.
4
7
 
5
8
  The proxy url should be passed to the `Client` or `Pool` constructor, while the upstream server url
6
9
  should be added to every request call in the `path`.
package/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import Dispatcher = require('./types/dispatcher')
2
2
  import { setGlobalDispatcher, getGlobalDispatcher } from './types/global-dispatcher'
3
3
  import Pool = require('./types/pool')
4
+ import BalancedPool = require('./types/balanced-pool')
4
5
  import Client = require('./types/client')
5
6
  import buildConnector = require('./types/connector')
6
7
  import errors = require('./types/errors')
@@ -9,13 +10,14 @@ import MockClient = require('./types/mock-client')
9
10
  import MockPool = require('./types/mock-pool')
10
11
  import MockAgent = require('./types/mock-agent')
11
12
  import mockErrors = require('./types/mock-errors')
13
+ import ProxyAgent from './types/proxy-agent'
12
14
  import { request, pipeline, stream, connect, upgrade } from './types/api'
13
15
 
14
16
  export * from './types/fetch'
15
17
  export * from './types/file'
16
18
  export * from './types/formdata'
17
19
 
18
- export { Dispatcher, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, MockClient, MockPool, MockAgent, mockErrors }
20
+ export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent }
19
21
  export default Undici
20
22
 
21
23
  declare function Undici(url: string, opts: Pool.Options): Pool
@@ -23,6 +25,7 @@ declare function Undici(url: string, opts: Pool.Options): Pool
23
25
  declare namespace Undici {
24
26
  var Dispatcher: typeof import('./types/dispatcher')
25
27
  var Pool: typeof import('./types/pool');
28
+ var BalancedPool: typeof import('./types/balanced-pool');
26
29
  var Client: typeof import('./types/client');
27
30
  var buildConnector: typeof import('./types/connector');
28
31
  var errors: typeof import('./types/errors');
package/index.js CHANGED
@@ -4,6 +4,7 @@ const Client = require('./lib/client')
4
4
  const Dispatcher = require('./lib/dispatcher')
5
5
  const errors = require('./lib/core/errors')
6
6
  const Pool = require('./lib/pool')
7
+ const BalancedPool = require('./lib/balanced-pool')
7
8
  const Agent = require('./lib/agent')
8
9
  const util = require('./lib/core/util')
9
10
  const { InvalidArgumentError } = errors
@@ -13,6 +14,7 @@ const MockClient = require('./lib/mock/mock-client')
13
14
  const MockAgent = require('./lib/mock/mock-agent')
14
15
  const MockPool = require('./lib/mock/mock-pool')
15
16
  const mockErrors = require('./lib/mock/mock-errors')
17
+ const ProxyAgent = require('./lib/proxy-agent')
16
18
 
17
19
  const nodeVersion = process.versions.node.split('.')
18
20
  const nodeMajor = Number(nodeVersion[0])
@@ -23,7 +25,9 @@ Object.assign(Dispatcher.prototype, api)
23
25
  module.exports.Dispatcher = Dispatcher
24
26
  module.exports.Client = Client
25
27
  module.exports.Pool = Pool
28
+ module.exports.BalancedPool = BalancedPool
26
29
  module.exports.Agent = Agent
30
+ module.exports.ProxyAgent = ProxyAgent
27
31
 
28
32
  module.exports.buildConnector = buildConnector
29
33
  module.exports.errors = errors
@@ -89,8 +93,11 @@ module.exports.setGlobalDispatcher = setGlobalDispatcher
89
93
  module.exports.getGlobalDispatcher = getGlobalDispatcher
90
94
 
91
95
  if (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >= 5)) {
92
- const fetchImpl = require('./lib/fetch')
96
+ let fetchImpl = null
93
97
  module.exports.fetch = async function fetch (resource, init) {
98
+ if (!fetchImpl) {
99
+ fetchImpl = require('./lib/fetch')
100
+ }
94
101
  const dispatcher = getGlobalDispatcher()
95
102
  return fetchImpl.call(dispatcher, resource, init)
96
103
  }
@@ -0,0 +1,119 @@
1
+ 'use strict'
2
+
3
+ const { BalancedPoolMissingUpstreamError } = require('./core/errors')
4
+ const Dispatcher = require('./dispatcher')
5
+ const Pool = require('./pool')
6
+
7
+ const kPools = Symbol('kPools')
8
+ const kPoolOpts = Symbol('kPoolOpts')
9
+ const kUpstream = Symbol('kUpstream')
10
+ const kNeedDrain = Symbol('kNeedDrain')
11
+
12
+ class BalancedPool extends Dispatcher {
13
+ constructor (upstreams = [], opts = {}) {
14
+ super()
15
+
16
+ this[kPools] = []
17
+ this[kPoolOpts] = opts
18
+ this[kNeedDrain] = false
19
+
20
+ if (!Array.isArray(upstreams)) {
21
+ upstreams = [upstreams]
22
+ }
23
+
24
+ for (const upstream of upstreams) {
25
+ this.addUpstream(upstream)
26
+ }
27
+ }
28
+
29
+ addUpstream (upstream) {
30
+ if (this[kPools].find((pool) => pool[kUpstream] === upstream)) {
31
+ return this
32
+ }
33
+
34
+ const pool = new Pool(upstream, Object.assign({}, this[kPoolOpts]))
35
+
36
+ pool[kUpstream] = upstream
37
+
38
+ pool.on('connect', (...args) => {
39
+ this.emit('connect', ...args)
40
+ })
41
+
42
+ pool.on('disconnect', (...args) => {
43
+ this.emit('disconnect', ...args)
44
+ })
45
+
46
+ pool.on('drain', (...args) => {
47
+ if (this[kNeedDrain]) {
48
+ this[kNeedDrain] = false
49
+ this.emit('drain', ...args)
50
+ }
51
+ })
52
+
53
+ this[kPools].push(pool)
54
+ return this
55
+ }
56
+
57
+ dispatch (opts, handler) {
58
+ // We validate that pools is greater than 0,
59
+ // otherwise we would have to wait until an upstream
60
+ // is added, which might never happen.
61
+ if (this[kPools].length === 0) {
62
+ throw new BalancedPoolMissingUpstreamError()
63
+ }
64
+
65
+ let pool = this[kPools].shift()
66
+ this[kPools].push(pool)
67
+ if (pool.busy) {
68
+ pool = this[kPools].find(pool => !pool.busy) || pool
69
+ }
70
+ this[kNeedDrain] = !pool.dispatch(opts, handler)
71
+ return !this[kNeedDrain]
72
+ }
73
+
74
+ removeUpstream (upstream) {
75
+ const pool = this[kPools].find((pool) => pool[kUpstream] === upstream)
76
+ const idx = this[kPools].indexOf(pool)
77
+ this[kPools].splice(idx, 1)
78
+ pool.close()
79
+ return this
80
+ }
81
+
82
+ get upstreams () {
83
+ return this[kPools].map((p) => p[kUpstream])
84
+ }
85
+
86
+ get destroyed () {
87
+ return this[kPools].reduce((acc, pool) => acc && pool.destroyed, true)
88
+ }
89
+
90
+ get busy () {
91
+ return this[kPools].reduce((acc, pool) => acc && pool.busy, true) || false
92
+ }
93
+
94
+ get closed () {
95
+ return this[kPools].reduce((acc, pool) => acc && pool.closed, true)
96
+ }
97
+
98
+ close (cb) {
99
+ const p = Promise.all(this[kPools].map((p) => p.close()))
100
+
101
+ if (!cb) {
102
+ return p
103
+ }
104
+
105
+ p.then(() => process.nextTick(cb), (err) => process.nextTick(cb, err))
106
+ }
107
+
108
+ destroy (err, cb) {
109
+ const p = Promise.all(this[kPools].map((p) => p.destroy(err)))
110
+
111
+ if (!cb) {
112
+ return p
113
+ }
114
+
115
+ p.then(() => process.nextTick(cb))
116
+ }
117
+ }
118
+
119
+ module.exports = BalancedPool
package/lib/client.js CHANGED
@@ -397,76 +397,77 @@ class Client extends Dispatcher {
397
397
  }
398
398
  }
399
399
 
400
- const { resolve } = require('path')
401
- const { readFileSync } = require('fs')
402
400
  const constants = require('./llhttp/constants')
403
401
  const EMPTY_BUF = Buffer.alloc(0)
404
402
 
403
+ let llhttpPromise
405
404
  let llhttpInstance
406
- function lazyllhttp () {
407
- if (!llhttpInstance) {
408
- let mod
409
- try {
410
- mod = new WebAssembly.Module(readFileSync(resolve(__dirname, './llhttp/llhttp_simd.wasm')))
411
- } catch (e) {
412
- /* istanbul ignore next */
405
+ async function lazyllhttp () {
406
+ const { resolve } = require('path')
407
+ const { readFile } = require('fs').promises
413
408
 
414
- // We could check if the error was caused by the simd option not
415
- // being enabled, but the occurring of this other error
416
- // * https://github.com/emscripten-core/emscripten/issues/11495
417
- // got me to remove that check to avoid breaking Node 12.
418
- mod = new WebAssembly.Module(readFileSync(resolve(__dirname, './llhttp/llhttp.wasm')))
419
- }
409
+ let mod
410
+ try {
411
+ mod = await WebAssembly.compile(await readFile(resolve(__dirname, './llhttp/llhttp_simd.wasm')))
412
+ } catch (e) {
413
+ /* istanbul ignore next */
420
414
 
421
- llhttpInstance = new WebAssembly.Instance(mod, {
422
- env: {
423
- /* eslint-disable camelcase */
415
+ // We could check if the error was caused by the simd option not
416
+ // being enabled, but the occurring of this other error
417
+ // * https://github.com/emscripten-core/emscripten/issues/11495
418
+ // got me to remove that check to avoid breaking Node 12.
419
+ mod = await WebAssembly.compile(await readFile(resolve(__dirname, './llhttp/llhttp.wasm')))
420
+ }
424
421
 
425
- wasm_on_url: (p, at, len) => {
426
- /* istanbul ignore next */
427
- return 0
428
- },
429
- wasm_on_status: (p, at, len) => {
430
- assert.strictEqual(currentParser.ptr, p)
431
- const start = at - currentBufferPtr
432
- const end = start + len
433
- return currentParser.onStatus(currentBufferRef.slice(start, end)) || 0
434
- },
435
- wasm_on_message_begin: (p) => {
436
- assert.strictEqual(currentParser.ptr, p)
437
- return currentParser.onMessageBegin() || 0
438
- },
439
- wasm_on_header_field: (p, at, len) => {
440
- assert.strictEqual(currentParser.ptr, p)
441
- const start = at - currentBufferPtr
442
- const end = start + len
443
- return currentParser.onHeaderField(currentBufferRef.slice(start, end)) || 0
444
- },
445
- wasm_on_header_value: (p, at, len) => {
446
- assert.strictEqual(currentParser.ptr, p)
447
- const start = at - currentBufferPtr
448
- const end = start + len
449
- return currentParser.onHeaderValue(currentBufferRef.slice(start, end)) || 0
450
- },
451
- wasm_on_headers_complete: (p, statusCode, upgrade, shouldKeepAlive) => {
452
- assert.strictEqual(currentParser.ptr, p)
453
- return currentParser.onHeadersComplete(statusCode, Boolean(upgrade), Boolean(shouldKeepAlive)) || 0
454
- },
455
- wasm_on_body: (p, at, len) => {
456
- assert.strictEqual(currentParser.ptr, p)
457
- const start = at - currentBufferPtr
458
- const end = start + len
459
- return currentParser.onBody(currentBufferRef.slice(start, end)) || 0
460
- },
461
- wasm_on_message_complete: (p) => {
462
- assert.strictEqual(currentParser.ptr, p)
463
- return currentParser.onMessageComplete() || 0
464
- }
422
+ llhttpInstance = new WebAssembly.Instance(mod, {
423
+ env: {
424
+ /* eslint-disable camelcase */
465
425
 
466
- /* eslint-enable camelcase */
426
+ wasm_on_url: (p, at, len) => {
427
+ /* istanbul ignore next */
428
+ return 0
429
+ },
430
+ wasm_on_status: (p, at, len) => {
431
+ assert.strictEqual(currentParser.ptr, p)
432
+ const start = at - currentBufferPtr
433
+ const end = start + len
434
+ return currentParser.onStatus(currentBufferRef.slice(start, end)) || 0
435
+ },
436
+ wasm_on_message_begin: (p) => {
437
+ assert.strictEqual(currentParser.ptr, p)
438
+ return currentParser.onMessageBegin() || 0
439
+ },
440
+ wasm_on_header_field: (p, at, len) => {
441
+ assert.strictEqual(currentParser.ptr, p)
442
+ const start = at - currentBufferPtr
443
+ const end = start + len
444
+ return currentParser.onHeaderField(currentBufferRef.slice(start, end)) || 0
445
+ },
446
+ wasm_on_header_value: (p, at, len) => {
447
+ assert.strictEqual(currentParser.ptr, p)
448
+ const start = at - currentBufferPtr
449
+ const end = start + len
450
+ return currentParser.onHeaderValue(currentBufferRef.slice(start, end)) || 0
451
+ },
452
+ wasm_on_headers_complete: (p, statusCode, upgrade, shouldKeepAlive) => {
453
+ assert.strictEqual(currentParser.ptr, p)
454
+ return currentParser.onHeadersComplete(statusCode, Boolean(upgrade), Boolean(shouldKeepAlive)) || 0
455
+ },
456
+ wasm_on_body: (p, at, len) => {
457
+ assert.strictEqual(currentParser.ptr, p)
458
+ const start = at - currentBufferPtr
459
+ const end = start + len
460
+ return currentParser.onBody(currentBufferRef.slice(start, end)) || 0
461
+ },
462
+ wasm_on_message_complete: (p) => {
463
+ assert.strictEqual(currentParser.ptr, p)
464
+ return currentParser.onMessageComplete() || 0
467
465
  }
468
- })
469
- }
466
+
467
+ /* eslint-enable camelcase */
468
+ }
469
+ })
470
+
470
471
  return llhttpInstance
471
472
  }
472
473
 
@@ -480,10 +481,10 @@ const TIMEOUT_BODY = 2
480
481
  const TIMEOUT_IDLE = 3
481
482
 
482
483
  class Parser {
483
- constructor (client, socket) {
484
+ constructor (client, socket, { exports }) {
484
485
  assert(Number.isFinite(client[kMaxHeadersSize]) && client[kMaxHeadersSize] > 0)
485
486
 
486
- this.llhttp = lazyllhttp().exports
487
+ this.llhttp = exports
487
488
  this.ptr = this.llhttp.llhttp_alloc(constants.TYPE.RESPONSE)
488
489
  this.client = client
489
490
  this.socket = socket
@@ -1096,7 +1097,7 @@ function onSocketClose () {
1096
1097
  resume(client)
1097
1098
  }
1098
1099
 
1099
- function connect (client) {
1100
+ async function connect (client) {
1100
1101
  assert(!client[kConnecting])
1101
1102
  assert(!client[kSocket])
1102
1103
 
@@ -1128,78 +1129,97 @@ function connect (client) {
1128
1129
  })
1129
1130
  }
1130
1131
 
1131
- client[kConnector]({
1132
- host,
1133
- hostname,
1134
- protocol,
1135
- port,
1136
- servername: client[kServerName]
1137
- }, function (err, socket) {
1138
- client[kConnecting] = false
1139
-
1140
- if (err) {
1141
- if (channels.connectError.hasSubscribers) {
1142
- channels.connectError.publish({
1143
- connectParams: {
1144
- host,
1145
- hostname,
1146
- protocol,
1147
- port,
1148
- servername: client[kServerName]
1149
- },
1150
- connector: client[kConnector],
1151
- error: err
1152
- })
1132
+ try {
1133
+ if (!llhttpInstance) {
1134
+ if (!llhttpPromise) {
1135
+ llhttpPromise = lazyllhttp()
1153
1136
  }
1137
+ await llhttpPromise
1138
+ assert(llhttpInstance)
1139
+ llhttpPromise = null
1140
+ }
1154
1141
 
1155
- if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
1156
- assert(client[kRunning] === 0)
1157
- while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) {
1158
- const request = client[kQueue][client[kPendingIdx]++]
1159
- errorRequest(client, request, err)
1142
+ const socket = await new Promise((resolve, reject) => {
1143
+ client[kConnector]({
1144
+ host,
1145
+ hostname,
1146
+ protocol,
1147
+ port,
1148
+ servername: client[kServerName]
1149
+ }, (err, socket) => {
1150
+ if (err) {
1151
+ reject(err)
1152
+ } else {
1153
+ resolve(socket)
1160
1154
  }
1161
- } else {
1162
- onError(client, err)
1163
- }
1155
+ })
1156
+ })
1164
1157
 
1165
- client.emit('connectionError', client[kUrl], [client], err)
1166
- } else {
1167
- assert(socket)
1158
+ client[kConnecting] = false
1168
1159
 
1169
- client[kSocket] = socket
1160
+ assert(socket)
1170
1161
 
1171
- socket[kNoRef] = false
1172
- socket[kWriting] = false
1173
- socket[kReset] = false
1174
- socket[kBlocking] = false
1175
- socket[kError] = null
1176
- socket[kParser] = new Parser(client, socket)
1177
- socket[kClient] = client
1178
- socket
1179
- .on('error', onSocketError)
1180
- .on('readable', onSocketReadable)
1181
- .on('end', onSocketEnd)
1182
- .on('close', onSocketClose)
1183
-
1184
- if (channels.connected.hasSubscribers) {
1185
- channels.connected.publish({
1186
- connectParams: {
1187
- host,
1188
- hostname,
1189
- protocol,
1190
- port,
1191
- servername: client[kServerName]
1192
- },
1193
- connector: client[kConnector],
1194
- socket
1195
- })
1196
- }
1162
+ client[kSocket] = socket
1163
+
1164
+ socket[kNoRef] = false
1165
+ socket[kWriting] = false
1166
+ socket[kReset] = false
1167
+ socket[kBlocking] = false
1168
+ socket[kError] = null
1169
+ socket[kParser] = new Parser(client, socket, llhttpInstance)
1170
+ socket[kClient] = client
1171
+ socket
1172
+ .on('error', onSocketError)
1173
+ .on('readable', onSocketReadable)
1174
+ .on('end', onSocketEnd)
1175
+ .on('close', onSocketClose)
1176
+
1177
+ if (channels.connected.hasSubscribers) {
1178
+ channels.connected.publish({
1179
+ connectParams: {
1180
+ host,
1181
+ hostname,
1182
+ protocol,
1183
+ port,
1184
+ servername: client[kServerName]
1185
+ },
1186
+ connector: client[kConnector],
1187
+ socket
1188
+ })
1189
+ }
1197
1190
 
1198
- client.emit('connect', client[kUrl], [client])
1191
+ client.emit('connect', client[kUrl], [client])
1192
+ } catch (err) {
1193
+ client[kConnecting] = false
1194
+
1195
+ if (channels.connectError.hasSubscribers) {
1196
+ channels.connectError.publish({
1197
+ connectParams: {
1198
+ host,
1199
+ hostname,
1200
+ protocol,
1201
+ port,
1202
+ servername: client[kServerName]
1203
+ },
1204
+ connector: client[kConnector],
1205
+ error: err
1206
+ })
1199
1207
  }
1200
1208
 
1201
- resume(client)
1202
- })
1209
+ if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
1210
+ assert(client[kRunning] === 0)
1211
+ while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) {
1212
+ const request = client[kQueue][client[kPendingIdx]++]
1213
+ errorRequest(client, request, err)
1214
+ }
1215
+ } else {
1216
+ onError(client, err)
1217
+ }
1218
+
1219
+ client.emit('connectionError', client[kUrl], [client], err)
1220
+ }
1221
+
1222
+ resume(client)
1203
1223
  }
1204
1224
 
1205
1225
  function emitDrain (client) {
@@ -167,6 +167,16 @@ class NotSupportedError extends UndiciError {
167
167
  }
168
168
  }
169
169
 
170
+ class BalancedPoolMissingUpstreamError extends UndiciError {
171
+ constructor (message) {
172
+ super(message)
173
+ Error.captureStackTrace(this, NotSupportedError)
174
+ this.name = 'MissingUpstreamError'
175
+ this.message = message || 'No upstream has been added to the BalancedPool'
176
+ this.code = 'UND_ERR_BPL_MISSING_UPSTREAM'
177
+ }
178
+ }
179
+
170
180
  class HTTPParserError extends Error {
171
181
  constructor (message, code, data) {
172
182
  super(message)
@@ -195,5 +205,6 @@ module.exports = {
195
205
  InformationalError,
196
206
  SocketError,
197
207
  NotSupportedError,
198
- ResponseContentLengthMismatchError
208
+ ResponseContentLengthMismatchError,
209
+ BalancedPoolMissingUpstreamError
199
210
  }
@@ -40,5 +40,6 @@ module.exports = {
40
40
  kHostHeader: Symbol('host header'),
41
41
  kConnector: Symbol('connector'),
42
42
  kStrictContentLength: Symbol('strict content length'),
43
- kMaxRedirections: Symbol('maxRedirections')
43
+ kMaxRedirections: Symbol('maxRedirections'),
44
+ kProxy: Symbol('proxy agent options')
44
45
  }
@@ -0,0 +1,59 @@
1
+ 'use strict'
2
+
3
+ const { kClients, kProxy } = require('./core/symbols')
4
+ const url = require('url')
5
+ const Agent = require('./agent')
6
+ const Dispatcher = require('./dispatcher')
7
+ const { InvalidArgumentError } = require('./core/errors')
8
+
9
+ const kAgent = Symbol('proxy agent')
10
+
11
+ class ProxyAgent extends Dispatcher {
12
+ constructor (opts) {
13
+ super(opts)
14
+ this[kProxy] = buildProxyOptions(opts)
15
+
16
+ const agent = new Agent(opts)
17
+ this[kAgent] = agent
18
+
19
+ this[kClients] = agent[kClients]
20
+ }
21
+
22
+ dispatch (opts, handler) {
23
+ const { host } = url.parse(opts.origin)
24
+ return this[kAgent].dispatch(
25
+ {
26
+ ...opts,
27
+ origin: this[kProxy].uri,
28
+ path: opts.origin + opts.path,
29
+ headers: {
30
+ ...opts.headers,
31
+ host
32
+ },
33
+ },
34
+ handler
35
+ )
36
+ }
37
+
38
+ async close () {
39
+ await this[kAgent].close()
40
+ this[kClients].clear()
41
+ }
42
+ }
43
+
44
+ function buildProxyOptions(opts) {
45
+ if (typeof opts === 'string') {
46
+ opts = { uri: opts }
47
+ }
48
+
49
+ if (!opts || !opts.uri) {
50
+ throw new InvalidArgumentError('Proxy opts.uri is mandatory')
51
+ }
52
+
53
+ return {
54
+ uri: opts.uri,
55
+ protocol: opts.protocol || 'https'
56
+ }
57
+ }
58
+
59
+ module.exports = ProxyAgent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "4.7.2",
3
+ "version": "4.8.2",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -0,0 +1,19 @@
1
+ import Client = require('./client')
2
+ import Pool = require('./pool')
3
+ import Dispatcher = require('./dispatcher')
4
+ import { URL } from 'url'
5
+
6
+ export = BalancedPool
7
+
8
+ declare class BalancedPool extends Dispatcher {
9
+ constructor(url: string | URL | string[], options?: Pool.Options);
10
+
11
+ addUpstream(upstream: string): BalancedPool;
12
+ removeUpstream(upstream: string): BalancedPool;
13
+ upstreams: Array<string>;
14
+
15
+ /** `true` after `pool.close()` has been called. */
16
+ closed: boolean;
17
+ /** `true` after `pool.destroyed()` has been called or `pool.close()` has been called and the pool shutdown has completed. */
18
+ destroyed: boolean;
19
+ }
@@ -0,0 +1,17 @@
1
+ import Agent = require('./agent')
2
+ import Dispatcher = require('./dispatcher')
3
+
4
+ export = ProxyAgent
5
+
6
+ declare class ProxyAgent extends Dispatcher {
7
+ constructor(options: ProxyAgent.Options | string)
8
+
9
+ dispatch(options: Agent.DispatchOptions, handler: Dispatcher.DispatchHandlers): void;
10
+ close(): Promise<void>;
11
+ }
12
+
13
+ declare namespace ProxyAgent {
14
+ export interface Options extends Agent.Options {
15
+ uri: string;
16
+ }
17
+ }