undici 7.0.0-alpha.3 → 7.0.0-alpha.5
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 +2 -1
- package/docs/docs/api/Agent.md +14 -14
- package/docs/docs/api/BalancedPool.md +16 -16
- package/docs/docs/api/CacheStore.md +17 -14
- package/docs/docs/api/Client.md +11 -11
- package/docs/docs/api/Dispatcher.md +30 -10
- package/docs/docs/api/EnvHttpProxyAgent.md +12 -12
- package/docs/docs/api/MockAgent.md +3 -3
- package/docs/docs/api/MockClient.md +5 -5
- package/docs/docs/api/MockPool.md +2 -2
- package/docs/docs/api/Pool.md +15 -15
- package/docs/docs/api/PoolStats.md +1 -1
- package/docs/docs/api/ProxyAgent.md +3 -3
- package/docs/docs/api/RetryHandler.md +2 -2
- package/docs/docs/api/WebSocket.md +1 -1
- package/docs/docs/api/api-lifecycle.md +11 -11
- package/docs/docs/best-practices/mocking-request.md +2 -2
- package/docs/docs/best-practices/proxy.md +1 -1
- package/index.d.ts +1 -1
- package/index.js +2 -1
- package/lib/api/api-request.js +1 -1
- package/lib/cache/memory-cache-store.js +106 -342
- package/lib/core/connect.js +5 -0
- package/lib/core/request.js +2 -2
- package/lib/core/util.js +13 -40
- package/lib/dispatcher/client-h2.js +53 -33
- package/lib/handler/cache-handler.js +126 -85
- package/lib/handler/cache-revalidation-handler.js +45 -13
- package/lib/handler/redirect-handler.js +5 -3
- package/lib/handler/retry-handler.js +3 -3
- package/lib/interceptor/cache.js +213 -92
- package/lib/interceptor/dns.js +71 -48
- package/lib/util/cache.js +73 -13
- package/lib/util/timers.js +19 -1
- package/lib/web/cookies/index.js +12 -1
- package/lib/web/cookies/parse.js +6 -1
- package/lib/web/fetch/body.js +1 -5
- package/lib/web/fetch/formdata-parser.js +70 -43
- package/lib/web/fetch/headers.js +1 -1
- package/lib/web/fetch/index.js +4 -6
- package/lib/web/fetch/webidl.js +12 -4
- package/package.json +2 -3
- package/types/cache-interceptor.d.ts +51 -54
- package/types/cookies.d.ts +2 -0
- package/types/dispatcher.d.ts +1 -1
- package/types/index.d.ts +0 -1
- package/types/interceptors.d.ts +0 -1
|
@@ -1,35 +1,26 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { Writable
|
|
3
|
+
const { Writable } = require('node:stream')
|
|
4
|
+
const { nowAbsolute } = require('../util/timers.js')
|
|
4
5
|
|
|
5
6
|
/**
|
|
7
|
+
* @typedef {import('../../types/cache-interceptor.d.ts').default.CacheKey} CacheKey
|
|
8
|
+
* @typedef {import('../../types/cache-interceptor.d.ts').default.CacheValue} CacheValue
|
|
6
9
|
* @typedef {import('../../types/cache-interceptor.d.ts').default.CacheStore} CacheStore
|
|
10
|
+
* @typedef {import('../../types/cache-interceptor.d.ts').default.GetResult} GetResult
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
7
14
|
* @implements {CacheStore}
|
|
8
|
-
*
|
|
9
|
-
* @typedef {{
|
|
10
|
-
* readers: number
|
|
11
|
-
* readLock: boolean
|
|
12
|
-
* writeLock: boolean
|
|
13
|
-
* opts: import('../../types/cache-interceptor.d.ts').default.CacheStoreValue
|
|
14
|
-
* body: Buffer[]
|
|
15
|
-
* }} MemoryStoreValue
|
|
16
15
|
*/
|
|
17
16
|
class MemoryCacheStore {
|
|
18
|
-
#
|
|
19
|
-
|
|
17
|
+
#maxCount = Infinity
|
|
18
|
+
#maxSize = Infinity
|
|
20
19
|
#maxEntrySize = Infinity
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
#errorCallback = undefined
|
|
26
|
-
|
|
27
|
-
#entryCount = 0
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* @type {Map<string, Map<string, MemoryStoreValue>>}
|
|
31
|
-
*/
|
|
32
|
-
#data = new Map()
|
|
21
|
+
#size = 0
|
|
22
|
+
#count = 0
|
|
23
|
+
#entries = new Map()
|
|
33
24
|
|
|
34
25
|
/**
|
|
35
26
|
* @param {import('../../types/cache-interceptor.d.ts').default.MemoryCacheStoreOpts | undefined} [opts]
|
|
@@ -40,15 +31,26 @@ class MemoryCacheStore {
|
|
|
40
31
|
throw new TypeError('MemoryCacheStore options must be an object')
|
|
41
32
|
}
|
|
42
33
|
|
|
43
|
-
if (opts.
|
|
34
|
+
if (opts.maxCount !== undefined) {
|
|
35
|
+
if (
|
|
36
|
+
typeof opts.maxCount !== 'number' ||
|
|
37
|
+
!Number.isInteger(opts.maxCount) ||
|
|
38
|
+
opts.maxCount < 0
|
|
39
|
+
) {
|
|
40
|
+
throw new TypeError('MemoryCacheStore options.maxCount must be a non-negative integer')
|
|
41
|
+
}
|
|
42
|
+
this.#maxCount = opts.maxCount
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (opts.maxSize !== undefined) {
|
|
44
46
|
if (
|
|
45
|
-
typeof opts.
|
|
46
|
-
!Number.isInteger(opts.
|
|
47
|
-
opts.
|
|
47
|
+
typeof opts.maxSize !== 'number' ||
|
|
48
|
+
!Number.isInteger(opts.maxSize) ||
|
|
49
|
+
opts.maxSize < 0
|
|
48
50
|
) {
|
|
49
|
-
throw new TypeError('MemoryCacheStore options.
|
|
51
|
+
throw new TypeError('MemoryCacheStore options.maxSize must be a non-negative integer')
|
|
50
52
|
}
|
|
51
|
-
this.#
|
|
53
|
+
this.#maxSize = opts.maxSize
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
if (opts.maxEntrySize !== undefined) {
|
|
@@ -61,356 +63,118 @@ class MemoryCacheStore {
|
|
|
61
63
|
}
|
|
62
64
|
this.#maxEntrySize = opts.maxEntrySize
|
|
63
65
|
}
|
|
64
|
-
|
|
65
|
-
if (opts.errorCallback !== undefined) {
|
|
66
|
-
if (typeof opts.errorCallback !== 'function') {
|
|
67
|
-
throw new TypeError('MemoryCacheStore options.errorCallback must be a function')
|
|
68
|
-
}
|
|
69
|
-
this.#errorCallback = opts.errorCallback
|
|
70
|
-
}
|
|
71
66
|
}
|
|
72
67
|
}
|
|
73
68
|
|
|
74
|
-
get isFull () {
|
|
75
|
-
return this.#entryCount >= this.#maxEntries
|
|
76
|
-
}
|
|
77
|
-
|
|
78
69
|
/**
|
|
79
|
-
* @param {import('../../types/
|
|
80
|
-
* @returns {import('../../types/cache-interceptor.d.ts').default.
|
|
70
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} req
|
|
71
|
+
* @returns {import('../../types/cache-interceptor.d.ts').default.GetResult | undefined}
|
|
81
72
|
*/
|
|
82
|
-
|
|
83
|
-
if (typeof
|
|
84
|
-
throw new TypeError(`expected
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const values = this.#getValuesForRequest(req, false)
|
|
88
|
-
if (!values) {
|
|
89
|
-
return undefined
|
|
73
|
+
get (key) {
|
|
74
|
+
if (typeof key !== 'object') {
|
|
75
|
+
throw new TypeError(`expected key to be object, got ${typeof key}`)
|
|
90
76
|
}
|
|
91
77
|
|
|
92
|
-
const
|
|
78
|
+
const topLevelKey = `${key.origin}:${key.path}`
|
|
93
79
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
80
|
+
const now = nowAbsolute()
|
|
81
|
+
const entry = this.#entries.get(topLevelKey)?.find((entry) => (
|
|
82
|
+
entry.deleteAt > now &&
|
|
83
|
+
entry.method === key.method &&
|
|
84
|
+
(entry.vary == null || Object.keys(entry.vary).every(headerName => entry.vary[headerName] === key.headers?.[headerName]))
|
|
85
|
+
))
|
|
97
86
|
|
|
98
|
-
return
|
|
87
|
+
return entry == null
|
|
88
|
+
? undefined
|
|
89
|
+
: {
|
|
90
|
+
statusMessage: entry.statusMessage,
|
|
91
|
+
statusCode: entry.statusCode,
|
|
92
|
+
rawHeaders: entry.rawHeaders,
|
|
93
|
+
body: entry.body,
|
|
94
|
+
etag: entry.etag,
|
|
95
|
+
cachedAt: entry.cachedAt,
|
|
96
|
+
staleAt: entry.staleAt,
|
|
97
|
+
deleteAt: entry.deleteAt
|
|
98
|
+
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
|
-
* @param {import('../../types/
|
|
103
|
-
* @param {import('../../types/cache-interceptor.d.ts').default.
|
|
104
|
-
* @returns {
|
|
102
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key
|
|
103
|
+
* @param {import('../../types/cache-interceptor.d.ts').default.CacheValue} val
|
|
104
|
+
* @returns {Writable | undefined}
|
|
105
105
|
*/
|
|
106
|
-
createWriteStream (
|
|
107
|
-
if (typeof
|
|
108
|
-
throw new TypeError(`expected
|
|
109
|
-
}
|
|
110
|
-
if (typeof opts !== 'object') {
|
|
111
|
-
throw new TypeError(`expected value to be object, got ${typeof opts}`)
|
|
106
|
+
createWriteStream (key, val) {
|
|
107
|
+
if (typeof key !== 'object') {
|
|
108
|
+
throw new TypeError(`expected key to be object, got ${typeof key}`)
|
|
112
109
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return undefined
|
|
110
|
+
if (typeof val !== 'object') {
|
|
111
|
+
throw new TypeError(`expected value to be object, got ${typeof val}`)
|
|
116
112
|
}
|
|
117
113
|
|
|
118
|
-
const
|
|
114
|
+
const topLevelKey = `${key.origin}:${key.path}`
|
|
119
115
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// The value doesn't already exist, meaning we haven't cached this
|
|
123
|
-
// response before. Let's assign it a value and insert it into our data
|
|
124
|
-
// property.
|
|
116
|
+
const store = this
|
|
117
|
+
const entry = { ...key, ...val, body: [], size: 0 }
|
|
125
118
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.#entryCount++
|
|
132
|
-
|
|
133
|
-
value = {
|
|
134
|
-
readers: 0,
|
|
135
|
-
readLock: false,
|
|
136
|
-
writeLock: false,
|
|
137
|
-
opts,
|
|
138
|
-
body: []
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// We want to sort our responses in decending order by their deleteAt
|
|
142
|
-
// timestamps so that deleting expired responses is faster
|
|
143
|
-
if (
|
|
144
|
-
values.length === 0 ||
|
|
145
|
-
opts.deleteAt < values[values.length - 1].deleteAt
|
|
146
|
-
) {
|
|
147
|
-
// Our value is either the only response for this path or our deleteAt
|
|
148
|
-
// time is sooner than all the other responses
|
|
149
|
-
values.push(value)
|
|
150
|
-
} else if (opts.deleteAt >= values[0].deleteAt) {
|
|
151
|
-
// Our deleteAt is later than everyone elses
|
|
152
|
-
values.unshift(value)
|
|
153
|
-
} else {
|
|
154
|
-
// We're neither in the front or the end, let's just binary search to
|
|
155
|
-
// find our stop we need to be in
|
|
156
|
-
let startIndex = 0
|
|
157
|
-
let endIndex = values.length
|
|
158
|
-
while (true) {
|
|
159
|
-
if (startIndex === endIndex) {
|
|
160
|
-
values.splice(startIndex, 0, value)
|
|
161
|
-
break
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const middleIndex = Math.floor((startIndex + endIndex) / 2)
|
|
165
|
-
const middleValue = values[middleIndex]
|
|
166
|
-
if (opts.deleteAt === middleIndex) {
|
|
167
|
-
values.splice(middleIndex, 0, value)
|
|
168
|
-
break
|
|
169
|
-
} else if (opts.deleteAt > middleValue.opts.deleteAt) {
|
|
170
|
-
endIndex = middleIndex
|
|
171
|
-
continue
|
|
172
|
-
} else {
|
|
173
|
-
startIndex = middleIndex
|
|
174
|
-
continue
|
|
175
|
-
}
|
|
119
|
+
return new Writable({
|
|
120
|
+
write (chunk, encoding, callback) {
|
|
121
|
+
if (typeof chunk === 'string') {
|
|
122
|
+
chunk = Buffer.from(chunk, encoding)
|
|
176
123
|
}
|
|
177
|
-
}
|
|
178
|
-
} else {
|
|
179
|
-
// Check if there's already another request writing to the value or
|
|
180
|
-
// a request reading from it
|
|
181
|
-
if (value.writeLock || value.readLock) {
|
|
182
|
-
return undefined
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Empty it so we can overwrite it
|
|
186
|
-
value.body = []
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const writable = new MemoryStoreWritableStream(
|
|
190
|
-
value,
|
|
191
|
-
this.#maxEntrySize
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
// Remove the value if there was some error
|
|
195
|
-
writable.on('error', (err) => {
|
|
196
|
-
values.filter(current => value !== current)
|
|
197
|
-
if (this.#errorCallback) {
|
|
198
|
-
this.#errorCallback(err)
|
|
199
|
-
}
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
writable.on('bodyOversized', () => {
|
|
203
|
-
values.filter(current => value !== current)
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
return writable
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* @param {string} origin
|
|
211
|
-
*/
|
|
212
|
-
deleteByOrigin (origin) {
|
|
213
|
-
this.#data.delete(origin)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Gets all of the requests of the same origin, path, and method. Does not
|
|
218
|
-
* take the `vary` property into account.
|
|
219
|
-
* @param {import('../../types/dispatcher.d.ts').default.RequestOptions} req
|
|
220
|
-
* @param {boolean} [makeIfDoesntExist=false]
|
|
221
|
-
*/
|
|
222
|
-
#getValuesForRequest (req, makeIfDoesntExist) {
|
|
223
|
-
// https://www.rfc-editor.org/rfc/rfc9111.html#section-2-3
|
|
224
|
-
let cachedPaths = this.#data.get(req.origin)
|
|
225
|
-
if (!cachedPaths) {
|
|
226
|
-
if (!makeIfDoesntExist) {
|
|
227
|
-
return undefined
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
cachedPaths = new Map()
|
|
231
|
-
this.#data.set(req.origin, cachedPaths)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
let values = cachedPaths.get(`${req.path}:${req.method}`)
|
|
235
|
-
if (!values && makeIfDoesntExist) {
|
|
236
|
-
values = []
|
|
237
|
-
cachedPaths.set(`${req.path}:${req.method}`, values)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return values
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Given a list of values of a certain request, this decides the best value
|
|
245
|
-
* to respond with.
|
|
246
|
-
* @param {import('../../types/dispatcher.d.ts').default.RequestOptions} req
|
|
247
|
-
* @param {MemoryStoreValue[]} values
|
|
248
|
-
* @returns {MemoryStoreValue | undefined}
|
|
249
|
-
*/
|
|
250
|
-
#findValue (req, values) {
|
|
251
|
-
/**
|
|
252
|
-
* @type {MemoryStoreValue}
|
|
253
|
-
*/
|
|
254
|
-
let value
|
|
255
|
-
const now = Date.now()
|
|
256
|
-
for (let i = values.length - 1; i >= 0; i--) {
|
|
257
|
-
const current = values[i]
|
|
258
|
-
const currentCacheValue = current.opts
|
|
259
|
-
if (now >= currentCacheValue.deleteAt) {
|
|
260
|
-
// We've reached expired values, let's delete them
|
|
261
|
-
this.#entryCount -= values.length - i
|
|
262
|
-
values.length = i
|
|
263
|
-
break
|
|
264
|
-
}
|
|
265
124
|
|
|
266
|
-
|
|
125
|
+
entry.size += chunk.byteLength
|
|
267
126
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
127
|
+
if (entry.size >= store.#maxEntrySize) {
|
|
128
|
+
this.destroy()
|
|
129
|
+
} else {
|
|
130
|
+
entry.body.push(chunk)
|
|
272
131
|
}
|
|
273
132
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
133
|
+
callback(null)
|
|
134
|
+
},
|
|
135
|
+
final (callback) {
|
|
136
|
+
let entries = store.#entries.get(topLevelKey)
|
|
137
|
+
if (!entries) {
|
|
138
|
+
entries = []
|
|
139
|
+
store.#entries.set(topLevelKey, entries)
|
|
140
|
+
}
|
|
141
|
+
entries.push(entry)
|
|
142
|
+
|
|
143
|
+
store.#size += entry.size
|
|
144
|
+
store.#count += 1
|
|
145
|
+
|
|
146
|
+
if (store.#size > store.#maxSize || store.#count > store.#maxCount) {
|
|
147
|
+
for (const [key, entries] of store.#entries) {
|
|
148
|
+
for (const entry of entries.splice(0, entries.length / 2)) {
|
|
149
|
+
store.#size -= entry.size
|
|
150
|
+
store.#count -= 1
|
|
151
|
+
}
|
|
152
|
+
if (entries.length === 0) {
|
|
153
|
+
store.#entries.delete(key)
|
|
154
|
+
}
|
|
278
155
|
}
|
|
279
156
|
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (matches) {
|
|
283
|
-
value = current
|
|
284
|
-
break
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return value
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
class MemoryStoreReadableStream extends Readable {
|
|
293
|
-
/**
|
|
294
|
-
* @type {MemoryStoreValue}
|
|
295
|
-
*/
|
|
296
|
-
#value
|
|
297
|
-
/**
|
|
298
|
-
* @type {Buffer[]}
|
|
299
|
-
*/
|
|
300
|
-
#chunksToSend = []
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* @param {MemoryStoreValue} value
|
|
304
|
-
*/
|
|
305
|
-
constructor (value) {
|
|
306
|
-
super()
|
|
307
|
-
|
|
308
|
-
if (value.readLock) {
|
|
309
|
-
throw new Error('can\'t read a locked value')
|
|
310
|
-
}
|
|
311
157
|
|
|
312
|
-
|
|
313
|
-
this.#chunksToSend = value?.body ? [...value.body, null] : [null]
|
|
314
|
-
|
|
315
|
-
this.#value.readers++
|
|
316
|
-
this.#value.writeLock = true
|
|
317
|
-
|
|
318
|
-
this.on('close', () => {
|
|
319
|
-
this.#value.readers--
|
|
320
|
-
|
|
321
|
-
if (this.#value.readers === 0) {
|
|
322
|
-
this.#value.writeLock = false
|
|
158
|
+
callback(null)
|
|
323
159
|
}
|
|
324
160
|
})
|
|
325
161
|
}
|
|
326
162
|
|
|
327
|
-
get value () {
|
|
328
|
-
return this.#value.opts
|
|
329
|
-
}
|
|
330
|
-
|
|
331
163
|
/**
|
|
332
|
-
* @param {
|
|
164
|
+
* @param {CacheKey} key
|
|
333
165
|
*/
|
|
334
|
-
|
|
335
|
-
if (
|
|
336
|
-
throw new
|
|
166
|
+
delete (key) {
|
|
167
|
+
if (typeof key !== 'object') {
|
|
168
|
+
throw new TypeError(`expected key to be object, got ${typeof key}`)
|
|
337
169
|
}
|
|
338
170
|
|
|
339
|
-
|
|
340
|
-
size = this.#chunksToSend.length
|
|
341
|
-
}
|
|
171
|
+
const topLevelKey = `${key.origin}:${key.path}`
|
|
342
172
|
|
|
343
|
-
for (
|
|
344
|
-
this
|
|
173
|
+
for (const entry of this.#entries.get(topLevelKey) ?? []) {
|
|
174
|
+
this.#size -= entry.size
|
|
175
|
+
this.#count -= 1
|
|
345
176
|
}
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
class MemoryStoreWritableStream extends Writable {
|
|
350
|
-
/**
|
|
351
|
-
* @type {MemoryStoreValue}
|
|
352
|
-
*/
|
|
353
|
-
#value
|
|
354
|
-
#currentSize = 0
|
|
355
|
-
#maxEntrySize = 0
|
|
356
|
-
/**
|
|
357
|
-
* @type {Buffer[]|null}
|
|
358
|
-
*/
|
|
359
|
-
#body = []
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* @param {MemoryStoreValue} value
|
|
363
|
-
* @param {number} maxEntrySize
|
|
364
|
-
*/
|
|
365
|
-
constructor (value, maxEntrySize) {
|
|
366
|
-
super()
|
|
367
|
-
this.#value = value
|
|
368
|
-
this.#value.readLock = true
|
|
369
|
-
this.#maxEntrySize = maxEntrySize ?? Infinity
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
get rawTrailers () {
|
|
373
|
-
return this.#value.opts.rawTrailers
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* @param {string[] | undefined} trailers
|
|
378
|
-
*/
|
|
379
|
-
set rawTrailers (trailers) {
|
|
380
|
-
this.#value.opts.rawTrailers = trailers
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* @param {Buffer} chunk
|
|
385
|
-
* @param {string} encoding
|
|
386
|
-
* @param {BufferEncoding} encoding
|
|
387
|
-
*/
|
|
388
|
-
_write (chunk, encoding, callback) {
|
|
389
|
-
if (typeof chunk === 'string') {
|
|
390
|
-
chunk = Buffer.from(chunk, encoding)
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
this.#currentSize += chunk.byteLength
|
|
394
|
-
if (this.#currentSize < this.#maxEntrySize) {
|
|
395
|
-
this.#body.push(chunk)
|
|
396
|
-
} else {
|
|
397
|
-
this.#body = null // release memory as early as possible
|
|
398
|
-
this.emit('bodyOversized')
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
callback()
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* @param {() => void} callback
|
|
406
|
-
*/
|
|
407
|
-
_final (callback) {
|
|
408
|
-
if (this.#currentSize < this.#maxEntrySize) {
|
|
409
|
-
this.#value.readLock = false
|
|
410
|
-
this.#value.body = this.#body
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
callback()
|
|
177
|
+
this.#entries.delete(topLevelKey)
|
|
414
178
|
}
|
|
415
179
|
}
|
|
416
180
|
|
package/lib/core/connect.js
CHANGED
|
@@ -220,6 +220,11 @@ const setupConnectTimeout = process.platform === 'win32'
|
|
|
220
220
|
* @param {number} opts.port
|
|
221
221
|
*/
|
|
222
222
|
function onConnectTimeout (socket, opts) {
|
|
223
|
+
// The socket could be already garbage collected
|
|
224
|
+
if (socket == null) {
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
|
|
223
228
|
let message = 'Connect Timeout Error'
|
|
224
229
|
if (Array.isArray(socket.autoSelectFamilyAttemptedAddresses)) {
|
|
225
230
|
message += ` (attempted addresses: ${socket.autoSelectFamilyAttemptedAddresses.join(', ')},`
|
package/lib/core/request.js
CHANGED
|
@@ -130,7 +130,6 @@ class Request {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
this.completed = false
|
|
133
|
-
|
|
134
133
|
this.aborted = false
|
|
135
134
|
|
|
136
135
|
this.upgrade = upgrade || null
|
|
@@ -143,7 +142,7 @@ class Request {
|
|
|
143
142
|
? method === 'HEAD' || method === 'GET'
|
|
144
143
|
: idempotent
|
|
145
144
|
|
|
146
|
-
this.blocking = blocking
|
|
145
|
+
this.blocking = blocking ?? this.method !== 'HEAD'
|
|
147
146
|
|
|
148
147
|
this.reset = reset == null ? null : reset
|
|
149
148
|
|
|
@@ -272,6 +271,7 @@ class Request {
|
|
|
272
271
|
this.onFinally()
|
|
273
272
|
|
|
274
273
|
assert(!this.aborted)
|
|
274
|
+
assert(!this.completed)
|
|
275
275
|
|
|
276
276
|
this.completed = true
|
|
277
277
|
if (channels.trailers.hasSubscribers) {
|
package/lib/core/util.js
CHANGED
|
@@ -10,7 +10,7 @@ const nodeUtil = require('node:util')
|
|
|
10
10
|
const { stringify } = require('node:querystring')
|
|
11
11
|
const { EventEmitter: EE } = require('node:events')
|
|
12
12
|
const { InvalidArgumentError } = require('./errors')
|
|
13
|
-
const { headerNameLowerCasedRecord
|
|
13
|
+
const { headerNameLowerCasedRecord } = require('./constants')
|
|
14
14
|
const { tree } = require('./tree')
|
|
15
15
|
|
|
16
16
|
const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v))
|
|
@@ -436,44 +436,6 @@ function parseHeaders (headers, obj) {
|
|
|
436
436
|
return obj
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
-
/**
|
|
440
|
-
* @param {Record<string, string | string[]>} headers
|
|
441
|
-
* @returns {(Buffer | Buffer[])[]}
|
|
442
|
-
*/
|
|
443
|
-
function encodeHeaders (headers) {
|
|
444
|
-
const headerNames = Object.keys(headers)
|
|
445
|
-
|
|
446
|
-
/**
|
|
447
|
-
* @type {Buffer[]|Buffer[][]}
|
|
448
|
-
*/
|
|
449
|
-
const rawHeaders = new Array(headerNames.length * 2)
|
|
450
|
-
|
|
451
|
-
let rawHeadersIndex = 0
|
|
452
|
-
for (const header of headerNames) {
|
|
453
|
-
let rawValue
|
|
454
|
-
const value = headers[header]
|
|
455
|
-
if (Array.isArray(value)) {
|
|
456
|
-
rawValue = new Array(value.length)
|
|
457
|
-
|
|
458
|
-
for (let i = 0; i < value.length; i++) {
|
|
459
|
-
rawValue[i] = Buffer.from(value[i])
|
|
460
|
-
}
|
|
461
|
-
} else {
|
|
462
|
-
rawValue = Buffer.from(value)
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const headerBuffer = getHeaderNameAsBuffer(header)
|
|
466
|
-
|
|
467
|
-
rawHeaders[rawHeadersIndex] = headerBuffer
|
|
468
|
-
rawHeadersIndex++
|
|
469
|
-
|
|
470
|
-
rawHeaders[rawHeadersIndex] = rawValue
|
|
471
|
-
rawHeadersIndex++
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
return rawHeaders
|
|
475
|
-
}
|
|
476
|
-
|
|
477
439
|
/**
|
|
478
440
|
* @param {Buffer[]} headers
|
|
479
441
|
* @returns {string[]}
|
|
@@ -516,6 +478,17 @@ function parseRawHeaders (headers) {
|
|
|
516
478
|
return ret
|
|
517
479
|
}
|
|
518
480
|
|
|
481
|
+
/**
|
|
482
|
+
* @param {string[]} headers
|
|
483
|
+
* @param {Buffer[]} headers
|
|
484
|
+
*/
|
|
485
|
+
function encodeRawHeaders (headers) {
|
|
486
|
+
if (!Array.isArray(headers)) {
|
|
487
|
+
throw new TypeError('expected headers to be an array')
|
|
488
|
+
}
|
|
489
|
+
return headers.map(x => Buffer.from(x))
|
|
490
|
+
}
|
|
491
|
+
|
|
519
492
|
/**
|
|
520
493
|
* @param {*} buffer
|
|
521
494
|
* @returns {buffer is Buffer}
|
|
@@ -901,8 +874,8 @@ module.exports = {
|
|
|
901
874
|
removeAllListeners,
|
|
902
875
|
errorRequest,
|
|
903
876
|
parseRawHeaders,
|
|
877
|
+
encodeRawHeaders,
|
|
904
878
|
parseHeaders,
|
|
905
|
-
encodeHeaders,
|
|
906
879
|
parseKeepAliveTimeout,
|
|
907
880
|
destroy,
|
|
908
881
|
bodyLength,
|