undici 5.16.0 → 5.17.1

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.
@@ -0,0 +1,57 @@
1
+ # MIME Type Parsing
2
+
3
+ ## `MIMEType` interface
4
+
5
+ * **type** `string`
6
+ * **subtype** `string`
7
+ * **parameters** `Map<string, string>`
8
+ * **essence** `string`
9
+
10
+ ## `parseMIMEType(input)`
11
+
12
+ Implements [parse a MIME type](https://mimesniff.spec.whatwg.org/#parse-a-mime-type).
13
+
14
+ Parses a MIME type, returning its type, subtype, and any associated parameters. If the parser can't parse an input it returns the string literal `'failure'`.
15
+
16
+ ```js
17
+ import { parseMIMEType } from 'undici'
18
+
19
+ parseMIMEType('text/html; charset=gbk')
20
+ // {
21
+ // type: 'text',
22
+ // subtype: 'html',
23
+ // parameters: Map(1) { 'charset' => 'gbk' },
24
+ // essence: 'text/html'
25
+ // }
26
+ ```
27
+
28
+ Arguments:
29
+
30
+ * **input** `string`
31
+
32
+ Returns: `MIMEType|'failure'`
33
+
34
+ ## `serializeAMimeType(input)`
35
+
36
+ Implements [serialize a MIME type](https://mimesniff.spec.whatwg.org/#serialize-a-mime-type).
37
+
38
+ Serializes a MIMEType object.
39
+
40
+ ```js
41
+ import { serializeAMimeType } from 'undici'
42
+
43
+ serializeAMimeType({
44
+ type: 'text',
45
+ subtype: 'html',
46
+ parameters: new Map([['charset', 'gbk']]),
47
+ essence: 'text/html'
48
+ })
49
+ // text/html;charset=gbk
50
+
51
+ ```
52
+
53
+ Arguments:
54
+
55
+ * **mimeType** `MIMEType`
56
+
57
+ Returns: `string`
@@ -74,7 +74,7 @@ Returns: `void | Promise<ConnectData>` - Only returns a `Promise` if no `callbac
74
74
  #### Parameter: `ConnectData`
75
75
 
76
76
  * **statusCode** `number`
77
- * **headers** `http.IncomingHttpHeaders`
77
+ * **headers** `Record<string, string | string[]>`
78
78
  * **socket** `stream.Duplex`
79
79
  * **opaque** `unknown`
80
80
 
@@ -383,7 +383,7 @@ Extends: [`RequestOptions`](#parameter-requestoptions)
383
383
  #### Parameter: PipelineHandlerData
384
384
 
385
385
  * **statusCode** `number`
386
- * **headers** `IncomingHttpHeaders`
386
+ * **headers** `Record<string, string | string[]>`
387
387
  * **opaque** `unknown`
388
388
  * **body** `stream.Readable`
389
389
  * **context** `object`
@@ -477,7 +477,7 @@ The `RequestOptions.method` property should not be value `'CONNECT'`.
477
477
  #### Parameter: `ResponseData`
478
478
 
479
479
  * **statusCode** `number`
480
- * **headers** `http.IncomingHttpHeaders` - Note that all header keys are lower-cased, e. g. `content-type`.
480
+ * **headers** `Record<string, string | string[]>` - Note that all header keys are lower-cased, e. g. `content-type`.
481
481
  * **body** `stream.Readable` which also implements [the body mixin from the Fetch Standard](https://fetch.spec.whatwg.org/#body-mixin).
482
482
  * **trailers** `Record<string, string>` - This object starts out
483
483
  as empty and will be mutated to contain trailers after `body` has emitted `'end'`.
@@ -631,7 +631,7 @@ try {
631
631
 
632
632
  A faster version of `Dispatcher.request`. This method expects the second argument `factory` to return a [`stream.Writable`](https://nodejs.org/api/stream.html#stream_class_stream_writable) stream which the response will be written to. This improves performance by avoiding creating an intermediate [`stream.Readable`](https://nodejs.org/api/stream.html#stream_readable_streams) stream when the user expects to directly pipe the response body to a [`stream.Writable`](https://nodejs.org/api/stream.html#stream_class_stream_writable) stream.
633
633
 
634
- As demonstrated in [Example 1 - Basic GET stream request](#example-1-basic-get-stream-request), it is recommended to use the `option.opaque` property to avoid creating a closure for the `factory` method. This pattern works well with Node.js Web Frameworks such as [Fastify](https://fastify.io). See [Example 2 - Stream to Fastify Response](#example-2-stream-to-fastify-response) for more details.
634
+ As demonstrated in [Example 1 - Basic GET stream request](#example-1---basic-get-stream-request), it is recommended to use the `option.opaque` property to avoid creating a closure for the `factory` method. This pattern works well with Node.js Web Frameworks such as [Fastify](https://fastify.io). See [Example 2 - Stream to Fastify Response](#example-2---stream-to-fastify-response) for more details.
635
635
 
636
636
  Arguments:
637
637
 
@@ -644,7 +644,7 @@ Returns: `void | Promise<StreamData>` - Only returns a `Promise` if no `callback
644
644
  #### Parameter: `StreamFactoryData`
645
645
 
646
646
  * **statusCode** `number`
647
- * **headers** `http.IncomingHttpHeaders`
647
+ * **headers** `Record<string, string | string[]>`
648
648
  * **opaque** `unknown`
649
649
  * **onInfo** `({statusCode: number, headers: Record<string, string | string[]>}) => void | null` (optional) - Default: `null` - Callback collecting all the info headers (HTTP 100-199) received.
650
650
 
@@ -853,9 +853,9 @@ Emitted when dispatcher is no longer busy.
853
853
 
854
854
  ## Parameter: `UndiciHeaders`
855
855
 
856
- * `http.IncomingHttpHeaders | string[] | null`
856
+ * `Record<string, string | string[]> | string[] | null`
857
857
 
858
- Header arguments such as `options.headers` in [`Client.dispatch`](Client.md#clientdispatchoptions-handlers) can be specified in two forms; either as an object specified by the `http.IncomingHttpHeaders` type, or an array of strings. An array representation of a header list must have an even length or an `InvalidArgumentError` will be thrown.
858
+ Header arguments such as `options.headers` in [`Client.dispatch`](Client.md#clientdispatchoptions-handlers) can be specified in two forms; either as an object specified by the `Record<string, string | string[]>` (`IncomingHttpHeaders`) type, or an array of strings. An array representation of a header list must have an even length or an `InvalidArgumentError` will be thrown.
859
859
 
860
860
  Keys are lowercase and values are not modified.
861
861
 
package/index.d.ts CHANGED
@@ -23,6 +23,7 @@ export * from './types/filereader'
23
23
  export * from './types/formdata'
24
24
  export * from './types/diagnostics-channel'
25
25
  export * from './types/websocket'
26
+ export * from './types/content-type'
26
27
  export { Interceptable } from './types/mock-interceptor'
27
28
 
28
29
  export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, setGlobalOrigin, getGlobalOrigin, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent, RedirectHandler, DecoratorHandler }
package/index.js CHANGED
@@ -134,6 +134,11 @@ if (nodeMajor >= 16) {
134
134
  module.exports.getCookies = getCookies
135
135
  module.exports.getSetCookies = getSetCookies
136
136
  module.exports.setCookie = setCookie
137
+
138
+ const { parseMIMEType, serializeAMimeType } = require('./lib/fetch/dataURL')
139
+
140
+ module.exports.parseMIMEType = parseMIMEType
141
+ module.exports.serializeAMimeType = serializeAMimeType
137
142
  }
138
143
 
139
144
  if (nodeMajor >= 18 && hasCrypto) {
package/lib/client.js CHANGED
@@ -65,6 +65,7 @@ const {
65
65
  kLocalAddress,
66
66
  kMaxResponseSize
67
67
  } = require('./core/symbols')
68
+ const FastBuffer = Buffer[Symbol.species]
68
69
 
69
70
  const kClosedResolve = Symbol('kClosedResolve')
70
71
 
@@ -362,9 +363,8 @@ async function lazyllhttp () {
362
363
  },
363
364
  wasm_on_status: (p, at, len) => {
364
365
  assert.strictEqual(currentParser.ptr, p)
365
- const start = at - currentBufferPtr
366
- const end = start + len
367
- return currentParser.onStatus(currentBufferRef.slice(start, end)) || 0
366
+ const start = at - currentBufferPtr + currentBufferRef.byteOffset
367
+ return currentParser.onStatus(new FastBuffer(currentBufferRef.buffer, start, len)) || 0
368
368
  },
369
369
  wasm_on_message_begin: (p) => {
370
370
  assert.strictEqual(currentParser.ptr, p)
@@ -372,15 +372,13 @@ async function lazyllhttp () {
372
372
  },
373
373
  wasm_on_header_field: (p, at, len) => {
374
374
  assert.strictEqual(currentParser.ptr, p)
375
- const start = at - currentBufferPtr
376
- const end = start + len
377
- return currentParser.onHeaderField(currentBufferRef.slice(start, end)) || 0
375
+ const start = at - currentBufferPtr + currentBufferRef.byteOffset
376
+ return currentParser.onHeaderField(new FastBuffer(currentBufferRef.buffer, start, len)) || 0
378
377
  },
379
378
  wasm_on_header_value: (p, at, len) => {
380
379
  assert.strictEqual(currentParser.ptr, p)
381
- const start = at - currentBufferPtr
382
- const end = start + len
383
- return currentParser.onHeaderValue(currentBufferRef.slice(start, end)) || 0
380
+ const start = at - currentBufferPtr + currentBufferRef.byteOffset
381
+ return currentParser.onHeaderValue(new FastBuffer(currentBufferRef.buffer, start, len)) || 0
384
382
  },
385
383
  wasm_on_headers_complete: (p, statusCode, upgrade, shouldKeepAlive) => {
386
384
  assert.strictEqual(currentParser.ptr, p)
@@ -388,9 +386,8 @@ async function lazyllhttp () {
388
386
  },
389
387
  wasm_on_body: (p, at, len) => {
390
388
  assert.strictEqual(currentParser.ptr, p)
391
- const start = at - currentBufferPtr
392
- const end = start + len
393
- return currentParser.onBody(currentBufferRef.slice(start, end)) || 0
389
+ const start = at - currentBufferPtr + currentBufferRef.byteOffset
390
+ return currentParser.onBody(new FastBuffer(currentBufferRef.buffer, start, len)) || 0
394
391
  },
395
392
  wasm_on_message_complete: (p) => {
396
393
  assert.strictEqual(currentParser.ptr, p)
@@ -284,6 +284,7 @@ function processHeaderValue (key, val) {
284
284
  } else if (headerCharRegex.exec(val) !== null) {
285
285
  throw new InvalidArgumentError(`invalid ${key} header`)
286
286
  }
287
+
287
288
  return `${key}: ${val}\r\n`
288
289
  }
289
290
 
@@ -313,11 +314,10 @@ function processHeader (request, key, val) {
313
314
  } else if (
314
315
  request.contentType === null &&
315
316
  key.length === 12 &&
316
- key.toLowerCase() === 'content-type' &&
317
- headerCharRegex.exec(val) === null
317
+ key.toLowerCase() === 'content-type'
318
318
  ) {
319
319
  request.contentType = val
320
- request.headers += `${key}: ${val}\r\n`
320
+ request.headers += processHeaderValue(key, val)
321
321
  } else if (
322
322
  key.length === 17 &&
323
323
  key.toLowerCase() === 'transfer-encoding'
@@ -327,7 +327,7 @@ function processHeader (request, key, val) {
327
327
  key.length === 10 &&
328
328
  key.toLowerCase() === 'connection'
329
329
  ) {
330
- const value = val.toLowerCase()
330
+ const value = typeof val === 'string' ? val.toLowerCase() : null
331
331
  if (value !== 'close' && value !== 'keep-alive') {
332
332
  throw new InvalidArgumentError('invalid connection header')
333
333
  } else if (value === 'close') {
@@ -31,8 +31,8 @@ function dataURLProcessor (dataURL) {
31
31
  // 5. Let mimeType be the result of collecting a
32
32
  // sequence of code points that are not equal
33
33
  // to U+002C (,), given position.
34
- let mimeType = collectASequenceOfCodePoints(
35
- (char) => char !== ',',
34
+ let mimeType = collectASequenceOfCodePointsFast(
35
+ ',',
36
36
  input,
37
37
  position
38
38
  )
@@ -145,6 +145,25 @@ function collectASequenceOfCodePoints (condition, input, position) {
145
145
  return result
146
146
  }
147
147
 
148
+ /**
149
+ * A faster collectASequenceOfCodePoints that only works when comparing a single character.
150
+ * @param {string} char
151
+ * @param {string} input
152
+ * @param {{ position: number }} position
153
+ */
154
+ function collectASequenceOfCodePointsFast (char, input, position) {
155
+ const idx = input.indexOf(char, position.position)
156
+ const start = position.position
157
+
158
+ if (idx === -1) {
159
+ position.position = input.length
160
+ return input.slice(start)
161
+ }
162
+
163
+ position.position = idx
164
+ return input.slice(start, position.position)
165
+ }
166
+
148
167
  // https://url.spec.whatwg.org/#string-percent-decode
149
168
  /** @param {string} input */
150
169
  function stringPercentDecode (input) {
@@ -214,8 +233,8 @@ function parseMIMEType (input) {
214
233
  // 3. Let type be the result of collecting a sequence
215
234
  // of code points that are not U+002F (/) from
216
235
  // input, given position.
217
- const type = collectASequenceOfCodePoints(
218
- (char) => char !== '/',
236
+ const type = collectASequenceOfCodePointsFast(
237
+ '/',
219
238
  input,
220
239
  position
221
240
  )
@@ -239,8 +258,8 @@ function parseMIMEType (input) {
239
258
  // 7. Let subtype be the result of collecting a sequence of
240
259
  // code points that are not U+003B (;) from input, given
241
260
  // position.
242
- let subtype = collectASequenceOfCodePoints(
243
- (char) => char !== ';',
261
+ let subtype = collectASequenceOfCodePointsFast(
262
+ ';',
244
263
  input,
245
264
  position
246
265
  )
@@ -324,8 +343,8 @@ function parseMIMEType (input) {
324
343
 
325
344
  // 2. Collect a sequence of code points that are not
326
345
  // U+003B (;) from input, given position.
327
- collectASequenceOfCodePoints(
328
- (char) => char !== ';',
346
+ collectASequenceOfCodePointsFast(
347
+ ';',
329
348
  input,
330
349
  position
331
350
  )
@@ -335,8 +354,8 @@ function parseMIMEType (input) {
335
354
  // 1. Set parameterValue to the result of collecting
336
355
  // a sequence of code points that are not U+003B (;)
337
356
  // from input, given position.
338
- parameterValue = collectASequenceOfCodePoints(
339
- (char) => char !== ';',
357
+ parameterValue = collectASequenceOfCodePointsFast(
358
+ ';',
340
359
  input,
341
360
  position
342
361
  )
@@ -104,7 +104,11 @@ class HeadersList {
104
104
 
105
105
  // 2. Append (name, value) to list.
106
106
  if (exists) {
107
- this[kHeadersMap].set(lowercaseName, { name: exists.name, value: `${exists.value}, ${value}` })
107
+ const delimiter = lowercaseName === 'cookie' ? '; ' : ', '
108
+ this[kHeadersMap].set(lowercaseName, {
109
+ name: exists.name,
110
+ value: `${exists.value}${delimiter}${value}`
111
+ })
108
112
  } else {
109
113
  this[kHeadersMap].set(lowercaseName, { name, value })
110
114
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "5.16.0",
3
+ "version": "5.17.1",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -55,7 +55,7 @@
55
55
  "test:tdd": "tap test/*.js test/diagnostics-channel/*.js -w",
56
56
  "test:typescript": "tsd && tsc test/imports/undici-import.ts",
57
57
  "test:websocket": "node scripts/verifyVersion.js 18 || tap test/websocket/*.js",
58
- "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)",
58
+ "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 test/wpt/start-websockets.mjs)",
59
59
  "coverage": "nyc --reporter=text --reporter=html npm run test",
60
60
  "coverage:ci": "nyc --reporter=lcov npm run test",
61
61
  "bench": "PORT=3042 concurrently -k -s first npm:bench:server npm:bench:run",
@@ -0,0 +1,21 @@
1
+ /// <reference types="node" />
2
+
3
+ interface MIMEType {
4
+ type: string
5
+ subtype: string
6
+ parameters: Map<string, string>
7
+ essence: string
8
+ }
9
+
10
+ /**
11
+ * Parse a string to a {@link MIMEType} object. Returns `failure` if the string
12
+ * couldn't be parsed.
13
+ * @see https://mimesniff.spec.whatwg.org/#parse-a-mime-type
14
+ */
15
+ export function parseMIMEType (input: string): 'failure' | MIMEType
16
+
17
+ /**
18
+ * Convert a MIMEType object to a string.
19
+ * @see https://mimesniff.spec.whatwg.org/#serialize-a-mime-type
20
+ */
21
+ export function serializeAMimeType (mimeType: MIMEType): string
@@ -1,8 +1,8 @@
1
1
  import { URL } from 'url'
2
2
  import { Duplex, Readable, Writable } from 'stream'
3
3
  import { EventEmitter } from 'events'
4
- import { IncomingHttpHeaders } from 'http'
5
4
  import { Blob } from 'buffer'
5
+ import { IncomingHttpHeaders } from './header'
6
6
  import BodyReadable from './readable'
7
7
  import { FormData } from './formdata'
8
8
  import Errors from './errors'
@@ -105,6 +105,8 @@ declare namespace Dispatcher {
105
105
  query?: Record<string, any>;
106
106
  /** Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceding requests in the pipeline have completed. Default: `true` if `method` is `HEAD` or `GET`. */
107
107
  idempotent?: boolean;
108
+ /** Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received. */
109
+ blocking?: boolean;
108
110
  /** Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`. Default: `method === 'CONNECT' || null`. */
109
111
  upgrade?: boolean | string | null;
110
112
  /** The amount of time the parser will wait to receive the complete HTTP headers. Defaults to 30 seconds. */
package/types/errors.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {IncomingHttpHeaders} from "http";
1
+ import { IncomingHttpHeaders } from "./header";
2
2
  import Client from './client'
3
3
 
4
4
  export default Errors
@@ -6,12 +6,24 @@ export default Errors
6
6
  declare namespace Errors {
7
7
  export class UndiciError extends Error { }
8
8
 
9
+ /** Connect timeout error. */
10
+ export class ConnectTimeoutError extends UndiciError {
11
+ name: 'ConnectTimeoutError';
12
+ code: 'UND_ERR_CONNECT_TIMEOUT';
13
+ }
14
+
9
15
  /** A header exceeds the `headersTimeout` option. */
10
16
  export class HeadersTimeoutError extends UndiciError {
11
17
  name: 'HeadersTimeoutError';
12
18
  code: 'UND_ERR_HEADERS_TIMEOUT';
13
19
  }
14
20
 
21
+ /** Headers overflow error. */
22
+ export class HeadersOverflowError extends UndiciError {
23
+ name: 'HeadersOverflowError'
24
+ code: 'UND_ERR_HEADERS_OVERFLOW'
25
+ }
26
+
15
27
  /** A body exceeds the `bodyTimeout` option. */
16
28
  export class BodyTimeoutError extends UndiciError {
17
29
  name: 'BodyTimeoutError';
@@ -27,12 +39,6 @@ declare namespace Errors {
27
39
  headers: IncomingHttpHeaders | string[] | null;
28
40
  }
29
41
 
30
- /** A socket exceeds the `socketTimeout` option. */
31
- export class SocketTimeoutError extends UndiciError {
32
- name: 'SocketTimeoutError';
33
- code: 'UND_ERR_SOCKET_TIMEOUT';
34
- }
35
-
36
42
  /** Passed an invalid argument. */
37
43
  export class InvalidArgumentError extends UndiciError {
38
44
  name: 'InvalidArgumentError';
@@ -40,14 +46,14 @@ declare namespace Errors {
40
46
  }
41
47
 
42
48
  /** Returned an invalid value. */
43
- export class InvalidReturnError extends UndiciError {
44
- name: 'InvalidReturnError';
49
+ export class InvalidReturnValueError extends UndiciError {
50
+ name: 'InvalidReturnValueError';
45
51
  code: 'UND_ERR_INVALID_RETURN_VALUE';
46
52
  }
47
53
 
48
54
  /** The request has been aborted by the user. */
49
55
  export class RequestAbortedError extends UndiciError {
50
- name: 'RequestAbortedError';
56
+ name: 'AbortError';
51
57
  code: 'UND_ERR_ABORTED';
52
58
  }
53
59
 
@@ -57,12 +63,18 @@ declare namespace Errors {
57
63
  code: 'UND_ERR_INFO';
58
64
  }
59
65
 
60
- /** Body does not match content-length header. */
66
+ /** Request body length does not match content-length header. */
61
67
  export class RequestContentLengthMismatchError extends UndiciError {
62
68
  name: 'RequestContentLengthMismatchError';
63
69
  code: 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH';
64
70
  }
65
71
 
72
+ /** Response body length does not match content-length header. */
73
+ export class ResponseContentLengthMismatchError extends UndiciError {
74
+ name: 'ResponseContentLengthMismatchError';
75
+ code: 'UND_ERR_RES_CONTENT_LENGTH_MISMATCH';
76
+ }
77
+
66
78
  /** Trying to use a destroyed client. */
67
79
  export class ClientDestroyedError extends UndiciError {
68
80
  name: 'ClientDestroyedError';
@@ -88,7 +100,18 @@ declare namespace Errors {
88
100
  code: 'UND_ERR_NOT_SUPPORTED';
89
101
  }
90
102
 
91
- /** The response exceed the length allowed */
103
+ /** No upstream has been added to the BalancedPool. */
104
+ export class BalancedPoolMissingUpstreamError extends UndiciError {
105
+ name: 'MissingUpstreamError';
106
+ code: 'UND_ERR_BPL_MISSING_UPSTREAM';
107
+ }
108
+
109
+ export class HTTPParserError extends UndiciError {
110
+ name: 'HTTPParserError';
111
+ code: string;
112
+ }
113
+
114
+ /** The response exceed the length allowed. */
92
115
  export class ResponseExceededMaxSizeError extends UndiciError {
93
116
  name: 'ResponseExceededMaxSizeError';
94
117
  code: 'UND_ERR_RES_EXCEEDED_MAX_SIZE';
@@ -0,0 +1,4 @@
1
+ /**
2
+ * The header type declaration of `undici`.
3
+ */
4
+ export type IncomingHttpHeaders = Record<string, string | string[]>;
@@ -1,4 +1,4 @@
1
- import { IncomingHttpHeaders } from 'http'
1
+ import { IncomingHttpHeaders } from './header'
2
2
  import Dispatcher from './dispatcher';
3
3
  import { BodyInit, Headers } from './fetch'
4
4
 
@@ -1,8 +1,7 @@
1
- import { IncomingHttpHeaders } from 'http'
2
-
3
1
  import Agent from './agent'
4
2
  import buildConnector from './connector';
5
3
  import Dispatcher from './dispatcher'
4
+ import { IncomingHttpHeaders } from './header'
6
5
 
7
6
  export default ProxyAgent
8
7