undici 4.8.0 → 4.9.1
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/ProxyAgent.md +100 -0
- package/docs/best-practices/proxy.md +4 -1
- package/index.d.ts +2 -1
- package/index.js +6 -1
- package/lib/client.js +163 -129
- package/lib/core/symbols.js +4 -1
- package/lib/fetch/index.js +1 -1
- package/lib/proxy-agent.js +59 -0
- package/package.json +1 -1
- package/types/client.d.ts +2 -0
- package/types/proxy-agent.d.ts +17 -0
|
@@ -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
|
@@ -10,13 +10,14 @@ import MockClient = require('./types/mock-client')
|
|
|
10
10
|
import MockPool = require('./types/mock-pool')
|
|
11
11
|
import MockAgent = require('./types/mock-agent')
|
|
12
12
|
import mockErrors = require('./types/mock-errors')
|
|
13
|
+
import ProxyAgent from './types/proxy-agent'
|
|
13
14
|
import { request, pipeline, stream, connect, upgrade } from './types/api'
|
|
14
15
|
|
|
15
16
|
export * from './types/fetch'
|
|
16
17
|
export * from './types/file'
|
|
17
18
|
export * from './types/formdata'
|
|
18
19
|
|
|
19
|
-
export { Dispatcher, BalancedPool, 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 }
|
|
20
21
|
export default Undici
|
|
21
22
|
|
|
22
23
|
declare function Undici(url: string, opts: Pool.Options): Pool
|
package/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const MockClient = require('./lib/mock/mock-client')
|
|
|
14
14
|
const MockAgent = require('./lib/mock/mock-agent')
|
|
15
15
|
const MockPool = require('./lib/mock/mock-pool')
|
|
16
16
|
const mockErrors = require('./lib/mock/mock-errors')
|
|
17
|
+
const ProxyAgent = require('./lib/proxy-agent')
|
|
17
18
|
|
|
18
19
|
const nodeVersion = process.versions.node.split('.')
|
|
19
20
|
const nodeMajor = Number(nodeVersion[0])
|
|
@@ -26,6 +27,7 @@ module.exports.Client = Client
|
|
|
26
27
|
module.exports.Pool = Pool
|
|
27
28
|
module.exports.BalancedPool = BalancedPool
|
|
28
29
|
module.exports.Agent = Agent
|
|
30
|
+
module.exports.ProxyAgent = ProxyAgent
|
|
29
31
|
|
|
30
32
|
module.exports.buildConnector = buildConnector
|
|
31
33
|
module.exports.errors = errors
|
|
@@ -91,8 +93,11 @@ module.exports.setGlobalDispatcher = setGlobalDispatcher
|
|
|
91
93
|
module.exports.getGlobalDispatcher = getGlobalDispatcher
|
|
92
94
|
|
|
93
95
|
if (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >= 5)) {
|
|
94
|
-
|
|
96
|
+
let fetchImpl = null
|
|
95
97
|
module.exports.fetch = async function fetch (resource, init) {
|
|
98
|
+
if (!fetchImpl) {
|
|
99
|
+
fetchImpl = require('./lib/fetch')
|
|
100
|
+
}
|
|
96
101
|
const dispatcher = getGlobalDispatcher()
|
|
97
102
|
return fetchImpl.call(dispatcher, resource, init)
|
|
98
103
|
}
|
package/lib/client.js
CHANGED
|
@@ -62,7 +62,9 @@ const {
|
|
|
62
62
|
kBodyTimeout,
|
|
63
63
|
kStrictContentLength,
|
|
64
64
|
kConnector,
|
|
65
|
-
kMaxRedirections
|
|
65
|
+
kMaxRedirections,
|
|
66
|
+
kMaxRequests,
|
|
67
|
+
kCounter
|
|
66
68
|
} = require('./core/symbols')
|
|
67
69
|
|
|
68
70
|
const channels = {}
|
|
@@ -100,7 +102,8 @@ class Client extends Dispatcher {
|
|
|
100
102
|
strictContentLength,
|
|
101
103
|
maxCachedSessions,
|
|
102
104
|
maxRedirections,
|
|
103
|
-
connect
|
|
105
|
+
connect,
|
|
106
|
+
maxRequestsPerClient
|
|
104
107
|
} = {}) {
|
|
105
108
|
super()
|
|
106
109
|
|
|
@@ -164,6 +167,10 @@ class Client extends Dispatcher {
|
|
|
164
167
|
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
|
165
168
|
}
|
|
166
169
|
|
|
170
|
+
if (maxRequestsPerClient != null && (!Number.isInteger(maxRequestsPerClient) || maxRequestsPerClient < 0)) {
|
|
171
|
+
throw new InvalidArgumentError('maxRequestsPerClient must be a positive number')
|
|
172
|
+
}
|
|
173
|
+
|
|
167
174
|
if (typeof connect !== 'function') {
|
|
168
175
|
connect = buildConnector({
|
|
169
176
|
...tls,
|
|
@@ -194,6 +201,7 @@ class Client extends Dispatcher {
|
|
|
194
201
|
this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 30e3
|
|
195
202
|
this[kStrictContentLength] = strictContentLength == null ? true : strictContentLength
|
|
196
203
|
this[kMaxRedirections] = maxRedirections
|
|
204
|
+
this[kMaxRequests] = maxRequestsPerClient
|
|
197
205
|
|
|
198
206
|
// kQueue is built up of 3 sections separated by
|
|
199
207
|
// the kRunningIdx and kPendingIdx indices.
|
|
@@ -397,76 +405,77 @@ class Client extends Dispatcher {
|
|
|
397
405
|
}
|
|
398
406
|
}
|
|
399
407
|
|
|
400
|
-
const { resolve } = require('path')
|
|
401
|
-
const { readFileSync } = require('fs')
|
|
402
408
|
const constants = require('./llhttp/constants')
|
|
403
409
|
const EMPTY_BUF = Buffer.alloc(0)
|
|
404
410
|
|
|
411
|
+
let llhttpPromise
|
|
405
412
|
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 */
|
|
413
|
+
async function lazyllhttp () {
|
|
414
|
+
const { resolve } = require('path')
|
|
415
|
+
const { readFile } = require('fs').promises
|
|
413
416
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
417
|
+
let mod
|
|
418
|
+
try {
|
|
419
|
+
mod = await WebAssembly.compile(await readFile(resolve(__dirname, './llhttp/llhttp_simd.wasm')))
|
|
420
|
+
} catch (e) {
|
|
421
|
+
/* istanbul ignore next */
|
|
420
422
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
423
|
+
// We could check if the error was caused by the simd option not
|
|
424
|
+
// being enabled, but the occurring of this other error
|
|
425
|
+
// * https://github.com/emscripten-core/emscripten/issues/11495
|
|
426
|
+
// got me to remove that check to avoid breaking Node 12.
|
|
427
|
+
mod = await WebAssembly.compile(await readFile(resolve(__dirname, './llhttp/llhttp.wasm')))
|
|
428
|
+
}
|
|
424
429
|
|
|
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
|
-
}
|
|
430
|
+
llhttpInstance = new WebAssembly.Instance(mod, {
|
|
431
|
+
env: {
|
|
432
|
+
/* eslint-disable camelcase */
|
|
465
433
|
|
|
466
|
-
|
|
434
|
+
wasm_on_url: (p, at, len) => {
|
|
435
|
+
/* istanbul ignore next */
|
|
436
|
+
return 0
|
|
437
|
+
},
|
|
438
|
+
wasm_on_status: (p, at, len) => {
|
|
439
|
+
assert.strictEqual(currentParser.ptr, p)
|
|
440
|
+
const start = at - currentBufferPtr
|
|
441
|
+
const end = start + len
|
|
442
|
+
return currentParser.onStatus(currentBufferRef.slice(start, end)) || 0
|
|
443
|
+
},
|
|
444
|
+
wasm_on_message_begin: (p) => {
|
|
445
|
+
assert.strictEqual(currentParser.ptr, p)
|
|
446
|
+
return currentParser.onMessageBegin() || 0
|
|
447
|
+
},
|
|
448
|
+
wasm_on_header_field: (p, at, len) => {
|
|
449
|
+
assert.strictEqual(currentParser.ptr, p)
|
|
450
|
+
const start = at - currentBufferPtr
|
|
451
|
+
const end = start + len
|
|
452
|
+
return currentParser.onHeaderField(currentBufferRef.slice(start, end)) || 0
|
|
453
|
+
},
|
|
454
|
+
wasm_on_header_value: (p, at, len) => {
|
|
455
|
+
assert.strictEqual(currentParser.ptr, p)
|
|
456
|
+
const start = at - currentBufferPtr
|
|
457
|
+
const end = start + len
|
|
458
|
+
return currentParser.onHeaderValue(currentBufferRef.slice(start, end)) || 0
|
|
459
|
+
},
|
|
460
|
+
wasm_on_headers_complete: (p, statusCode, upgrade, shouldKeepAlive) => {
|
|
461
|
+
assert.strictEqual(currentParser.ptr, p)
|
|
462
|
+
return currentParser.onHeadersComplete(statusCode, Boolean(upgrade), Boolean(shouldKeepAlive)) || 0
|
|
463
|
+
},
|
|
464
|
+
wasm_on_body: (p, at, len) => {
|
|
465
|
+
assert.strictEqual(currentParser.ptr, p)
|
|
466
|
+
const start = at - currentBufferPtr
|
|
467
|
+
const end = start + len
|
|
468
|
+
return currentParser.onBody(currentBufferRef.slice(start, end)) || 0
|
|
469
|
+
},
|
|
470
|
+
wasm_on_message_complete: (p) => {
|
|
471
|
+
assert.strictEqual(currentParser.ptr, p)
|
|
472
|
+
return currentParser.onMessageComplete() || 0
|
|
467
473
|
}
|
|
468
|
-
|
|
469
|
-
|
|
474
|
+
|
|
475
|
+
/* eslint-enable camelcase */
|
|
476
|
+
}
|
|
477
|
+
})
|
|
478
|
+
|
|
470
479
|
return llhttpInstance
|
|
471
480
|
}
|
|
472
481
|
|
|
@@ -480,10 +489,10 @@ const TIMEOUT_BODY = 2
|
|
|
480
489
|
const TIMEOUT_IDLE = 3
|
|
481
490
|
|
|
482
491
|
class Parser {
|
|
483
|
-
constructor (client, socket) {
|
|
492
|
+
constructor (client, socket, { exports }) {
|
|
484
493
|
assert(Number.isFinite(client[kMaxHeadersSize]) && client[kMaxHeadersSize] > 0)
|
|
485
494
|
|
|
486
|
-
this.llhttp =
|
|
495
|
+
this.llhttp = exports
|
|
487
496
|
this.ptr = this.llhttp.llhttp_alloc(constants.TYPE.RESPONSE)
|
|
488
497
|
this.client = client
|
|
489
498
|
this.socket = socket
|
|
@@ -1096,7 +1105,7 @@ function onSocketClose () {
|
|
|
1096
1105
|
resume(client)
|
|
1097
1106
|
}
|
|
1098
1107
|
|
|
1099
|
-
function connect (client) {
|
|
1108
|
+
async function connect (client) {
|
|
1100
1109
|
assert(!client[kConnecting])
|
|
1101
1110
|
assert(!client[kSocket])
|
|
1102
1111
|
|
|
@@ -1128,78 +1137,98 @@ function connect (client) {
|
|
|
1128
1137
|
})
|
|
1129
1138
|
}
|
|
1130
1139
|
|
|
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
|
-
})
|
|
1140
|
+
try {
|
|
1141
|
+
if (!llhttpInstance) {
|
|
1142
|
+
if (!llhttpPromise) {
|
|
1143
|
+
llhttpPromise = lazyllhttp()
|
|
1153
1144
|
}
|
|
1145
|
+
await llhttpPromise
|
|
1146
|
+
assert(llhttpInstance)
|
|
1147
|
+
llhttpPromise = null
|
|
1148
|
+
}
|
|
1154
1149
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1150
|
+
const socket = await new Promise((resolve, reject) => {
|
|
1151
|
+
client[kConnector]({
|
|
1152
|
+
host,
|
|
1153
|
+
hostname,
|
|
1154
|
+
protocol,
|
|
1155
|
+
port,
|
|
1156
|
+
servername: client[kServerName]
|
|
1157
|
+
}, (err, socket) => {
|
|
1158
|
+
if (err) {
|
|
1159
|
+
reject(err)
|
|
1160
|
+
} else {
|
|
1161
|
+
resolve(socket)
|
|
1160
1162
|
}
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
}
|
|
1163
|
+
})
|
|
1164
|
+
})
|
|
1164
1165
|
|
|
1165
|
-
|
|
1166
|
-
} else {
|
|
1167
|
-
assert(socket)
|
|
1166
|
+
client[kConnecting] = false
|
|
1168
1167
|
|
|
1169
|
-
|
|
1168
|
+
assert(socket)
|
|
1170
1169
|
|
|
1171
|
-
|
|
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
|
-
}
|
|
1170
|
+
client[kSocket] = socket
|
|
1197
1171
|
|
|
1198
|
-
|
|
1172
|
+
socket[kNoRef] = false
|
|
1173
|
+
socket[kWriting] = false
|
|
1174
|
+
socket[kReset] = false
|
|
1175
|
+
socket[kBlocking] = false
|
|
1176
|
+
socket[kError] = null
|
|
1177
|
+
socket[kParser] = new Parser(client, socket, llhttpInstance)
|
|
1178
|
+
socket[kClient] = client
|
|
1179
|
+
socket[kCounter] = 0
|
|
1180
|
+
socket[kMaxRequests] = client[kMaxRequests]
|
|
1181
|
+
socket
|
|
1182
|
+
.on('error', onSocketError)
|
|
1183
|
+
.on('readable', onSocketReadable)
|
|
1184
|
+
.on('end', onSocketEnd)
|
|
1185
|
+
.on('close', onSocketClose)
|
|
1186
|
+
|
|
1187
|
+
if (channels.connected.hasSubscribers) {
|
|
1188
|
+
channels.connected.publish({
|
|
1189
|
+
connectParams: {
|
|
1190
|
+
host,
|
|
1191
|
+
hostname,
|
|
1192
|
+
protocol,
|
|
1193
|
+
port,
|
|
1194
|
+
servername: client[kServerName]
|
|
1195
|
+
},
|
|
1196
|
+
connector: client[kConnector],
|
|
1197
|
+
socket
|
|
1198
|
+
})
|
|
1199
1199
|
}
|
|
1200
|
+
client.emit('connect', client[kUrl], [client])
|
|
1201
|
+
} catch (err) {
|
|
1202
|
+
client[kConnecting] = false
|
|
1200
1203
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1204
|
+
if (channels.connectError.hasSubscribers) {
|
|
1205
|
+
channels.connectError.publish({
|
|
1206
|
+
connectParams: {
|
|
1207
|
+
host,
|
|
1208
|
+
hostname,
|
|
1209
|
+
protocol,
|
|
1210
|
+
port,
|
|
1211
|
+
servername: client[kServerName]
|
|
1212
|
+
},
|
|
1213
|
+
connector: client[kConnector],
|
|
1214
|
+
error: err
|
|
1215
|
+
})
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
|
|
1219
|
+
assert(client[kRunning] === 0)
|
|
1220
|
+
while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) {
|
|
1221
|
+
const request = client[kQueue][client[kPendingIdx]++]
|
|
1222
|
+
errorRequest(client, request, err)
|
|
1223
|
+
}
|
|
1224
|
+
} else {
|
|
1225
|
+
onError(client, err)
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
client.emit('connectionError', client[kUrl], [client], err)
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
resume(client)
|
|
1203
1232
|
}
|
|
1204
1233
|
|
|
1205
1234
|
function emitDrain (client) {
|
|
@@ -1213,6 +1242,7 @@ function resume (client, sync) {
|
|
|
1213
1242
|
}
|
|
1214
1243
|
|
|
1215
1244
|
client[kResuming] = 2
|
|
1245
|
+
|
|
1216
1246
|
_resume(client, sync)
|
|
1217
1247
|
client[kResuming] = 0
|
|
1218
1248
|
|
|
@@ -1445,6 +1475,10 @@ function write (client, request) {
|
|
|
1445
1475
|
socket[kReset] = true
|
|
1446
1476
|
}
|
|
1447
1477
|
|
|
1478
|
+
if (client[kMaxRequests] && socket[kCounter]++ >= client[kMaxRequests]) {
|
|
1479
|
+
socket[kReset] = true
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1448
1482
|
if (blocking) {
|
|
1449
1483
|
socket[kBlocking] = true
|
|
1450
1484
|
}
|
package/lib/core/symbols.js
CHANGED
|
@@ -40,5 +40,8 @@ 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
|
+
kMaxRequests: Symbol('maxRequestsPerClient'),
|
|
45
|
+
kProxy: Symbol('proxy agent options'),
|
|
46
|
+
kCounter: Symbol('socket request counter')
|
|
44
47
|
}
|
package/lib/fetch/index.js
CHANGED
|
@@ -171,7 +171,7 @@ async function fetch (...args) {
|
|
|
171
171
|
|
|
172
172
|
// 3. If response is a network error, then reject p with a TypeError
|
|
173
173
|
// and terminate these substeps.
|
|
174
|
-
if (response.
|
|
174
|
+
if (response.type === 'error') {
|
|
175
175
|
p.reject(
|
|
176
176
|
Object.assign(new TypeError('fetch failed'), { cause: response.error })
|
|
177
177
|
)
|
|
@@ -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
package/types/client.d.ts
CHANGED
|
@@ -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
|
+
}
|