undici 7.14.0 → 7.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/docs/docs/api/Agent.md +1 -0
- package/docs/docs/api/Dispatcher.md +59 -0
- package/docs/docs/api/Errors.md +0 -1
- package/index-fetch.js +2 -2
- package/index.js +6 -9
- package/lib/api/api-request.js +22 -8
- package/lib/api/readable.js +7 -5
- package/lib/core/errors.js +217 -13
- package/lib/core/request.js +5 -1
- package/lib/core/util.js +45 -11
- package/lib/dispatcher/agent.js +44 -23
- package/lib/dispatcher/client-h1.js +20 -9
- package/lib/dispatcher/client-h2.js +13 -3
- package/lib/dispatcher/client.js +57 -57
- package/lib/dispatcher/dispatcher-base.js +12 -7
- package/lib/dispatcher/env-http-proxy-agent.js +12 -16
- package/lib/dispatcher/fixed-queue.js +15 -39
- package/lib/dispatcher/h2c-client.js +6 -6
- package/lib/dispatcher/pool-base.js +60 -43
- package/lib/dispatcher/pool.js +2 -2
- package/lib/dispatcher/proxy-agent.js +14 -9
- package/lib/global.js +19 -1
- package/lib/interceptor/cache.js +61 -0
- package/lib/interceptor/decompress.js +253 -0
- package/lib/llhttp/constants.d.ts +99 -1
- package/lib/llhttp/constants.js +34 -1
- package/lib/llhttp/llhttp-wasm.js +1 -1
- package/lib/llhttp/llhttp_simd-wasm.js +1 -1
- package/lib/llhttp/utils.d.ts +2 -2
- package/lib/llhttp/utils.js +3 -6
- package/lib/mock/mock-agent.js +4 -4
- package/lib/mock/mock-errors.js +10 -0
- package/lib/mock/mock-utils.js +12 -10
- package/lib/util/cache.js +6 -7
- package/lib/util/date.js +534 -140
- package/lib/web/cookies/index.js +1 -1
- package/lib/web/cookies/parse.js +2 -2
- package/lib/web/eventsource/eventsource-stream.js +2 -2
- package/lib/web/eventsource/eventsource.js +34 -29
- package/lib/web/eventsource/util.js +1 -9
- package/lib/web/fetch/body.js +20 -26
- package/lib/web/fetch/index.js +15 -16
- package/lib/web/fetch/response.js +2 -4
- package/lib/web/fetch/util.js +8 -230
- package/lib/web/subresource-integrity/Readme.md +9 -0
- package/lib/web/subresource-integrity/subresource-integrity.js +306 -0
- package/lib/web/webidl/index.js +203 -42
- package/lib/web/websocket/connection.js +4 -3
- package/lib/web/websocket/events.js +1 -1
- package/lib/web/websocket/stream/websocketerror.js +22 -1
- package/lib/web/websocket/stream/websocketstream.js +16 -7
- package/lib/web/websocket/websocket.js +32 -42
- package/package.json +9 -7
- package/types/agent.d.ts +1 -0
- package/types/diagnostics-channel.d.ts +0 -1
- package/types/errors.d.ts +5 -15
- package/types/interceptors.d.ts +5 -0
- package/types/snapshot-agent.d.ts +5 -3
- package/types/webidl.d.ts +82 -21
- package/lib/api/util.js +0 -95
- package/lib/llhttp/constants.js.map +0 -1
- package/lib/llhttp/utils.js.map +0 -1
package/lib/core/util.js
CHANGED
|
@@ -102,13 +102,24 @@ function isBlobLike (object) {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* @param {string} url The path to check for query strings or fragments.
|
|
107
|
+
* @returns {boolean} Returns true if the path contains a query string or fragment.
|
|
108
|
+
*/
|
|
109
|
+
function pathHasQueryOrFragment (url) {
|
|
110
|
+
return (
|
|
111
|
+
url.includes('?') ||
|
|
112
|
+
url.includes('#')
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
105
116
|
/**
|
|
106
117
|
* @param {string} url The URL to add the query params to
|
|
107
118
|
* @param {import('node:querystring').ParsedUrlQueryInput} queryParams The object to serialize into a URL query string
|
|
108
119
|
* @returns {string} The URL with the query params added
|
|
109
120
|
*/
|
|
110
121
|
function serializePathWithQuery (url, queryParams) {
|
|
111
|
-
if (
|
|
122
|
+
if (pathHasQueryOrFragment(url)) {
|
|
112
123
|
throw new Error('Query params cannot be passed when url already contains "?" or "#".')
|
|
113
124
|
}
|
|
114
125
|
|
|
@@ -598,12 +609,11 @@ function ReadableStreamFrom (iterable) {
|
|
|
598
609
|
let iterator
|
|
599
610
|
return new ReadableStream(
|
|
600
611
|
{
|
|
601
|
-
|
|
612
|
+
start () {
|
|
602
613
|
iterator = iterable[Symbol.asyncIterator]()
|
|
603
614
|
},
|
|
604
615
|
pull (controller) {
|
|
605
|
-
|
|
606
|
-
const { done, value } = await iterator.next()
|
|
616
|
+
return iterator.next().then(({ done, value }) => {
|
|
607
617
|
if (done) {
|
|
608
618
|
queueMicrotask(() => {
|
|
609
619
|
controller.close()
|
|
@@ -614,15 +624,13 @@ function ReadableStreamFrom (iterable) {
|
|
|
614
624
|
if (buf.byteLength) {
|
|
615
625
|
controller.enqueue(new Uint8Array(buf))
|
|
616
626
|
} else {
|
|
617
|
-
return
|
|
627
|
+
return this.pull(controller)
|
|
618
628
|
}
|
|
619
629
|
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
return pull()
|
|
630
|
+
})
|
|
623
631
|
},
|
|
624
|
-
|
|
625
|
-
|
|
632
|
+
cancel () {
|
|
633
|
+
return iterator.return()
|
|
626
634
|
},
|
|
627
635
|
type: 'bytes'
|
|
628
636
|
}
|
|
@@ -868,6 +876,30 @@ function onConnectTimeout (socket, opts) {
|
|
|
868
876
|
destroy(socket, new ConnectTimeoutError(message))
|
|
869
877
|
}
|
|
870
878
|
|
|
879
|
+
/**
|
|
880
|
+
* @param {string} urlString
|
|
881
|
+
* @returns {string}
|
|
882
|
+
*/
|
|
883
|
+
function getProtocolFromUrlString (urlString) {
|
|
884
|
+
if (
|
|
885
|
+
urlString[0] === 'h' &&
|
|
886
|
+
urlString[1] === 't' &&
|
|
887
|
+
urlString[2] === 't' &&
|
|
888
|
+
urlString[3] === 'p'
|
|
889
|
+
) {
|
|
890
|
+
switch (urlString[4]) {
|
|
891
|
+
case ':':
|
|
892
|
+
return 'http:'
|
|
893
|
+
case 's':
|
|
894
|
+
if (urlString[5] === ':') {
|
|
895
|
+
return 'https:'
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
// fallback if none of the usual suspects
|
|
900
|
+
return urlString.slice(0, urlString.indexOf(':') + 1)
|
|
901
|
+
}
|
|
902
|
+
|
|
871
903
|
const kEnumerableProperty = Object.create(null)
|
|
872
904
|
kEnumerableProperty.enumerable = true
|
|
873
905
|
|
|
@@ -924,6 +956,7 @@ module.exports = {
|
|
|
924
956
|
assertRequestHandler,
|
|
925
957
|
getSocketInfo,
|
|
926
958
|
isFormDataLike,
|
|
959
|
+
pathHasQueryOrFragment,
|
|
927
960
|
serializePathWithQuery,
|
|
928
961
|
addAbortListener,
|
|
929
962
|
isValidHTTPToken,
|
|
@@ -938,5 +971,6 @@ module.exports = {
|
|
|
938
971
|
nodeMinor,
|
|
939
972
|
safeHTTPMethods: Object.freeze(['GET', 'HEAD', 'OPTIONS', 'TRACE']),
|
|
940
973
|
wrapRequestBody,
|
|
941
|
-
setupConnectTimeout
|
|
974
|
+
setupConnectTimeout,
|
|
975
|
+
getProtocolFromUrlString
|
|
942
976
|
}
|
package/lib/dispatcher/agent.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { InvalidArgumentError } = require('../core/errors')
|
|
3
|
+
const { InvalidArgumentError, MaxOriginsReachedError } = require('../core/errors')
|
|
4
4
|
const { kClients, kRunning, kClose, kDestroy, kDispatch, kUrl } = require('../core/symbols')
|
|
5
5
|
const DispatcherBase = require('./dispatcher-base')
|
|
6
6
|
const Pool = require('./pool')
|
|
@@ -13,6 +13,7 @@ const kOnConnectionError = Symbol('onConnectionError')
|
|
|
13
13
|
const kOnDrain = Symbol('onDrain')
|
|
14
14
|
const kFactory = Symbol('factory')
|
|
15
15
|
const kOptions = Symbol('options')
|
|
16
|
+
const kOrigins = Symbol('origins')
|
|
16
17
|
|
|
17
18
|
function defaultFactory (origin, opts) {
|
|
18
19
|
return opts && opts.connections === 1
|
|
@@ -21,7 +22,7 @@ function defaultFactory (origin, opts) {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
class Agent extends DispatcherBase {
|
|
24
|
-
constructor ({ factory = defaultFactory, connect, ...options } = {}) {
|
|
25
|
+
constructor ({ factory = defaultFactory, maxOrigins = Infinity, connect, ...options } = {}) {
|
|
25
26
|
if (typeof factory !== 'function') {
|
|
26
27
|
throw new InvalidArgumentError('factory must be a function.')
|
|
27
28
|
}
|
|
@@ -30,42 +31,34 @@ class Agent extends DispatcherBase {
|
|
|
30
31
|
throw new InvalidArgumentError('connect must be a function or an object')
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
if (typeof maxOrigins !== 'number' || Number.isNaN(maxOrigins) || maxOrigins <= 0) {
|
|
35
|
+
throw new InvalidArgumentError('maxOrigins must be a number greater than 0')
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
super()
|
|
34
39
|
|
|
35
40
|
if (connect && typeof connect !== 'function') {
|
|
36
41
|
connect = { ...connect }
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
this[kOptions] = { ...util.deepClone(options), connect }
|
|
44
|
+
this[kOptions] = { ...util.deepClone(options), maxOrigins, connect }
|
|
40
45
|
this[kFactory] = factory
|
|
41
46
|
this[kClients] = new Map()
|
|
47
|
+
this[kOrigins] = new Set()
|
|
42
48
|
|
|
43
49
|
this[kOnDrain] = (origin, targets) => {
|
|
44
50
|
this.emit('drain', origin, [this, ...targets])
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
this[kOnConnect] = (origin, targets) => {
|
|
48
|
-
const result = this[kClients].get(origin)
|
|
49
|
-
if (result) {
|
|
50
|
-
result.count += 1
|
|
51
|
-
}
|
|
52
54
|
this.emit('connect', origin, [this, ...targets])
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
this[kOnDisconnect] = (origin, targets, err) => {
|
|
56
|
-
const result = this[kClients].get(origin)
|
|
57
|
-
if (result) {
|
|
58
|
-
result.count -= 1
|
|
59
|
-
if (result.count <= 0) {
|
|
60
|
-
this[kClients].delete(origin)
|
|
61
|
-
result.dispatcher.destroy()
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
58
|
this.emit('disconnect', origin, [this, ...targets], err)
|
|
65
59
|
}
|
|
66
60
|
|
|
67
61
|
this[kOnConnectionError] = (origin, targets, err) => {
|
|
68
|
-
// TODO: should this decrement result.count here?
|
|
69
62
|
this.emit('connectionError', origin, [this, ...targets], err)
|
|
70
63
|
}
|
|
71
64
|
}
|
|
@@ -86,39 +79,67 @@ class Agent extends DispatcherBase {
|
|
|
86
79
|
throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.')
|
|
87
80
|
}
|
|
88
81
|
|
|
82
|
+
if (this[kOrigins].size >= this[kOptions].maxOrigins && !this[kOrigins].has(key)) {
|
|
83
|
+
throw new MaxOriginsReachedError()
|
|
84
|
+
}
|
|
85
|
+
|
|
89
86
|
const result = this[kClients].get(key)
|
|
90
87
|
let dispatcher = result && result.dispatcher
|
|
91
88
|
if (!dispatcher) {
|
|
89
|
+
const closeClientIfUnused = (connected) => {
|
|
90
|
+
const result = this[kClients].get(key)
|
|
91
|
+
if (result) {
|
|
92
|
+
if (connected) result.count -= 1
|
|
93
|
+
if (result.count <= 0) {
|
|
94
|
+
this[kClients].delete(key)
|
|
95
|
+
result.dispatcher.close()
|
|
96
|
+
}
|
|
97
|
+
this[kOrigins].delete(key)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
92
100
|
dispatcher = this[kFactory](opts.origin, this[kOptions])
|
|
93
101
|
.on('drain', this[kOnDrain])
|
|
94
|
-
.on('connect',
|
|
95
|
-
|
|
96
|
-
|
|
102
|
+
.on('connect', (origin, targets) => {
|
|
103
|
+
const result = this[kClients].get(key)
|
|
104
|
+
if (result) {
|
|
105
|
+
result.count += 1
|
|
106
|
+
}
|
|
107
|
+
this[kOnConnect](origin, targets)
|
|
108
|
+
})
|
|
109
|
+
.on('disconnect', (origin, targets, err) => {
|
|
110
|
+
closeClientIfUnused(true)
|
|
111
|
+
this[kOnDisconnect](origin, targets, err)
|
|
112
|
+
})
|
|
113
|
+
.on('connectionError', (origin, targets, err) => {
|
|
114
|
+
closeClientIfUnused(false)
|
|
115
|
+
this[kOnConnectionError](origin, targets, err)
|
|
116
|
+
})
|
|
97
117
|
|
|
98
118
|
this[kClients].set(key, { count: 0, dispatcher })
|
|
119
|
+
this[kOrigins].add(key)
|
|
99
120
|
}
|
|
100
121
|
|
|
101
122
|
return dispatcher.dispatch(opts, handler)
|
|
102
123
|
}
|
|
103
124
|
|
|
104
|
-
|
|
125
|
+
[kClose] () {
|
|
105
126
|
const closePromises = []
|
|
106
127
|
for (const { dispatcher } of this[kClients].values()) {
|
|
107
128
|
closePromises.push(dispatcher.close())
|
|
108
129
|
}
|
|
109
130
|
this[kClients].clear()
|
|
110
131
|
|
|
111
|
-
|
|
132
|
+
return Promise.all(closePromises)
|
|
112
133
|
}
|
|
113
134
|
|
|
114
|
-
|
|
135
|
+
[kDestroy] (err) {
|
|
115
136
|
const destroyPromises = []
|
|
116
137
|
for (const { dispatcher } of this[kClients].values()) {
|
|
117
138
|
destroyPromises.push(dispatcher.destroy(err))
|
|
118
139
|
}
|
|
119
140
|
this[kClients].clear()
|
|
120
141
|
|
|
121
|
-
|
|
142
|
+
return Promise.all(destroyPromises)
|
|
122
143
|
}
|
|
123
144
|
|
|
124
145
|
get stats () {
|
|
@@ -64,11 +64,26 @@ function lazyllhttp () {
|
|
|
64
64
|
const llhttpWasmData = process.env.JEST_WORKER_ID ? require('../llhttp/llhttp-wasm.js') : undefined
|
|
65
65
|
|
|
66
66
|
let mod
|
|
67
|
-
try {
|
|
68
|
-
mod = new WebAssembly.Module(require('../llhttp/llhttp_simd-wasm.js'))
|
|
69
|
-
} catch (e) {
|
|
70
|
-
/* istanbul ignore next */
|
|
71
67
|
|
|
68
|
+
// We disable wasm SIMD on ppc64 as it seems to be broken on Power 9 architectures.
|
|
69
|
+
let useWasmSIMD = process.arch !== 'ppc64'
|
|
70
|
+
// The Env Variable UNDICI_NO_WASM_SIMD allows explicitly overriding the default behavior
|
|
71
|
+
if (process.env.UNDICI_NO_WASM_SIMD === '1') {
|
|
72
|
+
useWasmSIMD = true
|
|
73
|
+
} else if (process.env.UNDICI_NO_WASM_SIMD === '0') {
|
|
74
|
+
useWasmSIMD = false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (useWasmSIMD) {
|
|
78
|
+
try {
|
|
79
|
+
mod = new WebAssembly.Module(require('../llhttp/llhttp_simd-wasm.js'))
|
|
80
|
+
/* istanbul ignore next */
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* istanbul ignore next */
|
|
86
|
+
if (!mod) {
|
|
72
87
|
// We could check if the error was caused by the simd option not
|
|
73
88
|
// being enabled, but the occurring of this other error
|
|
74
89
|
// * https://github.com/emscripten-core/emscripten/issues/11495
|
|
@@ -325,10 +340,6 @@ class Parser {
|
|
|
325
340
|
currentBufferRef = chunk
|
|
326
341
|
currentParser = this
|
|
327
342
|
ret = llhttp.llhttp_execute(this.ptr, currentBufferPtr, chunk.length)
|
|
328
|
-
/* eslint-disable-next-line no-useless-catch */
|
|
329
|
-
} catch (err) {
|
|
330
|
-
/* istanbul ignore next: difficult to make a test case for */
|
|
331
|
-
throw err
|
|
332
343
|
} finally {
|
|
333
344
|
currentParser = null
|
|
334
345
|
currentBufferRef = null
|
|
@@ -760,7 +771,7 @@ function onParserTimeout (parser) {
|
|
|
760
771
|
* @param {import('net').Socket} socket
|
|
761
772
|
* @returns
|
|
762
773
|
*/
|
|
763
|
-
|
|
774
|
+
function connectH1 (client, socket) {
|
|
764
775
|
client[kSocket] = socket
|
|
765
776
|
|
|
766
777
|
if (!llhttpInstance) {
|
|
@@ -77,7 +77,7 @@ function parseH2Headers (headers) {
|
|
|
77
77
|
return result
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
function connectH2 (client, socket) {
|
|
81
81
|
client[kSocket] = socket
|
|
82
82
|
|
|
83
83
|
const session = http2.connect(client[kUrl], {
|
|
@@ -279,7 +279,7 @@ function shouldSendContentLength (method) {
|
|
|
279
279
|
function writeH2 (client, request) {
|
|
280
280
|
const requestTimeout = request.bodyTimeout ?? client[kBodyTimeout]
|
|
281
281
|
const session = client[kHTTP2Session]
|
|
282
|
-
const { method, path, host, upgrade, expectContinue, signal, headers: reqHeaders } = request
|
|
282
|
+
const { method, path, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request
|
|
283
283
|
let { body } = request
|
|
284
284
|
|
|
285
285
|
if (upgrade) {
|
|
@@ -292,6 +292,16 @@ function writeH2 (client, request) {
|
|
|
292
292
|
const key = reqHeaders[n + 0]
|
|
293
293
|
const val = reqHeaders[n + 1]
|
|
294
294
|
|
|
295
|
+
if (key === 'cookie') {
|
|
296
|
+
if (headers[key] != null) {
|
|
297
|
+
headers[key] = Array.isArray(headers[key]) ? (headers[key].push(val), headers[key]) : [headers[key], val]
|
|
298
|
+
} else {
|
|
299
|
+
headers[key] = val
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
continue
|
|
303
|
+
}
|
|
304
|
+
|
|
295
305
|
if (Array.isArray(val)) {
|
|
296
306
|
for (let i = 0; i < val.length; i++) {
|
|
297
307
|
if (headers[key]) {
|
|
@@ -387,7 +397,7 @@ function writeH2 (client, request) {
|
|
|
387
397
|
// :path and :scheme headers must be omitted when sending CONNECT
|
|
388
398
|
|
|
389
399
|
headers[HTTP2_HEADER_PATH] = path
|
|
390
|
-
headers[HTTP2_HEADER_SCHEME] = 'https'
|
|
400
|
+
headers[HTTP2_HEADER_SCHEME] = protocol === 'http:' ? 'http' : 'https'
|
|
391
401
|
|
|
392
402
|
// https://tools.ietf.org/html/rfc7231#section-4.3.1
|
|
393
403
|
// https://tools.ietf.org/html/rfc7231#section-4.3.2
|
package/lib/dispatcher/client.js
CHANGED
|
@@ -296,8 +296,7 @@ class Client extends DispatcherBase {
|
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
[kDispatch] (opts, handler) {
|
|
299
|
-
const
|
|
300
|
-
const request = new Request(origin, opts, handler)
|
|
299
|
+
const request = new Request(this[kUrl].origin, opts, handler)
|
|
301
300
|
|
|
302
301
|
this[kQueue].push(request)
|
|
303
302
|
if (this[kResuming]) {
|
|
@@ -317,7 +316,7 @@ class Client extends DispatcherBase {
|
|
|
317
316
|
return this[kNeedDrain] < 2
|
|
318
317
|
}
|
|
319
318
|
|
|
320
|
-
|
|
319
|
+
[kClose] () {
|
|
321
320
|
// TODO: for H2 we need to gracefully flush the remaining enqueued
|
|
322
321
|
// request and close each stream.
|
|
323
322
|
return new Promise((resolve) => {
|
|
@@ -329,7 +328,7 @@ class Client extends DispatcherBase {
|
|
|
329
328
|
})
|
|
330
329
|
}
|
|
331
330
|
|
|
332
|
-
|
|
331
|
+
[kDestroy] (err) {
|
|
333
332
|
return new Promise((resolve) => {
|
|
334
333
|
const requests = this[kQueue].splice(this[kPendingIdx])
|
|
335
334
|
for (let i = 0; i < requests.length; i++) {
|
|
@@ -381,9 +380,9 @@ function onError (client, err) {
|
|
|
381
380
|
|
|
382
381
|
/**
|
|
383
382
|
* @param {Client} client
|
|
384
|
-
* @returns
|
|
383
|
+
* @returns {void}
|
|
385
384
|
*/
|
|
386
|
-
|
|
385
|
+
function connect (client) {
|
|
387
386
|
assert(!client[kConnecting])
|
|
388
387
|
assert(!client[kHTTPContext])
|
|
389
388
|
|
|
@@ -417,26 +416,23 @@ async function connect (client) {
|
|
|
417
416
|
})
|
|
418
417
|
}
|
|
419
418
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
resolve(socket)
|
|
434
|
-
}
|
|
435
|
-
})
|
|
436
|
-
})
|
|
419
|
+
client[kConnector]({
|
|
420
|
+
host,
|
|
421
|
+
hostname,
|
|
422
|
+
protocol,
|
|
423
|
+
port,
|
|
424
|
+
servername: client[kServerName],
|
|
425
|
+
localAddress: client[kLocalAddress]
|
|
426
|
+
}, (err, socket) => {
|
|
427
|
+
if (err) {
|
|
428
|
+
handleConnectError(client, err, { host, hostname, protocol, port })
|
|
429
|
+
client[kResume]()
|
|
430
|
+
return
|
|
431
|
+
}
|
|
437
432
|
|
|
438
433
|
if (client.destroyed) {
|
|
439
434
|
util.destroy(socket.on('error', noop), new ClientDestroyedError())
|
|
435
|
+
client[kResume]()
|
|
440
436
|
return
|
|
441
437
|
}
|
|
442
438
|
|
|
@@ -444,11 +440,13 @@ async function connect (client) {
|
|
|
444
440
|
|
|
445
441
|
try {
|
|
446
442
|
client[kHTTPContext] = socket.alpnProtocol === 'h2'
|
|
447
|
-
?
|
|
448
|
-
:
|
|
443
|
+
? connectH2(client, socket)
|
|
444
|
+
: connectH1(client, socket)
|
|
449
445
|
} catch (err) {
|
|
450
446
|
socket.destroy().on('error', noop)
|
|
451
|
-
|
|
447
|
+
handleConnectError(client, err, { host, hostname, protocol, port })
|
|
448
|
+
client[kResume]()
|
|
449
|
+
return
|
|
452
450
|
}
|
|
453
451
|
|
|
454
452
|
client[kConnecting] = false
|
|
@@ -473,44 +471,46 @@ async function connect (client) {
|
|
|
473
471
|
socket
|
|
474
472
|
})
|
|
475
473
|
}
|
|
474
|
+
|
|
476
475
|
client.emit('connect', client[kUrl], [client])
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
476
|
+
client[kResume]()
|
|
477
|
+
})
|
|
478
|
+
}
|
|
481
479
|
|
|
482
|
-
|
|
480
|
+
function handleConnectError (client, err, { host, hostname, protocol, port }) {
|
|
481
|
+
if (client.destroyed) {
|
|
482
|
+
return
|
|
483
|
+
}
|
|
483
484
|
|
|
484
|
-
|
|
485
|
-
channels.connectError.publish({
|
|
486
|
-
connectParams: {
|
|
487
|
-
host,
|
|
488
|
-
hostname,
|
|
489
|
-
protocol,
|
|
490
|
-
port,
|
|
491
|
-
version: client[kHTTPContext]?.version,
|
|
492
|
-
servername: client[kServerName],
|
|
493
|
-
localAddress: client[kLocalAddress]
|
|
494
|
-
},
|
|
495
|
-
connector: client[kConnector],
|
|
496
|
-
error: err
|
|
497
|
-
})
|
|
498
|
-
}
|
|
485
|
+
client[kConnecting] = false
|
|
499
486
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
487
|
+
if (channels.connectError.hasSubscribers) {
|
|
488
|
+
channels.connectError.publish({
|
|
489
|
+
connectParams: {
|
|
490
|
+
host,
|
|
491
|
+
hostname,
|
|
492
|
+
protocol,
|
|
493
|
+
port,
|
|
494
|
+
version: client[kHTTPContext]?.version,
|
|
495
|
+
servername: client[kServerName],
|
|
496
|
+
localAddress: client[kLocalAddress]
|
|
497
|
+
},
|
|
498
|
+
connector: client[kConnector],
|
|
499
|
+
error: err
|
|
500
|
+
})
|
|
501
|
+
}
|
|
509
502
|
|
|
510
|
-
|
|
503
|
+
if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
|
|
504
|
+
assert(client[kRunning] === 0)
|
|
505
|
+
while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) {
|
|
506
|
+
const request = client[kQueue][client[kPendingIdx]++]
|
|
507
|
+
util.errorRequest(client, request, err)
|
|
508
|
+
}
|
|
509
|
+
} else {
|
|
510
|
+
onError(client, err)
|
|
511
511
|
}
|
|
512
512
|
|
|
513
|
-
client[
|
|
513
|
+
client.emit('connectionError', client[kUrl], [client], err)
|
|
514
514
|
}
|
|
515
515
|
|
|
516
516
|
function emitDrain (client) {
|
|
@@ -13,19 +13,24 @@ const kOnDestroyed = Symbol('onDestroyed')
|
|
|
13
13
|
const kOnClosed = Symbol('onClosed')
|
|
14
14
|
|
|
15
15
|
class DispatcherBase extends Dispatcher {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/** @type {boolean} */
|
|
17
|
+
[kDestroyed] = false;
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
/** @type {Array|null} */
|
|
20
|
+
[kOnDestroyed] = null;
|
|
21
|
+
|
|
22
|
+
/** @type {boolean} */
|
|
23
|
+
[kClosed] = false;
|
|
24
|
+
|
|
25
|
+
/** @type {Array} */
|
|
26
|
+
[kOnClosed] = []
|
|
24
27
|
|
|
28
|
+
/** @returns {boolean} */
|
|
25
29
|
get destroyed () {
|
|
26
30
|
return this[kDestroyed]
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
/** @returns {boolean} */
|
|
29
34
|
get closed () {
|
|
30
35
|
return this[kClosed]
|
|
31
36
|
}
|
|
@@ -46,24 +46,20 @@ class EnvHttpProxyAgent extends DispatcherBase {
|
|
|
46
46
|
return agent.dispatch(opts, handler)
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
await this[kHttpsProxyAgent].close()
|
|
56
|
-
}
|
|
49
|
+
[kClose] () {
|
|
50
|
+
return Promise.all([
|
|
51
|
+
this[kNoProxyAgent].close(),
|
|
52
|
+
!this[kHttpProxyAgent][kClosed] && this[kHttpProxyAgent].close(),
|
|
53
|
+
!this[kHttpsProxyAgent][kClosed] && this[kHttpsProxyAgent].close()
|
|
54
|
+
])
|
|
57
55
|
}
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
await this[kHttpsProxyAgent].destroy(err)
|
|
66
|
-
}
|
|
57
|
+
[kDestroy] (err) {
|
|
58
|
+
return Promise.all([
|
|
59
|
+
this[kNoProxyAgent].destroy(err),
|
|
60
|
+
!this[kHttpProxyAgent][kDestroyed] && this[kHttpProxyAgent].destroy(err),
|
|
61
|
+
!this[kHttpsProxyAgent][kDestroyed] && this[kHttpsProxyAgent].destroy(err)
|
|
62
|
+
])
|
|
67
63
|
}
|
|
68
64
|
|
|
69
65
|
#getProxyAgentForUrl (url) {
|
|
@@ -59,35 +59,21 @@ const kMask = kSize - 1
|
|
|
59
59
|
* @template T
|
|
60
60
|
*/
|
|
61
61
|
class FixedCircularBuffer {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this.top = 0
|
|
71
|
-
/**
|
|
72
|
-
* @type {Array<T|undefined>}
|
|
73
|
-
*/
|
|
74
|
-
this.list = new Array(kSize).fill(undefined)
|
|
75
|
-
/**
|
|
76
|
-
* @type {T|null}
|
|
77
|
-
*/
|
|
78
|
-
this.next = null
|
|
79
|
-
}
|
|
62
|
+
/** @type {number} */
|
|
63
|
+
bottom = 0
|
|
64
|
+
/** @type {number} */
|
|
65
|
+
top = 0
|
|
66
|
+
/** @type {Array<T|undefined>} */
|
|
67
|
+
list = new Array(kSize).fill(undefined)
|
|
68
|
+
/** @type {T|null} */
|
|
69
|
+
next = null
|
|
80
70
|
|
|
81
|
-
/**
|
|
82
|
-
* @returns {boolean}
|
|
83
|
-
*/
|
|
71
|
+
/** @returns {boolean} */
|
|
84
72
|
isEmpty () {
|
|
85
73
|
return this.top === this.bottom
|
|
86
74
|
}
|
|
87
75
|
|
|
88
|
-
/**
|
|
89
|
-
* @returns {boolean}
|
|
90
|
-
*/
|
|
76
|
+
/** @returns {boolean} */
|
|
91
77
|
isFull () {
|
|
92
78
|
return ((this.top + 1) & kMask) === this.bottom
|
|
93
79
|
}
|
|
@@ -101,9 +87,7 @@ class FixedCircularBuffer {
|
|
|
101
87
|
this.top = (this.top + 1) & kMask
|
|
102
88
|
}
|
|
103
89
|
|
|
104
|
-
/**
|
|
105
|
-
* @returns {T|null}
|
|
106
|
-
*/
|
|
90
|
+
/** @returns {T|null} */
|
|
107
91
|
shift () {
|
|
108
92
|
const nextItem = this.list[this.bottom]
|
|
109
93
|
if (nextItem === undefined) { return null }
|
|
@@ -118,22 +102,16 @@ class FixedCircularBuffer {
|
|
|
118
102
|
*/
|
|
119
103
|
module.exports = class FixedQueue {
|
|
120
104
|
constructor () {
|
|
121
|
-
/**
|
|
122
|
-
* @type {FixedCircularBuffer<T>}
|
|
123
|
-
*/
|
|
105
|
+
/** @type {FixedCircularBuffer<T>} */
|
|
124
106
|
this.head = this.tail = new FixedCircularBuffer()
|
|
125
107
|
}
|
|
126
108
|
|
|
127
|
-
/**
|
|
128
|
-
* @returns {boolean}
|
|
129
|
-
*/
|
|
109
|
+
/** @returns {boolean} */
|
|
130
110
|
isEmpty () {
|
|
131
111
|
return this.head.isEmpty()
|
|
132
112
|
}
|
|
133
113
|
|
|
134
|
-
/**
|
|
135
|
-
* @param {T} data
|
|
136
|
-
*/
|
|
114
|
+
/** @param {T} data */
|
|
137
115
|
push (data) {
|
|
138
116
|
if (this.head.isFull()) {
|
|
139
117
|
// Head is full: Creates a new queue, sets the old queue's `.next` to it,
|
|
@@ -143,9 +121,7 @@ module.exports = class FixedQueue {
|
|
|
143
121
|
this.head.push(data)
|
|
144
122
|
}
|
|
145
123
|
|
|
146
|
-
/**
|
|
147
|
-
* @returns {T|null}
|
|
148
|
-
*/
|
|
124
|
+
/** @returns {T|null} */
|
|
149
125
|
shift () {
|
|
150
126
|
const tail = this.tail
|
|
151
127
|
const next = tail.shift()
|