undici 4.8.1 → 4.9.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/ProxyAgent.md +100 -0
- package/docs/best-practices/proxy.md +4 -1
- package/index.d.ts +2 -1
- package/index.js +2 -0
- package/lib/client.js +163 -131
- 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
|
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,79 +405,82 @@ 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
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
let mod
|
|
409
|
-
try {
|
|
410
|
-
mod = new WebAssembly.Module(readFileSync(resolve(__dirname, './llhttp/llhttp_simd.wasm')))
|
|
411
|
-
} catch (e) {
|
|
412
|
-
/* istanbul ignore next */
|
|
411
|
+
async function lazyllhttp () {
|
|
412
|
+
const { resolve } = require('path')
|
|
413
|
+
const { readFile } = require('fs').promises
|
|
413
414
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
415
|
+
let mod
|
|
416
|
+
try {
|
|
417
|
+
mod = await WebAssembly.compile(await readFile(resolve(__dirname, './llhttp/llhttp_simd.wasm')))
|
|
418
|
+
} catch (e) {
|
|
419
|
+
/* istanbul ignore next */
|
|
420
420
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
421
|
+
// We could check if the error was caused by the simd option not
|
|
422
|
+
// being enabled, but the occurring of this other error
|
|
423
|
+
// * https://github.com/emscripten-core/emscripten/issues/11495
|
|
424
|
+
// got me to remove that check to avoid breaking Node 12.
|
|
425
|
+
mod = await WebAssembly.compile(await readFile(resolve(__dirname, './llhttp/llhttp.wasm')))
|
|
426
|
+
}
|
|
424
427
|
|
|
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
|
-
}
|
|
428
|
+
return await WebAssembly.instantiate(mod, {
|
|
429
|
+
env: {
|
|
430
|
+
/* eslint-disable camelcase */
|
|
465
431
|
|
|
466
|
-
|
|
432
|
+
wasm_on_url: (p, at, len) => {
|
|
433
|
+
/* istanbul ignore next */
|
|
434
|
+
return 0
|
|
435
|
+
},
|
|
436
|
+
wasm_on_status: (p, at, len) => {
|
|
437
|
+
assert.strictEqual(currentParser.ptr, p)
|
|
438
|
+
const start = at - currentBufferPtr
|
|
439
|
+
const end = start + len
|
|
440
|
+
return currentParser.onStatus(currentBufferRef.slice(start, end)) || 0
|
|
441
|
+
},
|
|
442
|
+
wasm_on_message_begin: (p) => {
|
|
443
|
+
assert.strictEqual(currentParser.ptr, p)
|
|
444
|
+
return currentParser.onMessageBegin() || 0
|
|
445
|
+
},
|
|
446
|
+
wasm_on_header_field: (p, at, len) => {
|
|
447
|
+
assert.strictEqual(currentParser.ptr, p)
|
|
448
|
+
const start = at - currentBufferPtr
|
|
449
|
+
const end = start + len
|
|
450
|
+
return currentParser.onHeaderField(currentBufferRef.slice(start, end)) || 0
|
|
451
|
+
},
|
|
452
|
+
wasm_on_header_value: (p, at, len) => {
|
|
453
|
+
assert.strictEqual(currentParser.ptr, p)
|
|
454
|
+
const start = at - currentBufferPtr
|
|
455
|
+
const end = start + len
|
|
456
|
+
return currentParser.onHeaderValue(currentBufferRef.slice(start, end)) || 0
|
|
457
|
+
},
|
|
458
|
+
wasm_on_headers_complete: (p, statusCode, upgrade, shouldKeepAlive) => {
|
|
459
|
+
assert.strictEqual(currentParser.ptr, p)
|
|
460
|
+
return currentParser.onHeadersComplete(statusCode, Boolean(upgrade), Boolean(shouldKeepAlive)) || 0
|
|
461
|
+
},
|
|
462
|
+
wasm_on_body: (p, at, len) => {
|
|
463
|
+
assert.strictEqual(currentParser.ptr, p)
|
|
464
|
+
const start = at - currentBufferPtr
|
|
465
|
+
const end = start + len
|
|
466
|
+
return currentParser.onBody(currentBufferRef.slice(start, end)) || 0
|
|
467
|
+
},
|
|
468
|
+
wasm_on_message_complete: (p) => {
|
|
469
|
+
assert.strictEqual(currentParser.ptr, p)
|
|
470
|
+
return currentParser.onMessageComplete() || 0
|
|
467
471
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
472
|
+
|
|
473
|
+
/* eslint-enable camelcase */
|
|
474
|
+
}
|
|
475
|
+
})
|
|
471
476
|
}
|
|
472
477
|
|
|
478
|
+
let llhttpInstance = null
|
|
479
|
+
let llhttpPromise = lazyllhttp()
|
|
480
|
+
.catch(() => {
|
|
481
|
+
// TODO: Emit warning?
|
|
482
|
+
})
|
|
483
|
+
|
|
473
484
|
let currentParser = null
|
|
474
485
|
let currentBufferRef = null
|
|
475
486
|
let currentBufferSize = 0
|
|
@@ -480,10 +491,10 @@ const TIMEOUT_BODY = 2
|
|
|
480
491
|
const TIMEOUT_IDLE = 3
|
|
481
492
|
|
|
482
493
|
class Parser {
|
|
483
|
-
constructor (client, socket) {
|
|
494
|
+
constructor (client, socket, { exports }) {
|
|
484
495
|
assert(Number.isFinite(client[kMaxHeadersSize]) && client[kMaxHeadersSize] > 0)
|
|
485
496
|
|
|
486
|
-
this.llhttp =
|
|
497
|
+
this.llhttp = exports
|
|
487
498
|
this.ptr = this.llhttp.llhttp_alloc(constants.TYPE.RESPONSE)
|
|
488
499
|
this.client = client
|
|
489
500
|
this.socket = socket
|
|
@@ -1096,7 +1107,7 @@ function onSocketClose () {
|
|
|
1096
1107
|
resume(client)
|
|
1097
1108
|
}
|
|
1098
1109
|
|
|
1099
|
-
function connect (client) {
|
|
1110
|
+
async function connect (client) {
|
|
1100
1111
|
assert(!client[kConnecting])
|
|
1101
1112
|
assert(!client[kSocket])
|
|
1102
1113
|
|
|
@@ -1128,78 +1139,94 @@ function connect (client) {
|
|
|
1128
1139
|
})
|
|
1129
1140
|
}
|
|
1130
1141
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1142
|
+
try {
|
|
1143
|
+
const socket = await new Promise((resolve, reject) => {
|
|
1144
|
+
client[kConnector]({
|
|
1145
|
+
host,
|
|
1146
|
+
hostname,
|
|
1147
|
+
protocol,
|
|
1148
|
+
port,
|
|
1149
|
+
servername: client[kServerName]
|
|
1150
|
+
}, (err, socket) => {
|
|
1151
|
+
if (err) {
|
|
1152
|
+
reject(err)
|
|
1153
|
+
} else {
|
|
1154
|
+
resolve(socket)
|
|
1155
|
+
}
|
|
1156
|
+
})
|
|
1157
|
+
})
|
|
1158
|
+
|
|
1159
|
+
if (!llhttpInstance) {
|
|
1160
|
+
llhttpInstance = await llhttpPromise
|
|
1161
|
+
llhttpPromise = null
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1138
1164
|
client[kConnecting] = false
|
|
1139
1165
|
|
|
1140
|
-
|
|
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
|
-
})
|
|
1153
|
-
}
|
|
1166
|
+
assert(socket)
|
|
1154
1167
|
|
|
1155
|
-
|
|
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)
|
|
1160
|
-
}
|
|
1161
|
-
} else {
|
|
1162
|
-
onError(client, err)
|
|
1163
|
-
}
|
|
1168
|
+
client[kSocket] = socket
|
|
1164
1169
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1170
|
+
socket[kNoRef] = false
|
|
1171
|
+
socket[kWriting] = false
|
|
1172
|
+
socket[kReset] = false
|
|
1173
|
+
socket[kBlocking] = false
|
|
1174
|
+
socket[kError] = null
|
|
1175
|
+
socket[kParser] = new Parser(client, socket, llhttpInstance)
|
|
1176
|
+
socket[kClient] = client
|
|
1177
|
+
socket[kCounter] = 0
|
|
1178
|
+
socket[kMaxRequests] = client[kMaxRequests]
|
|
1179
|
+
socket
|
|
1180
|
+
.on('error', onSocketError)
|
|
1181
|
+
.on('readable', onSocketReadable)
|
|
1182
|
+
.on('end', onSocketEnd)
|
|
1183
|
+
.on('close', onSocketClose)
|
|
1184
|
+
|
|
1185
|
+
if (channels.connected.hasSubscribers) {
|
|
1186
|
+
channels.connected.publish({
|
|
1187
|
+
connectParams: {
|
|
1188
|
+
host,
|
|
1189
|
+
hostname,
|
|
1190
|
+
protocol,
|
|
1191
|
+
port,
|
|
1192
|
+
servername: client[kServerName]
|
|
1193
|
+
},
|
|
1194
|
+
connector: client[kConnector],
|
|
1195
|
+
socket
|
|
1196
|
+
})
|
|
1197
|
+
}
|
|
1198
|
+
client.emit('connect', client[kUrl], [client])
|
|
1199
|
+
} catch (err) {
|
|
1200
|
+
client[kConnecting] = false
|
|
1168
1201
|
|
|
1169
|
-
|
|
1202
|
+
if (channels.connectError.hasSubscribers) {
|
|
1203
|
+
channels.connectError.publish({
|
|
1204
|
+
connectParams: {
|
|
1205
|
+
host,
|
|
1206
|
+
hostname,
|
|
1207
|
+
protocol,
|
|
1208
|
+
port,
|
|
1209
|
+
servername: client[kServerName]
|
|
1210
|
+
},
|
|
1211
|
+
connector: client[kConnector],
|
|
1212
|
+
error: err
|
|
1213
|
+
})
|
|
1214
|
+
}
|
|
1170
1215
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
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
|
-
})
|
|
1216
|
+
if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
|
|
1217
|
+
assert(client[kRunning] === 0)
|
|
1218
|
+
while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) {
|
|
1219
|
+
const request = client[kQueue][client[kPendingIdx]++]
|
|
1220
|
+
errorRequest(client, request, err)
|
|
1196
1221
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1222
|
+
} else {
|
|
1223
|
+
onError(client, err)
|
|
1199
1224
|
}
|
|
1200
1225
|
|
|
1201
|
-
|
|
1202
|
-
}
|
|
1226
|
+
client.emit('connectionError', client[kUrl], [client], err)
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
resume(client)
|
|
1203
1230
|
}
|
|
1204
1231
|
|
|
1205
1232
|
function emitDrain (client) {
|
|
@@ -1213,6 +1240,7 @@ function resume (client, sync) {
|
|
|
1213
1240
|
}
|
|
1214
1241
|
|
|
1215
1242
|
client[kResuming] = 2
|
|
1243
|
+
|
|
1216
1244
|
_resume(client, sync)
|
|
1217
1245
|
client[kResuming] = 0
|
|
1218
1246
|
|
|
@@ -1445,6 +1473,10 @@ function write (client, request) {
|
|
|
1445
1473
|
socket[kReset] = true
|
|
1446
1474
|
}
|
|
1447
1475
|
|
|
1476
|
+
if (client[kMaxRequests] && socket[kCounter]++ >= client[kMaxRequests]) {
|
|
1477
|
+
socket[kReset] = true
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1448
1480
|
if (blocking) {
|
|
1449
1481
|
socket[kBlocking] = true
|
|
1450
1482
|
}
|
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
|
+
}
|