undici 5.26.5 → 5.27.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.
@@ -222,6 +222,14 @@ class Request {
222
222
  if (channels.bodySent.hasSubscribers) {
223
223
  channels.bodySent.publish({ request: this })
224
224
  }
225
+
226
+ if (this[kHandler].onRequestSent) {
227
+ try {
228
+ this[kHandler].onRequestSent()
229
+ } catch (err) {
230
+ this.onError(err)
231
+ }
232
+ }
225
233
  }
226
234
 
227
235
  onConnect (abort) {
package/lib/fetch/body.js CHANGED
@@ -26,6 +26,8 @@ let ReadableStream = globalThis.ReadableStream
26
26
 
27
27
  /** @type {globalThis['File']} */
28
28
  const File = NativeFile ?? UndiciFile
29
+ const textEncoder = new TextEncoder()
30
+ const textDecoder = new TextDecoder()
29
31
 
30
32
  // https://fetch.spec.whatwg.org/#concept-bodyinit-extract
31
33
  function extractBody (object, keepalive = false) {
@@ -49,7 +51,7 @@ function extractBody (object, keepalive = false) {
49
51
  stream = new ReadableStream({
50
52
  async pull (controller) {
51
53
  controller.enqueue(
52
- typeof source === 'string' ? new TextEncoder().encode(source) : source
54
+ typeof source === 'string' ? textEncoder.encode(source) : source
53
55
  )
54
56
  queueMicrotask(() => readableStreamClose(controller))
55
57
  },
@@ -119,7 +121,6 @@ function extractBody (object, keepalive = false) {
119
121
  // - That the content-length is calculated in advance.
120
122
  // - And that all parts are pre-encoded and ready to be sent.
121
123
 
122
- const enc = new TextEncoder()
123
124
  const blobParts = []
124
125
  const rn = new Uint8Array([13, 10]) // '\r\n'
125
126
  length = 0
@@ -127,13 +128,13 @@ function extractBody (object, keepalive = false) {
127
128
 
128
129
  for (const [name, value] of object) {
129
130
  if (typeof value === 'string') {
130
- const chunk = enc.encode(prefix +
131
+ const chunk = textEncoder.encode(prefix +
131
132
  `; name="${escape(normalizeLinefeeds(name))}"` +
132
133
  `\r\n\r\n${normalizeLinefeeds(value)}\r\n`)
133
134
  blobParts.push(chunk)
134
135
  length += chunk.byteLength
135
136
  } else {
136
- const chunk = enc.encode(`${prefix}; name="${escape(normalizeLinefeeds(name))}"` +
137
+ const chunk = textEncoder.encode(`${prefix}; name="${escape(normalizeLinefeeds(name))}"` +
137
138
  (value.name ? `; filename="${escape(value.name)}"` : '') + '\r\n' +
138
139
  `Content-Type: ${
139
140
  value.type || 'application/octet-stream'
@@ -147,7 +148,7 @@ function extractBody (object, keepalive = false) {
147
148
  }
148
149
  }
149
150
 
150
- const chunk = enc.encode(`--${boundary}--`)
151
+ const chunk = textEncoder.encode(`--${boundary}--`)
151
152
  blobParts.push(chunk)
152
153
  length += chunk.byteLength
153
154
  if (hasUnknownSizeValue) {
@@ -443,14 +444,16 @@ function bodyMixinMethods (instance) {
443
444
  let text = ''
444
445
  // application/x-www-form-urlencoded parser will keep the BOM.
445
446
  // https://url.spec.whatwg.org/#concept-urlencoded-parser
446
- const textDecoder = new TextDecoder('utf-8', { ignoreBOM: true })
447
+ // Note that streaming decoder is stateful and cannot be reused
448
+ const streamingDecoder = new TextDecoder('utf-8', { ignoreBOM: true })
449
+
447
450
  for await (const chunk of consumeBody(this[kState].body)) {
448
451
  if (!isUint8Array(chunk)) {
449
452
  throw new TypeError('Expected Uint8Array chunk')
450
453
  }
451
- text += textDecoder.decode(chunk, { stream: true })
454
+ text += streamingDecoder.decode(chunk, { stream: true })
452
455
  }
453
- text += textDecoder.decode()
456
+ text += streamingDecoder.decode()
454
457
  entries = new URLSearchParams(text)
455
458
  } catch (err) {
456
459
  // istanbul ignore next: Unclear when new URLSearchParams can fail on a string.
@@ -565,7 +568,7 @@ function utf8DecodeBytes (buffer) {
565
568
 
566
569
  // 3. Process a queue with an instance of UTF-8’s
567
570
  // decoder, ioQueue, output, and "replacement".
568
- const output = new TextDecoder().decode(buffer)
571
+ const output = textDecoder.decode(buffer)
569
572
 
570
573
  // 4. Return output.
571
574
  return output
@@ -3,10 +3,12 @@
3
3
  const { MessageChannel, receiveMessageOnPort } = require('worker_threads')
4
4
 
5
5
  const corsSafeListedMethods = ['GET', 'HEAD', 'POST']
6
+ const corsSafeListedMethodsSet = new Set(corsSafeListedMethods)
6
7
 
7
8
  const nullBodyStatus = [101, 204, 205, 304]
8
9
 
9
10
  const redirectStatus = [301, 302, 303, 307, 308]
11
+ const redirectStatusSet = new Set(redirectStatus)
10
12
 
11
13
  // https://fetch.spec.whatwg.org/#block-bad-port
12
14
  const badPorts = [
@@ -18,6 +20,8 @@ const badPorts = [
18
20
  '10080'
19
21
  ]
20
22
 
23
+ const badPortsSet = new Set(badPorts)
24
+
21
25
  // https://w3c.github.io/webappsec-referrer-policy/#referrer-policies
22
26
  const referrerPolicy = [
23
27
  '',
@@ -30,10 +34,12 @@ const referrerPolicy = [
30
34
  'strict-origin-when-cross-origin',
31
35
  'unsafe-url'
32
36
  ]
37
+ const referrerPolicySet = new Set(referrerPolicy)
33
38
 
34
39
  const requestRedirect = ['follow', 'manual', 'error']
35
40
 
36
41
  const safeMethods = ['GET', 'HEAD', 'OPTIONS', 'TRACE']
42
+ const safeMethodsSet = new Set(safeMethods)
37
43
 
38
44
  const requestMode = ['navigate', 'same-origin', 'no-cors', 'cors']
39
45
 
@@ -68,6 +74,7 @@ const requestDuplex = [
68
74
 
69
75
  // http://fetch.spec.whatwg.org/#forbidden-method
70
76
  const forbiddenMethods = ['CONNECT', 'TRACE', 'TRACK']
77
+ const forbiddenMethodsSet = new Set(forbiddenMethods)
71
78
 
72
79
  const subresource = [
73
80
  'audio',
@@ -83,6 +90,7 @@ const subresource = [
83
90
  'xslt',
84
91
  ''
85
92
  ]
93
+ const subresourceSet = new Set(subresource)
86
94
 
87
95
  /** @type {globalThis['DOMException']} */
88
96
  const DOMException = globalThis.DOMException ?? (() => {
@@ -132,5 +140,12 @@ module.exports = {
132
140
  nullBodyStatus,
133
141
  safeMethods,
134
142
  badPorts,
135
- requestDuplex
143
+ requestDuplex,
144
+ subresourceSet,
145
+ badPortsSet,
146
+ redirectStatusSet,
147
+ corsSafeListedMethodsSet,
148
+ safeMethodsSet,
149
+ forbiddenMethodsSet,
150
+ referrerPolicySet
136
151
  }
package/lib/fetch/file.js CHANGED
@@ -7,6 +7,7 @@ const { isBlobLike } = require('./util')
7
7
  const { webidl } = require('./webidl')
8
8
  const { parseMIMEType, serializeAMimeType } = require('./dataURL')
9
9
  const { kEnumerableProperty } = require('../core/util')
10
+ const encoder = new TextEncoder()
10
11
 
11
12
  class File extends Blob {
12
13
  constructor (fileBits, fileName, options = {}) {
@@ -280,7 +281,7 @@ function processBlobParts (parts, options) {
280
281
  }
281
282
 
282
283
  // 3. Append the result of UTF-8 encoding s to bytes.
283
- bytes.push(new TextEncoder().encode(s))
284
+ bytes.push(encoder.encode(s))
284
285
  } else if (
285
286
  types.isAnyArrayBuffer(element) ||
286
287
  types.isTypedArray(element)
@@ -46,11 +46,11 @@ const { kState, kHeaders, kGuard, kRealm } = require('./symbols')
46
46
  const assert = require('assert')
47
47
  const { safelyExtractBody } = require('./body')
48
48
  const {
49
- redirectStatus,
49
+ redirectStatusSet,
50
50
  nullBodyStatus,
51
- safeMethods,
51
+ safeMethodsSet,
52
52
  requestBodyHeader,
53
- subresource,
53
+ subresourceSet,
54
54
  DOMException
55
55
  } = require('./constants')
56
56
  const { kHeadersList } = require('../core/symbols')
@@ -62,6 +62,7 @@ const { TransformStream } = require('stream/web')
62
62
  const { getGlobalDispatcher } = require('../global')
63
63
  const { webidl } = require('./webidl')
64
64
  const { STATUS_CODES } = require('http')
65
+ const GET_OR_HEAD = ['GET', 'HEAD']
65
66
 
66
67
  /** @type {import('buffer').resolveObjectURL} */
67
68
  let resolveObjectURL
@@ -509,7 +510,7 @@ function fetching ({
509
510
  }
510
511
 
511
512
  // 15. If request is a subresource request, then:
512
- if (subresource.includes(request.destination)) {
513
+ if (subresourceSet.has(request.destination)) {
513
514
  // TODO
514
515
  }
515
516
 
@@ -1063,7 +1064,7 @@ async function httpFetch (fetchParams) {
1063
1064
  }
1064
1065
 
1065
1066
  // 8. If actualResponse’s status is a redirect status, then:
1066
- if (redirectStatus.includes(actualResponse.status)) {
1067
+ if (redirectStatusSet.has(actualResponse.status)) {
1067
1068
  // 1. If actualResponse’s status is not 303, request’s body is not null,
1068
1069
  // and the connection uses HTTP/2, then user agents may, and are even
1069
1070
  // encouraged to, transmit an RST_STREAM frame.
@@ -1181,7 +1182,7 @@ function httpRedirectFetch (fetchParams, response) {
1181
1182
  if (
1182
1183
  ([301, 302].includes(actualResponse.status) && request.method === 'POST') ||
1183
1184
  (actualResponse.status === 303 &&
1184
- !['GET', 'HEAD'].includes(request.method))
1185
+ !GET_OR_HEAD.includes(request.method))
1185
1186
  ) {
1186
1187
  // then:
1187
1188
  // 1. Set request’s method to `GET` and request’s body to null.
@@ -1465,7 +1466,7 @@ async function httpNetworkOrCacheFetch (
1465
1466
  // responses in httpCache, as per the "Invalidation" chapter of HTTP
1466
1467
  // Caching, and set storedResponse to null. [HTTP-CACHING]
1467
1468
  if (
1468
- !safeMethods.includes(httpRequest.method) &&
1469
+ !safeMethodsSet.has(httpRequest.method) &&
1469
1470
  forwardResponse.status >= 200 &&
1470
1471
  forwardResponse.status <= 399
1471
1472
  ) {
@@ -2025,7 +2026,7 @@ async function httpNetworkFetch (
2025
2026
 
2026
2027
  const willFollow = request.redirect === 'follow' &&
2027
2028
  location &&
2028
- redirectStatus.includes(status)
2029
+ redirectStatusSet.has(status)
2029
2030
 
2030
2031
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
2031
2032
  if (request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) {
@@ -13,8 +13,8 @@ const {
13
13
  makePolicyContainer
14
14
  } = require('./util')
15
15
  const {
16
- forbiddenMethods,
17
- corsSafeListedMethods,
16
+ forbiddenMethodsSet,
17
+ corsSafeListedMethodsSet,
18
18
  referrerPolicy,
19
19
  requestRedirect,
20
20
  requestMode,
@@ -319,7 +319,7 @@ class Request {
319
319
  throw TypeError(`'${init.method}' is not a valid HTTP method.`)
320
320
  }
321
321
 
322
- if (forbiddenMethods.indexOf(method.toUpperCase()) !== -1) {
322
+ if (forbiddenMethodsSet.has(method.toUpperCase())) {
323
323
  throw TypeError(`'${init.method}' HTTP method is unsupported.`)
324
324
  }
325
325
 
@@ -404,7 +404,7 @@ class Request {
404
404
  if (mode === 'no-cors') {
405
405
  // 1. If this’s request’s method is not a CORS-safelisted method,
406
406
  // then throw a TypeError.
407
- if (!corsSafeListedMethods.includes(request.method)) {
407
+ if (!corsSafeListedMethodsSet.has(request.method)) {
408
408
  throw new TypeError(
409
409
  `'${request.method} is unsupported in no-cors mode.`
410
410
  )
@@ -14,7 +14,7 @@ const {
14
14
  isomorphicEncode
15
15
  } = require('./util')
16
16
  const {
17
- redirectStatus,
17
+ redirectStatusSet,
18
18
  nullBodyStatus,
19
19
  DOMException
20
20
  } = require('./constants')
@@ -28,6 +28,7 @@ const assert = require('assert')
28
28
  const { types } = require('util')
29
29
 
30
30
  const ReadableStream = globalThis.ReadableStream || require('stream/web').ReadableStream
31
+ const textEncoder = new TextEncoder('utf-8')
31
32
 
32
33
  // https://fetch.spec.whatwg.org/#response-class
33
34
  class Response {
@@ -57,7 +58,7 @@ class Response {
57
58
  }
58
59
 
59
60
  // 1. Let bytes the result of running serialize a JavaScript value to JSON bytes on data.
60
- const bytes = new TextEncoder('utf-8').encode(
61
+ const bytes = textEncoder.encode(
61
62
  serializeJavascriptValueToJSONString(data)
62
63
  )
63
64
 
@@ -102,7 +103,7 @@ class Response {
102
103
  }
103
104
 
104
105
  // 3. If status is not a redirect status, then throw a RangeError.
105
- if (!redirectStatus.includes(status)) {
106
+ if (!redirectStatusSet.has(status)) {
106
107
  throw new RangeError('Invalid status code ' + status)
107
108
  }
108
109
 
package/lib/fetch/util.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { redirectStatus, badPorts, referrerPolicy: referrerPolicyTokens } = require('./constants')
3
+ const { redirectStatusSet, referrerPolicySet: referrerPolicyTokens, badPortsSet } = require('./constants')
4
4
  const { getGlobalOrigin } = require('./global')
5
5
  const { performance } = require('perf_hooks')
6
6
  const { isBlobLike, toUSVString, ReadableStreamFrom } = require('../core/util')
@@ -29,7 +29,7 @@ function responseURL (response) {
29
29
  // https://fetch.spec.whatwg.org/#concept-response-location-url
30
30
  function responseLocationURL (response, requestFragment) {
31
31
  // 1. If response’s status is not a redirect status, then return null.
32
- if (!redirectStatus.includes(response.status)) {
32
+ if (!redirectStatusSet.has(response.status)) {
33
33
  return null
34
34
  }
35
35
 
@@ -64,7 +64,7 @@ function requestBadPort (request) {
64
64
 
65
65
  // 2. If url’s scheme is an HTTP(S) scheme and url’s port is a bad port,
66
66
  // then return blocked.
67
- if (urlIsHttpHttpsScheme(url) && badPorts.includes(url.port)) {
67
+ if (urlIsHttpHttpsScheme(url) && badPortsSet.has(url.port)) {
68
68
  return 'blocked'
69
69
  }
70
70
 
@@ -206,7 +206,7 @@ function setRequestReferrerPolicyOnRedirect (request, actualResponse) {
206
206
  // The left-most policy is the fallback.
207
207
  for (let i = policyHeader.length; i !== 0; i--) {
208
208
  const token = policyHeader[i - 1].trim()
209
- if (referrerPolicyTokens.includes(token)) {
209
+ if (referrerPolicyTokens.has(token)) {
210
210
  policy = token
211
211
  break
212
212
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "5.26.5",
3
+ "version": "5.27.0",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -84,7 +84,7 @@
84
84
  "test:tdd": "tap test/*.js test/diagnostics-channel/*.js -w",
85
85
  "test:typescript": "node scripts/verifyVersion.js 14 || tsd && tsc --skipLibCheck test/imports/undici-import.ts",
86
86
  "test:websocket": "node scripts/verifyVersion.js 18 || tap test/websocket/*.js",
87
- "test:wpt": "node scripts/verifyVersion 18 || (node test/wpt/start-fetch.mjs && node test/wpt/start-FileAPI.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node --no-warnings --expose-internals test/wpt/start-websockets.mjs)",
87
+ "test:wpt": "node scripts/verifyVersion 18 || (node test/wpt/start-fetch.mjs && node test/wpt/start-FileAPI.mjs && node test/wpt/start-mimesniff.mjs && node test/wpt/start-xhr.mjs && node test/wpt/start-websockets.mjs)",
88
88
  "coverage": "nyc --reporter=text --reporter=html npm run test",
89
89
  "coverage:ci": "nyc --reporter=lcov npm run test",
90
90
  "bench": "PORT=3042 concurrently -k -s first npm:bench:server npm:bench:run",