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.
- package/docs/api/BalancedPool.md +98 -0
- package/docs/api/ProxyAgent.md +100 -0
- package/docs/best-practices/proxy.md +4 -1
- package/index.d.ts +4 -1
- package/index.js +8 -1
- package/lib/balanced-pool.js +119 -0
- package/lib/client.js +147 -127
- package/lib/core/errors.js +12 -1
- package/lib/core/symbols.js +2 -1
- package/lib/proxy-agent.js +59 -0
- package/package.json +1 -1
- package/types/balanced-pool.d.ts +19 -0
- package/types/proxy-agent.d.ts +17 -0
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
408
|
-
|
|
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
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
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
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
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
|
-
}
|
|
1162
|
-
|
|
1163
|
-
}
|
|
1155
|
+
})
|
|
1156
|
+
})
|
|
1164
1157
|
|
|
1165
|
-
|
|
1166
|
-
} else {
|
|
1167
|
-
assert(socket)
|
|
1158
|
+
client[kConnecting] = false
|
|
1168
1159
|
|
|
1169
|
-
|
|
1160
|
+
assert(socket)
|
|
1170
1161
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
package/lib/core/errors.js
CHANGED
|
@@ -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
|
}
|
package/lib/core/symbols.js
CHANGED
|
@@ -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
|
@@ -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
|
+
}
|