undici 6.6.1 → 6.7.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 → docs/README.md} +19 -15
- package/docs/{api → docs/api}/Dispatcher.md +39 -3
- package/docs/docs/api/Fetch.md +57 -0
- package/docs/{api → docs/api}/ProxyAgent.md +3 -1
- package/docs/docs/api/RetryAgent.md +45 -0
- package/docs/{api → docs/api}/RetryHandler.md +1 -1
- package/docs/{api → docs/api}/api-lifecycle.md +33 -4
- package/docs/{best-practices → docs/best-practices}/proxy.md +6 -6
- package/index-fetch.js +11 -7
- package/index.js +31 -25
- package/lib/core/request.js +72 -135
- package/lib/core/symbols.js +6 -5
- package/lib/core/tree.js +46 -26
- package/lib/core/util.js +41 -20
- package/lib/{agent.js → dispatcher/agent.js} +4 -4
- package/lib/{balanced-pool.js → dispatcher/balanced-pool.js} +3 -3
- package/lib/dispatcher/client-h1.js +1339 -0
- package/lib/dispatcher/client-h2.js +639 -0
- package/lib/dispatcher/client.js +611 -0
- package/lib/{dispatcher-base.js → dispatcher/dispatcher-base.js} +2 -2
- package/lib/{pool-base.js → dispatcher/pool-base.js} +3 -3
- package/lib/{pool-stats.js → dispatcher/pool-stats.js} +1 -1
- package/lib/{pool.js → dispatcher/pool.js} +4 -4
- package/lib/{proxy-agent.js → dispatcher/proxy-agent.js} +29 -35
- package/lib/dispatcher/retry-agent.js +35 -0
- package/lib/global.js +1 -1
- package/lib/handler/{RetryHandler.js → retry-handler.js} +2 -2
- package/lib/interceptor/{redirectInterceptor.js → redirect-interceptor.js} +1 -1
- package/lib/mock/mock-agent.js +2 -2
- package/lib/mock/mock-client.js +1 -1
- package/lib/mock/mock-interceptor.js +2 -2
- package/lib/mock/mock-pool.js +1 -1
- package/lib/mock/mock-utils.js +6 -4
- package/lib/{cache → web/cache}/cache.js +2 -4
- package/lib/{cache → web/cache}/cachestorage.js +1 -1
- package/lib/web/cache/symbols.js +5 -0
- package/lib/{cache → web/cache}/util.js +5 -9
- package/lib/{cookies → web/cookies}/parse.js +1 -1
- package/lib/{cookies → web/cookies}/util.js +76 -60
- package/lib/{eventsource → web/eventsource}/eventsource.js +2 -6
- package/lib/{fetch → web/fetch}/body.js +23 -52
- package/lib/{fetch/dataURL.js → web/fetch/data-url.js} +2 -0
- package/lib/{compat → web/fetch}/dispatcher-weakref.js +1 -1
- package/lib/{fetch → web/fetch}/file.js +2 -2
- package/lib/{fetch → web/fetch}/formdata.js +6 -67
- package/lib/{fetch → web/fetch}/headers.js +99 -71
- package/lib/{fetch → web/fetch}/index.js +40 -31
- package/lib/{fetch → web/fetch}/request.js +14 -6
- package/lib/{fetch → web/fetch}/response.js +3 -3
- package/lib/{fetch → web/fetch}/symbols.js +2 -1
- package/lib/{fetch → web/fetch}/util.js +142 -48
- package/lib/{fetch → web/fetch}/webidl.js +53 -19
- package/lib/{fileapi → web/fileapi}/filereader.js +1 -1
- package/lib/{fileapi → web/fileapi}/util.js +1 -1
- package/lib/{websocket → web/websocket}/connection.js +20 -10
- package/lib/{websocket → web/websocket}/constants.js +7 -0
- package/lib/{websocket → web/websocket}/events.js +1 -1
- package/lib/{websocket → web/websocket}/frame.js +1 -0
- package/lib/{websocket → web/websocket}/receiver.js +9 -16
- package/lib/{websocket → web/websocket}/util.js +37 -23
- package/lib/{websocket → web/websocket}/websocket.js +21 -9
- package/package.json +27 -52
- package/types/dispatcher.d.ts +1 -1
- package/types/fetch.d.ts +20 -21
- package/types/index.d.ts +2 -1
- package/types/retry-agent.d.ts +11 -0
- package/types/webidl.d.ts +6 -1
- package/docs/api/Fetch.md +0 -27
- package/docs/assets/lifecycle-diagram.png +0 -0
- package/lib/cache/symbols.js +0 -5
- package/lib/client.js +0 -2295
- package/lib/llhttp/llhttp-wasm.js +0 -3
- package/lib/llhttp/llhttp.wasm +0 -0
- package/lib/llhttp/llhttp_simd.wasm +0 -0
- /package/docs/{api → docs/api}/Agent.md +0 -0
- /package/docs/{api → docs/api}/BalancedPool.md +0 -0
- /package/docs/{api → docs/api}/CacheStorage.md +0 -0
- /package/docs/{api → docs/api}/Client.md +0 -0
- /package/docs/{api → docs/api}/Connector.md +0 -0
- /package/docs/{api → docs/api}/ContentType.md +0 -0
- /package/docs/{api → docs/api}/Cookies.md +0 -0
- /package/docs/{api → docs/api}/Debug.md +0 -0
- /package/docs/{api → docs/api}/DiagnosticsChannel.md +0 -0
- /package/docs/{api → docs/api}/DispatchInterceptor.md +0 -0
- /package/docs/{api → docs/api}/Errors.md +0 -0
- /package/docs/{api → docs/api}/EventSource.md +0 -0
- /package/docs/{api → docs/api}/MockAgent.md +0 -0
- /package/docs/{api → docs/api}/MockClient.md +0 -0
- /package/docs/{api → docs/api}/MockErrors.md +0 -0
- /package/docs/{api → docs/api}/MockPool.md +0 -0
- /package/docs/{api → docs/api}/Pool.md +0 -0
- /package/docs/{api → docs/api}/PoolStats.md +0 -0
- /package/docs/{api → docs/api}/RedirectHandler.md +0 -0
- /package/docs/{api → docs/api}/Util.md +0 -0
- /package/docs/{api → docs/api}/WebSocket.md +0 -0
- /package/docs/{best-practices → docs/best-practices}/client-certificate.md +0 -0
- /package/docs/{best-practices → docs/best-practices}/mocking-request.md +0 -0
- /package/docs/{best-practices → docs/best-practices}/writing-tests.md +0 -0
- /package/lib/{dispatcher.js → dispatcher/dispatcher.js} +0 -0
- /package/lib/{node → dispatcher}/fixed-queue.js +0 -0
- /package/lib/handler/{DecoratorHandler.js → decorator-handler.js} +0 -0
- /package/lib/handler/{RedirectHandler.js → redirect-handler.js} +0 -0
- /package/lib/{timers.js → util/timers.js} +0 -0
- /package/lib/{cookies → web/cookies}/constants.js +0 -0
- /package/lib/{cookies → web/cookies}/index.js +0 -0
- /package/lib/{eventsource → web/eventsource}/eventsource-stream.js +0 -0
- /package/lib/{eventsource → web/eventsource}/util.js +0 -0
- /package/lib/{fetch → web/fetch}/LICENSE +0 -0
- /package/lib/{fetch → web/fetch}/constants.js +0 -0
- /package/lib/{fetch → web/fetch}/global.js +0 -0
- /package/lib/{fileapi → web/fileapi}/encoding.js +0 -0
- /package/lib/{fileapi → web/fileapi}/progressevent.js +0 -0
- /package/lib/{fileapi → web/fileapi}/symbols.js +0 -0
- /package/lib/{websocket → web/websocket}/symbols.js +0 -0
package/lib/core/request.js
CHANGED
|
@@ -5,29 +5,27 @@ const {
|
|
|
5
5
|
NotSupportedError
|
|
6
6
|
} = require('./errors')
|
|
7
7
|
const assert = require('node:assert')
|
|
8
|
-
const {
|
|
9
|
-
|
|
8
|
+
const {
|
|
9
|
+
isValidHTTPToken,
|
|
10
|
+
isValidHeaderChar,
|
|
11
|
+
isStream,
|
|
12
|
+
destroy,
|
|
13
|
+
isBuffer,
|
|
14
|
+
isFormDataLike,
|
|
15
|
+
isIterable,
|
|
16
|
+
isBlobLike,
|
|
17
|
+
buildURL,
|
|
18
|
+
validateHandler,
|
|
19
|
+
getServerName
|
|
20
|
+
} = require('./util')
|
|
10
21
|
const { channels } = require('./diagnostics.js')
|
|
11
22
|
const { headerNameLowerCasedRecord } = require('./constants')
|
|
12
23
|
|
|
13
|
-
// headerCharRegex have been lifted from
|
|
14
|
-
// https://github.com/nodejs/node/blob/main/lib/_http_common.js
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Matches if val contains an invalid field-vchar
|
|
18
|
-
* field-value = *( field-content / obs-fold )
|
|
19
|
-
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
|
|
20
|
-
* field-vchar = VCHAR / obs-text
|
|
21
|
-
*/
|
|
22
|
-
const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/
|
|
23
|
-
|
|
24
24
|
// Verifies that a given path is valid does not contain control chars \x00 to \x20
|
|
25
25
|
const invalidPathRegex = /[^\u0021-\u00ff]/
|
|
26
26
|
|
|
27
27
|
const kHandler = Symbol('handler')
|
|
28
28
|
|
|
29
|
-
let extractBody
|
|
30
|
-
|
|
31
29
|
class Request {
|
|
32
30
|
constructor (origin, {
|
|
33
31
|
path,
|
|
@@ -58,7 +56,7 @@ class Request {
|
|
|
58
56
|
|
|
59
57
|
if (typeof method !== 'string') {
|
|
60
58
|
throw new InvalidArgumentError('method must be a string')
|
|
61
|
-
} else if (!
|
|
59
|
+
} else if (!isValidHTTPToken(method)) {
|
|
62
60
|
throw new InvalidArgumentError('invalid request method')
|
|
63
61
|
}
|
|
64
62
|
|
|
@@ -94,13 +92,13 @@ class Request {
|
|
|
94
92
|
|
|
95
93
|
if (body == null) {
|
|
96
94
|
this.body = null
|
|
97
|
-
} else if (
|
|
95
|
+
} else if (isStream(body)) {
|
|
98
96
|
this.body = body
|
|
99
97
|
|
|
100
98
|
const rState = this.body._readableState
|
|
101
99
|
if (!rState || !rState.autoDestroy) {
|
|
102
100
|
this.endHandler = function autoDestroy () {
|
|
103
|
-
|
|
101
|
+
destroy(this)
|
|
104
102
|
}
|
|
105
103
|
this.body.on('end', this.endHandler)
|
|
106
104
|
}
|
|
@@ -113,7 +111,7 @@ class Request {
|
|
|
113
111
|
}
|
|
114
112
|
}
|
|
115
113
|
this.body.on('error', this.errorHandler)
|
|
116
|
-
} else if (
|
|
114
|
+
} else if (isBuffer(body)) {
|
|
117
115
|
this.body = body.byteLength ? body : null
|
|
118
116
|
} else if (ArrayBuffer.isView(body)) {
|
|
119
117
|
this.body = body.buffer.byteLength ? Buffer.from(body.buffer, body.byteOffset, body.byteLength) : null
|
|
@@ -121,7 +119,7 @@ class Request {
|
|
|
121
119
|
this.body = body.byteLength ? Buffer.from(body) : null
|
|
122
120
|
} else if (typeof body === 'string') {
|
|
123
121
|
this.body = body.length ? Buffer.from(body) : null
|
|
124
|
-
} else if (
|
|
122
|
+
} else if (isFormDataLike(body) || isIterable(body) || isBlobLike(body)) {
|
|
125
123
|
this.body = body
|
|
126
124
|
} else {
|
|
127
125
|
throw new InvalidArgumentError('body must be a string, a Buffer, a Readable stream, an iterable, or an async iterable')
|
|
@@ -133,7 +131,7 @@ class Request {
|
|
|
133
131
|
|
|
134
132
|
this.upgrade = upgrade || null
|
|
135
133
|
|
|
136
|
-
this.path = query ?
|
|
134
|
+
this.path = query ? buildURL(path, query) : path
|
|
137
135
|
|
|
138
136
|
this.origin = origin
|
|
139
137
|
|
|
@@ -151,7 +149,7 @@ class Request {
|
|
|
151
149
|
|
|
152
150
|
this.contentType = null
|
|
153
151
|
|
|
154
|
-
this.headers =
|
|
152
|
+
this.headers = []
|
|
155
153
|
|
|
156
154
|
// Only for H2
|
|
157
155
|
this.expectContinue = expectContinue != null ? expectContinue : false
|
|
@@ -164,35 +162,26 @@ class Request {
|
|
|
164
162
|
processHeader(this, headers[i], headers[i + 1])
|
|
165
163
|
}
|
|
166
164
|
} else if (headers && typeof headers === 'object') {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
165
|
+
if (headers[Symbol.iterator]) {
|
|
166
|
+
for (const header of headers) {
|
|
167
|
+
if (!Array.isArray(header) || header.length !== 2) {
|
|
168
|
+
throw new InvalidArgumentError('headers must be in key-value pair format')
|
|
169
|
+
}
|
|
170
|
+
processHeader(this, header[0], header[1])
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
const keys = Object.keys(headers)
|
|
174
|
+
for (let i = 0; i < keys.length; ++i) {
|
|
175
|
+
processHeader(this, keys[i], headers[keys[i]])
|
|
176
|
+
}
|
|
171
177
|
}
|
|
172
178
|
} else if (headers != null) {
|
|
173
179
|
throw new InvalidArgumentError('headers must be an object or an array')
|
|
174
180
|
}
|
|
175
181
|
|
|
176
|
-
|
|
177
|
-
if (!extractBody) {
|
|
178
|
-
extractBody = require('../fetch/body.js').extractBody
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const [bodyStream, contentType] = extractBody(body)
|
|
182
|
-
if (this.contentType == null) {
|
|
183
|
-
this.contentType = contentType
|
|
184
|
-
this.headers += `content-type: ${contentType}\r\n`
|
|
185
|
-
}
|
|
186
|
-
this.body = bodyStream.stream
|
|
187
|
-
this.contentLength = bodyStream.length
|
|
188
|
-
} else if (util.isBlobLike(body) && this.contentType == null && body.type) {
|
|
189
|
-
this.contentType = body.type
|
|
190
|
-
this.headers += `content-type: ${body.type}\r\n`
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
util.validateHandler(handler, method, upgrade)
|
|
182
|
+
validateHandler(handler, method, upgrade)
|
|
194
183
|
|
|
195
|
-
this.servername =
|
|
184
|
+
this.servername = getServerName(this.host)
|
|
196
185
|
|
|
197
186
|
this[kHandler] = handler
|
|
198
187
|
|
|
@@ -320,81 +309,13 @@ class Request {
|
|
|
320
309
|
}
|
|
321
310
|
}
|
|
322
311
|
|
|
323
|
-
// TODO: adjust to support H2
|
|
324
312
|
addHeader (key, value) {
|
|
325
313
|
processHeader(this, key, value)
|
|
326
314
|
return this
|
|
327
315
|
}
|
|
328
|
-
|
|
329
|
-
static [kHTTP1BuildRequest] (origin, opts, handler) {
|
|
330
|
-
// TODO: Migrate header parsing here, to make Requests
|
|
331
|
-
// HTTP agnostic
|
|
332
|
-
return new Request(origin, opts, handler)
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
static [kHTTP2BuildRequest] (origin, opts, handler) {
|
|
336
|
-
const headers = opts.headers
|
|
337
|
-
opts = { ...opts, headers: null }
|
|
338
|
-
|
|
339
|
-
const request = new Request(origin, opts, handler)
|
|
340
|
-
|
|
341
|
-
request.headers = {}
|
|
342
|
-
|
|
343
|
-
if (Array.isArray(headers)) {
|
|
344
|
-
if (headers.length % 2 !== 0) {
|
|
345
|
-
throw new InvalidArgumentError('headers array must be even')
|
|
346
|
-
}
|
|
347
|
-
for (let i = 0; i < headers.length; i += 2) {
|
|
348
|
-
processHeader(request, headers[i], headers[i + 1], true)
|
|
349
|
-
}
|
|
350
|
-
} else if (headers && typeof headers === 'object') {
|
|
351
|
-
const keys = Object.keys(headers)
|
|
352
|
-
for (let i = 0; i < keys.length; i++) {
|
|
353
|
-
const key = keys[i]
|
|
354
|
-
processHeader(request, key, headers[key], true)
|
|
355
|
-
}
|
|
356
|
-
} else if (headers != null) {
|
|
357
|
-
throw new InvalidArgumentError('headers must be an object or an array')
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
return request
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
static [kHTTP2CopyHeaders] (raw) {
|
|
364
|
-
const rawHeaders = raw.split('\r\n')
|
|
365
|
-
const headers = {}
|
|
366
|
-
|
|
367
|
-
for (const header of rawHeaders) {
|
|
368
|
-
const [key, value] = header.split(': ')
|
|
369
|
-
|
|
370
|
-
if (value == null || value.length === 0) continue
|
|
371
|
-
|
|
372
|
-
if (headers[key]) {
|
|
373
|
-
headers[key] += `,${value}`
|
|
374
|
-
} else {
|
|
375
|
-
headers[key] = value
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
return headers
|
|
380
|
-
}
|
|
381
316
|
}
|
|
382
317
|
|
|
383
|
-
function
|
|
384
|
-
if (val && typeof val === 'object') {
|
|
385
|
-
throw new InvalidArgumentError(`invalid ${key} header`)
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
val = val != null ? `${val}` : ''
|
|
389
|
-
|
|
390
|
-
if (headerCharRegex.exec(val) !== null) {
|
|
391
|
-
throw new InvalidArgumentError(`invalid ${key} header`)
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
return skipAppend ? val : `${key}: ${val}\r\n`
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function processHeader (request, key, val, skipAppend = false) {
|
|
318
|
+
function processHeader (request, key, val) {
|
|
398
319
|
if (val && (typeof val === 'object' && !Array.isArray(val))) {
|
|
399
320
|
throw new InvalidArgumentError(`invalid ${key} header`)
|
|
400
321
|
} else if (val === undefined) {
|
|
@@ -405,15 +326,44 @@ function processHeader (request, key, val, skipAppend = false) {
|
|
|
405
326
|
|
|
406
327
|
if (headerName === undefined) {
|
|
407
328
|
headerName = key.toLowerCase()
|
|
408
|
-
if (headerNameLowerCasedRecord[headerName] === undefined && !
|
|
329
|
+
if (headerNameLowerCasedRecord[headerName] === undefined && !isValidHTTPToken(headerName)) {
|
|
409
330
|
throw new InvalidArgumentError('invalid header key')
|
|
410
331
|
}
|
|
411
332
|
}
|
|
412
333
|
|
|
413
|
-
if (
|
|
414
|
-
|
|
334
|
+
if (Array.isArray(val)) {
|
|
335
|
+
const arr = []
|
|
336
|
+
for (let i = 0; i < val.length; i++) {
|
|
337
|
+
if (typeof val[i] === 'string') {
|
|
338
|
+
if (!isValidHeaderChar(val[i])) {
|
|
339
|
+
throw new InvalidArgumentError(`invalid ${key} header`)
|
|
340
|
+
}
|
|
341
|
+
arr.push(val[i])
|
|
342
|
+
} else if (val[i] === null) {
|
|
343
|
+
arr.push('')
|
|
344
|
+
} else if (typeof val[i] === 'object') {
|
|
345
|
+
throw new InvalidArgumentError(`invalid ${key} header`)
|
|
346
|
+
} else {
|
|
347
|
+
arr.push(`${val[i]}`)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
val = arr
|
|
351
|
+
} else if (typeof val === 'string') {
|
|
352
|
+
if (!isValidHeaderChar(val)) {
|
|
415
353
|
throw new InvalidArgumentError(`invalid ${key} header`)
|
|
416
354
|
}
|
|
355
|
+
} else if (val === null) {
|
|
356
|
+
val = ''
|
|
357
|
+
} else if (typeof val === 'object') {
|
|
358
|
+
throw new InvalidArgumentError(`invalid ${key} header`)
|
|
359
|
+
} else {
|
|
360
|
+
val = `${val}`
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (request.host === null && headerName === 'host') {
|
|
364
|
+
if (typeof val !== 'string') {
|
|
365
|
+
throw new InvalidArgumentError('invalid host header')
|
|
366
|
+
}
|
|
417
367
|
// Consumed by Client
|
|
418
368
|
request.host = val
|
|
419
369
|
} else if (request.contentLength === null && headerName === 'content-length') {
|
|
@@ -423,35 +373,22 @@ function processHeader (request, key, val, skipAppend = false) {
|
|
|
423
373
|
}
|
|
424
374
|
} else if (request.contentType === null && headerName === 'content-type') {
|
|
425
375
|
request.contentType = val
|
|
426
|
-
|
|
427
|
-
else request.headers += processHeaderValue(key, val)
|
|
376
|
+
request.headers.push(key, val)
|
|
428
377
|
} else if (headerName === 'transfer-encoding' || headerName === 'keep-alive' || headerName === 'upgrade') {
|
|
429
378
|
throw new InvalidArgumentError(`invalid ${headerName} header`)
|
|
430
379
|
} else if (headerName === 'connection') {
|
|
431
380
|
const value = typeof val === 'string' ? val.toLowerCase() : null
|
|
432
381
|
if (value !== 'close' && value !== 'keep-alive') {
|
|
433
382
|
throw new InvalidArgumentError('invalid connection header')
|
|
434
|
-
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (value === 'close') {
|
|
435
386
|
request.reset = true
|
|
436
387
|
}
|
|
437
388
|
} else if (headerName === 'expect') {
|
|
438
389
|
throw new NotSupportedError('expect header not supported')
|
|
439
|
-
} else if (Array.isArray(val)) {
|
|
440
|
-
for (let i = 0; i < val.length; i++) {
|
|
441
|
-
if (skipAppend) {
|
|
442
|
-
if (request.headers[key]) {
|
|
443
|
-
request.headers[key] += `,${processHeaderValue(key, val[i], skipAppend)}`
|
|
444
|
-
} else {
|
|
445
|
-
request.headers[key] = processHeaderValue(key, val[i], skipAppend)
|
|
446
|
-
}
|
|
447
|
-
} else {
|
|
448
|
-
request.headers += processHeaderValue(key, val[i])
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
} else if (skipAppend) {
|
|
452
|
-
request.headers[key] = processHeaderValue(key, val, skipAppend)
|
|
453
390
|
} else {
|
|
454
|
-
request.headers
|
|
391
|
+
request.headers.push(key, val)
|
|
455
392
|
}
|
|
456
393
|
}
|
|
457
394
|
|
package/lib/core/symbols.js
CHANGED
|
@@ -33,6 +33,8 @@ module.exports = {
|
|
|
33
33
|
kNeedDrain: Symbol('need drain'),
|
|
34
34
|
kReset: Symbol('reset'),
|
|
35
35
|
kDestroyed: Symbol.for('nodejs.stream.destroyed'),
|
|
36
|
+
kResume: Symbol('resume'),
|
|
37
|
+
kOnError: Symbol('on error'),
|
|
36
38
|
kMaxHeadersSize: Symbol('max headers size'),
|
|
37
39
|
kRunningIdx: Symbol('running index'),
|
|
38
40
|
kPendingIdx: Symbol('pending index'),
|
|
@@ -54,10 +56,9 @@ module.exports = {
|
|
|
54
56
|
kMaxResponseSize: Symbol('max response size'),
|
|
55
57
|
kHTTP2Session: Symbol('http2Session'),
|
|
56
58
|
kHTTP2SessionState: Symbol('http2Session state'),
|
|
57
|
-
kHTTP2BuildRequest: Symbol('http2 build request'),
|
|
58
|
-
kHTTP1BuildRequest: Symbol('http1 build request'),
|
|
59
|
-
kHTTP2CopyHeaders: Symbol('http2 copy headers'),
|
|
60
|
-
kHTTPConnVersion: Symbol('http connection version'),
|
|
61
59
|
kRetryHandlerDefaultRetry: Symbol('retry agent default retry'),
|
|
62
|
-
kConstruct: Symbol('constructable')
|
|
60
|
+
kConstruct: Symbol('constructable'),
|
|
61
|
+
kListeners: Symbol('listeners'),
|
|
62
|
+
kHTTPContext: Symbol('http context'),
|
|
63
|
+
kMaxConcurrentStreams: Symbol('max concurrent streams')
|
|
63
64
|
}
|
package/lib/core/tree.js
CHANGED
|
@@ -17,7 +17,7 @@ class TstNode {
|
|
|
17
17
|
/** @type {number} */
|
|
18
18
|
code
|
|
19
19
|
/**
|
|
20
|
-
* @param {
|
|
20
|
+
* @param {string} key
|
|
21
21
|
* @param {any} value
|
|
22
22
|
* @param {number} index
|
|
23
23
|
*/
|
|
@@ -25,7 +25,11 @@ class TstNode {
|
|
|
25
25
|
if (index === undefined || index >= key.length) {
|
|
26
26
|
throw new TypeError('Unreachable')
|
|
27
27
|
}
|
|
28
|
-
this.code = key
|
|
28
|
+
const code = this.code = key.charCodeAt(index)
|
|
29
|
+
// check code is ascii string
|
|
30
|
+
if (code > 0x7F) {
|
|
31
|
+
throw new TypeError('key must be ascii string')
|
|
32
|
+
}
|
|
29
33
|
if (key.length !== ++index) {
|
|
30
34
|
this.middle = new TstNode(key, value, index)
|
|
31
35
|
} else {
|
|
@@ -34,33 +38,45 @@ class TstNode {
|
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
/**
|
|
37
|
-
* @param {
|
|
41
|
+
* @param {string} key
|
|
38
42
|
* @param {any} value
|
|
39
|
-
* @param {number} index
|
|
40
43
|
*/
|
|
41
|
-
add (key, value
|
|
42
|
-
|
|
44
|
+
add (key, value) {
|
|
45
|
+
const length = key.length
|
|
46
|
+
if (length === 0) {
|
|
43
47
|
throw new TypeError('Unreachable')
|
|
44
48
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.middle = new TstNode(key, value, index)
|
|
49
|
+
let index = 0
|
|
50
|
+
let node = this
|
|
51
|
+
while (true) {
|
|
52
|
+
const code = key.charCodeAt(index)
|
|
53
|
+
// check code is ascii string
|
|
54
|
+
if (code > 0x7F) {
|
|
55
|
+
throw new TypeError('key must be ascii string')
|
|
53
56
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
if (node.code === code) {
|
|
58
|
+
if (length === ++index) {
|
|
59
|
+
node.value = value
|
|
60
|
+
break
|
|
61
|
+
} else if (node.middle !== null) {
|
|
62
|
+
node = node.middle
|
|
63
|
+
} else {
|
|
64
|
+
node.middle = new TstNode(key, value, index)
|
|
65
|
+
break
|
|
66
|
+
}
|
|
67
|
+
} else if (node.code < code) {
|
|
68
|
+
if (node.left !== null) {
|
|
69
|
+
node = node.left
|
|
70
|
+
} else {
|
|
71
|
+
node.left = new TstNode(key, value, index)
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
} else if (node.right !== null) {
|
|
75
|
+
node = node.right
|
|
57
76
|
} else {
|
|
58
|
-
|
|
77
|
+
node.right = new TstNode(key, value, index)
|
|
78
|
+
break
|
|
59
79
|
}
|
|
60
|
-
} else if (this.right !== null) {
|
|
61
|
-
this.right.add(key, value, index)
|
|
62
|
-
} else {
|
|
63
|
-
this.right = new TstNode(key, value, index)
|
|
64
80
|
}
|
|
65
81
|
}
|
|
66
82
|
|
|
@@ -75,7 +91,10 @@ class TstNode {
|
|
|
75
91
|
while (node !== null && index < keylength) {
|
|
76
92
|
let code = key[index]
|
|
77
93
|
// A-Z
|
|
78
|
-
|
|
94
|
+
// First check if it is bigger than 0x5a.
|
|
95
|
+
// Lowercase letters have higher char codes than uppercase ones.
|
|
96
|
+
// Also we assume that headers will mostly contain lowercase characters.
|
|
97
|
+
if (code <= 0x5a && code >= 0x41) {
|
|
79
98
|
// Lowercase for uppercase.
|
|
80
99
|
code |= 32
|
|
81
100
|
}
|
|
@@ -100,19 +119,20 @@ class TernarySearchTree {
|
|
|
100
119
|
node = null
|
|
101
120
|
|
|
102
121
|
/**
|
|
103
|
-
* @param {
|
|
122
|
+
* @param {string} key
|
|
104
123
|
* @param {any} value
|
|
105
124
|
* */
|
|
106
125
|
insert (key, value) {
|
|
107
126
|
if (this.node === null) {
|
|
108
127
|
this.node = new TstNode(key, value, 0)
|
|
109
128
|
} else {
|
|
110
|
-
this.node.add(key, value
|
|
129
|
+
this.node.add(key, value)
|
|
111
130
|
}
|
|
112
131
|
}
|
|
113
132
|
|
|
114
133
|
/**
|
|
115
134
|
* @param {Uint8Array} key
|
|
135
|
+
* @return {any}
|
|
116
136
|
*/
|
|
117
137
|
lookup (key) {
|
|
118
138
|
return this.node?.search(key)?.value ?? null
|
|
@@ -123,7 +143,7 @@ const tree = new TernarySearchTree()
|
|
|
123
143
|
|
|
124
144
|
for (let i = 0; i < wellknownHeaderNames.length; ++i) {
|
|
125
145
|
const key = headerNameLowerCasedRecord[wellknownHeaderNames[i]]
|
|
126
|
-
tree.insert(
|
|
146
|
+
tree.insert(key, key)
|
|
127
147
|
}
|
|
128
148
|
|
|
129
149
|
module.exports = {
|
package/lib/core/util.js
CHANGED
|
@@ -182,8 +182,8 @@ function bodyLength (body) {
|
|
|
182
182
|
return null
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
function isDestroyed (
|
|
186
|
-
return
|
|
185
|
+
function isDestroyed (body) {
|
|
186
|
+
return body && !!(body.destroyed || body[kDestroyed] || (stream.isDestroyed?.(body)))
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
function isReadableAborted (stream) {
|
|
@@ -204,9 +204,9 @@ function destroy (stream, err) {
|
|
|
204
204
|
|
|
205
205
|
stream.destroy(err)
|
|
206
206
|
} else if (err) {
|
|
207
|
-
|
|
207
|
+
queueMicrotask(() => {
|
|
208
208
|
stream.emit('error', err)
|
|
209
|
-
}
|
|
209
|
+
})
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
if (stream.destroyed !== true) {
|
|
@@ -279,22 +279,30 @@ function parseHeaders (headers, obj) {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
function parseRawHeaders (headers) {
|
|
282
|
-
const
|
|
282
|
+
const len = headers.length
|
|
283
|
+
const ret = new Array(len)
|
|
284
|
+
|
|
283
285
|
let hasContentLength = false
|
|
284
286
|
let contentDispositionIdx = -1
|
|
287
|
+
let key
|
|
288
|
+
let val
|
|
289
|
+
let kLen = 0
|
|
285
290
|
|
|
286
291
|
for (let n = 0; n < headers.length; n += 2) {
|
|
287
|
-
|
|
288
|
-
|
|
292
|
+
key = headers[n]
|
|
293
|
+
val = headers[n + 1]
|
|
294
|
+
|
|
295
|
+
typeof key !== 'string' && (key = key.toString())
|
|
296
|
+
typeof val !== 'string' && (val = val.toString('utf8'))
|
|
289
297
|
|
|
290
|
-
|
|
291
|
-
|
|
298
|
+
kLen = key.length
|
|
299
|
+
if (kLen === 14 && key[7] === '-' && (key === 'content-length' || key.toLowerCase() === 'content-length')) {
|
|
292
300
|
hasContentLength = true
|
|
293
|
-
} else if (
|
|
294
|
-
contentDispositionIdx =
|
|
295
|
-
} else {
|
|
296
|
-
ret.push(key, val)
|
|
301
|
+
} else if (kLen === 19 && key[7] === '-' && (key === 'content-disposition' || key.toLowerCase() === 'content-disposition')) {
|
|
302
|
+
contentDispositionIdx = n + 1
|
|
297
303
|
}
|
|
304
|
+
ret[n] = key
|
|
305
|
+
ret[n + 1] = val
|
|
298
306
|
}
|
|
299
307
|
|
|
300
308
|
// See https://github.com/nodejs/node/pull/46528
|
|
@@ -438,13 +446,7 @@ const hasToWellFormed = !!String.prototype.toWellFormed
|
|
|
438
446
|
* @param {string} val
|
|
439
447
|
*/
|
|
440
448
|
function toUSVString (val) {
|
|
441
|
-
|
|
442
|
-
return `${val}`.toWellFormed()
|
|
443
|
-
} else if (nodeUtil.toUSVString) {
|
|
444
|
-
return nodeUtil.toUSVString(val)
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
return `${val}`
|
|
449
|
+
return hasToWellFormed ? `${val}`.toWellFormed() : nodeUtil.toUSVString(val)
|
|
448
450
|
}
|
|
449
451
|
|
|
450
452
|
/**
|
|
@@ -493,6 +495,24 @@ function isValidHTTPToken (characters) {
|
|
|
493
495
|
return true
|
|
494
496
|
}
|
|
495
497
|
|
|
498
|
+
// headerCharRegex have been lifted from
|
|
499
|
+
// https://github.com/nodejs/node/blob/main/lib/_http_common.js
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Matches if val contains an invalid field-vchar
|
|
503
|
+
* field-value = *( field-content / obs-fold )
|
|
504
|
+
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
|
|
505
|
+
* field-vchar = VCHAR / obs-text
|
|
506
|
+
*/
|
|
507
|
+
const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* @param {string} characters
|
|
511
|
+
*/
|
|
512
|
+
function isValidHeaderChar (characters) {
|
|
513
|
+
return !headerCharRegex.test(characters)
|
|
514
|
+
}
|
|
515
|
+
|
|
496
516
|
// Parsed accordingly to RFC 9110
|
|
497
517
|
// https://www.rfc-editor.org/rfc/rfc9110#field.content-range
|
|
498
518
|
function parseRangeHeader (range) {
|
|
@@ -543,6 +563,7 @@ module.exports = {
|
|
|
543
563
|
buildURL,
|
|
544
564
|
addAbortListener,
|
|
545
565
|
isValidHTTPToken,
|
|
566
|
+
isValidHeaderChar,
|
|
546
567
|
isTokenCharCode,
|
|
547
568
|
parseRangeHeader,
|
|
548
569
|
nodeMajor,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { InvalidArgumentError } = require('
|
|
4
|
-
const { kClients, kRunning, kClose, kDestroy, kDispatch, kInterceptors } = require('
|
|
3
|
+
const { InvalidArgumentError } = require('../core/errors')
|
|
4
|
+
const { kClients, kRunning, kClose, kDestroy, kDispatch, kInterceptors } = require('../core/symbols')
|
|
5
5
|
const DispatcherBase = require('./dispatcher-base')
|
|
6
6
|
const Pool = require('./pool')
|
|
7
7
|
const Client = require('./client')
|
|
8
|
-
const util = require('
|
|
9
|
-
const createRedirectInterceptor = require('
|
|
8
|
+
const util = require('../core/util')
|
|
9
|
+
const createRedirectInterceptor = require('../interceptor/redirect-interceptor')
|
|
10
10
|
|
|
11
11
|
const kOnConnect = Symbol('onConnect')
|
|
12
12
|
const kOnDisconnect = Symbol('onDisconnect')
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const {
|
|
4
4
|
BalancedPoolMissingUpstreamError,
|
|
5
5
|
InvalidArgumentError
|
|
6
|
-
} = require('
|
|
6
|
+
} = require('../core/errors')
|
|
7
7
|
const {
|
|
8
8
|
PoolBase,
|
|
9
9
|
kClients,
|
|
@@ -13,8 +13,8 @@ const {
|
|
|
13
13
|
kGetDispatcher
|
|
14
14
|
} = require('./pool-base')
|
|
15
15
|
const Pool = require('./pool')
|
|
16
|
-
const { kUrl, kInterceptors } = require('
|
|
17
|
-
const { parseOrigin } = require('
|
|
16
|
+
const { kUrl, kInterceptors } = require('../core/symbols')
|
|
17
|
+
const { parseOrigin } = require('../core/util')
|
|
18
18
|
const kFactory = Symbol('factory')
|
|
19
19
|
|
|
20
20
|
const kOptions = Symbol('options')
|