| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 85f88c6 commit 2497216
17 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -405,6 +405,18 @@ implementations in Deno and Cloudflare Workers. | |||
| 405 | 405 | ||
| 406 | 406 | Refs: https://fetch.spec.whatwg.org/#atomic-http-redirect-handling | |
| 407 | 407 | ||
| 408 | + ## Workarounds | ||
| 409 | + | ||
| 410 | + ### Network address family autoselection. | ||
| 411 | + | ||
| 412 | + If you experience problem when connecting to a remote server that is resolved by your DNS servers to a IPv6 (AAAA record) | ||
| 413 | + first, there are chances that your local router or ISP might have problem connecting to IPv6 networks. In that case | ||
| 414 | + undici will throw an error with code `UND_ERR_CONNECT_TIMEOUT`. | ||
| 415 | + | ||
| 416 | + If the target server resolves to both a IPv6 and IPv4 (A records) address and you are using a compatible Node version | ||
| 417 | + (18.3.0 and above), you can fix the problem by providing the `autoSelectFamily` option (support by both `undici.request` | ||
| 418 | + and `undici.Agent`) which will enable the family autoselection algorithm when establishing the connection. | ||
| 419 | + | ||
| 408 | 420 | ## Collaborators | |
| 409 | 421 | ||
| 410 | 422 | * [__Daniele Belardi__](https://github.com/dnlup), <https://www.npmjs.com/~dnlup> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -17,8 +17,8 @@ Returns: `Client` | |||
| 17 | 17 | ||
| 18 | 18 | ### Parameter: `ClientOptions` | |
| 19 | 19 | ||
| 20 | - * **bodyTimeout** `number | null` (optional) - Default: `30e3` - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 30 seconds. | ||
| 21 | - * **headersTimeout** `number | null` (optional) - Default: `30e3` - The amount of time the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 30 seconds. | ||
| 20 | + * **bodyTimeout** `number | null` (optional) - Default: `300e3` - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 300 seconds. | ||
| 21 | + * **headersTimeout** `number | null` (optional) - Default: `300e3` - The amount of time the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 300 seconds. | ||
| 22 | 22 | * **keepAliveMaxTimeout** `number | null` (optional) - Default: `600e3` - The maximum allowed `keepAliveTimeout` when overridden by *keep-alive* hints from the server. Defaults to 10 minutes. | |
| 23 | 23 | * **keepAliveTimeout** `number | null` (optional) - Default: `4e3` - The timeout after which a socket without active requests will time out. Monitors time between activity on a connected socket. This value may be overridden by *keep-alive* hints from the server. See [MDN: HTTP - Headers - Keep-Alive directives](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Keep-Alive#directives) for more details. Defaults to 4 seconds. | |
| 24 | 24 | * **keepAliveTimeoutThreshold** `number | null` (optional) - Default: `1e3` - A number subtracted from server *keep-alive* hints when overriding `keepAliveTimeout` to account for timing inaccuracies caused by e.g. transport latency. Defaults to 1 second. | |
@@ -28,6 +28,8 @@ Returns: `Client` | |||
| 28 | 28 | * **connect** `ConnectOptions | Function | null` (optional) - Default: `null`. | |
| 29 | 29 | * **strictContentLength** `Boolean` (optional) - Default: `true` - Whether to treat request content length mismatches as errors. If true, an error is thrown when the request content-length header doesn't match the length of the request body. | |
| 30 | 30 | * **interceptors** `{ Client: DispatchInterceptor[] }` - Default: `[RedirectInterceptor]` - A list of interceptors that are applied to the dispatch method. Additional logic can be applied (such as, but not limited to: 302 status code handling, authentication, cookies, compression and caching). Note that the behavior of interceptors is Experimental and might change at any given time. | |
| 31 | + * **autoSelectFamily**: `boolean` (optional) - Default: depends on local Node version, on Node 18.13.0 and above is `false`. Enables a family autodetection algorithm that loosely implements section 5 of [RFC 8305](https://tools.ietf.org/html/rfc8305#section-5). See [here](https://nodejs.org/api/net.html#socketconnectoptions-connectlistener) for more details. This option is ignored if not supported by the current Node version. | ||
| 32 | + * **autoSelectFamilyAttemptTimeout**: `number` - Default: depends on local Node version, on Node 18.13.0 and above is `250`. The amount of time in milliseconds to wait for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option. See [here](https://nodejs.org/api/net.html#socketconnectoptions-connectlistener) for more details. | ||
| 31 | 33 | ||
| 32 | 34 | #### Parameter: `ConnectOptions` | |
| 33 | 35 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -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** `Record<string, string | string[]>` | ||
| 77 | + * **headers** `Record<string, string | string[] | undefined>` | ||
| 78 | 78 | * **socket** `stream.Duplex` | |
| 79 | 79 | * **opaque** `unknown` | |
| 80 | 80 | ||
@@ -199,8 +199,8 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo | |||
| 199 | 199 | * **idempotent** `boolean` (optional) - Default: `true` if `method` is `'HEAD'` or `'GET'` - Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceding requests in the pipeline has completed. | |
| 200 | 200 | * **blocking** `boolean` (optional) - Default: `false` - 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. | |
| 201 | 201 | * **upgrade** `string | null` (optional) - Default: `null` - Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`. | |
| 202 | - * **bodyTimeout** `number | null` (optional) - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 30 seconds. | ||
| 203 | - * **headersTimeout** `number | null` (optional) - The amount of time the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 30 seconds. | ||
| 202 | + * **bodyTimeout** `number | null` (optional) - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 300 seconds. | ||
| 203 | + * **headersTimeout** `number | null` (optional) - The amount of time the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 300 seconds. | ||
| 204 | 204 | * **throwOnError** `boolean` (optional) - Default: `false` - Whether Undici should throw an error upon receiving a 4xx or 5xx response from the server. | |
| 205 | 205 | ||
| 206 | 206 | #### Parameter: `DispatchHandler` | |
@@ -383,7 +383,7 @@ Extends: [`RequestOptions`](#parameter-requestoptions) | |||
| 383 | 383 | #### Parameter: PipelineHandlerData | |
| 384 | 384 | ||
| 385 | 385 | * **statusCode** `number` | |
| 386 | - * **headers** `Record<string, string | string[]>` | ||
| 386 | + * **headers** `Record<string, string | string[] | undefined>` | ||
| 387 | 387 | * **opaque** `unknown` | |
| 388 | 388 | * **body** `stream.Readable` | |
| 389 | 389 | * **context** `object` | |
@@ -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** `Record<string, string | string[]>` | ||
| 647 | + * **headers** `Record<string, string | string[] | undefined>` | ||
| 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 | - * `Record<string, string | string[]> | string[] | null` | ||
| 856 | + * `Record<string, string | string[] | undefined> | 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 `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. | ||
| 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[] | undefined>` (`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 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -20,10 +20,6 @@ const DecoratorHandler = require('./lib/handler/DecoratorHandler') | |||
| 20 | 20 | const RedirectHandler = require('./lib/handler/RedirectHandler') | |
| 21 | 21 | const createRedirectInterceptor = require('./lib/interceptor/redirectInterceptor') | |
| 22 | 22 | ||
| 23 | - const nodeVersion = process.versions.node.split('.') | ||
| 24 | - const nodeMajor = Number(nodeVersion[0]) | ||
| 25 | - const nodeMinor = Number(nodeVersion[1]) | ||
| 26 | - | ||
| 27 | 23 | let hasCrypto | |
| 28 | 24 | try { | |
| 29 | 25 | require('crypto') | |
@@ -100,7 +96,7 @@ function makeDispatcher (fn) { | |||
| 100 | 96 | module.exports.setGlobalDispatcher = setGlobalDispatcher | |
| 101 | 97 | module.exports.getGlobalDispatcher = getGlobalDispatcher | |
| 102 | 98 | ||
| 103 | - if (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >= 8)) { | ||
| 99 | + if (util.nodeMajor > 16 || (util.nodeMajor === 16 && util.nodeMinor >= 8)) { | ||
| 104 | 100 | let fetchImpl = null | |
| 105 | 101 | module.exports.fetch = async function fetch (resource) { | |
| 106 | 102 | if (!fetchImpl) { | |
@@ -127,7 +123,7 @@ if (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >= 8)) { | |||
| 127 | 123 | module.exports.getGlobalOrigin = getGlobalOrigin | |
| 128 | 124 | } | |
| 129 | 125 | ||
| 130 | - if (nodeMajor >= 16) { | ||
| 126 | + if (util.nodeMajor >= 16) { | ||
| 131 | 127 | const { deleteCookie, getCookies, getSetCookies, setCookie } = require('./lib/cookies') | |
| 132 | 128 | ||
| 133 | 129 | module.exports.deleteCookie = deleteCookie | |
@@ -141,7 +137,7 @@ if (nodeMajor >= 16) { | |||
| 141 | 137 | module.exports.serializeAMimeType = serializeAMimeType | |
| 142 | 138 | } | |
| 143 | 139 | ||
| 144 | - if (nodeMajor >= 18 && hasCrypto) { | ||
| 140 | + if (util.nodeMajor >= 18 && hasCrypto) { | ||
| 145 | 141 | const { WebSocket } = require('./lib/websocket/websocket') | |
| 146 | 142 | ||
| 147 | 143 | module.exports.WebSocket = WebSocket | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -109,7 +109,9 @@ class Client extends DispatcherBase { | |||
| 109 | 109 | connect, | |
| 110 | 110 | maxRequestsPerClient, | |
| 111 | 111 | localAddress, | |
| 112 | - maxResponseSize | ||
| 112 | + maxResponseSize, | ||
| 113 | + autoSelectFamily, | ||
| 114 | + autoSelectFamilyAttemptTimeout | ||
| 113 | 115 | } = {}) { | |
| 114 | 116 | super() | |
| 115 | 117 | ||
@@ -185,12 +187,20 @@ class Client extends DispatcherBase { | |||
| 185 | 187 | throw new InvalidArgumentError('maxResponseSize must be a positive number') | |
| 186 | 188 | } | |
| 187 | 189 | ||
| 190 | + if ( | ||
| 191 | + autoSelectFamilyAttemptTimeout != null && | ||
| 192 | + (!Number.isInteger(autoSelectFamilyAttemptTimeout) || autoSelectFamilyAttemptTimeout < -1) | ||
| 193 | + ) { | ||
| 194 | + throw new InvalidArgumentError('autoSelectFamilyAttemptTimeout must be a positive number') | ||
| 195 | + } | ||
| 196 | + | ||
| 188 | 197 | if (typeof connect !== 'function') { | |
| 189 | 198 | connect = buildConnector({ | |
| 190 | 199 | ...tls, | |
| 191 | 200 | maxCachedSessions, | |
| 192 | 201 | socketPath, | |
| 193 | 202 | timeout: connectTimeout, | |
| 203 | + ...(util.nodeHasAutoSelectFamily && autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined), | ||
| 194 | 204 | ...connect | |
| 195 | 205 | }) | |
| 196 | 206 | } | |
@@ -212,8 +222,8 @@ class Client extends DispatcherBase { | |||
| 212 | 222 | this[kResuming] = 0 // 0, idle, 1, scheduled, 2 resuming | |
| 213 | 223 | this[kNeedDrain] = 0 // 0, idle, 1, scheduled, 2 resuming | |
| 214 | 224 | this[kHostHeader] = `host: ${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ''}\r\n` | |
| 215 | - this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout : 30e3 | ||
| 216 | - this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 30e3 | ||
| 225 | + this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout : 300e3 | ||
| 226 | + this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 300e3 | ||
| 217 | 227 | this[kStrictContentLength] = strictContentLength == null ? true : strictContentLength | |
| 218 | 228 | this[kMaxRedirections] = maxRedirections | |
| 219 | 229 | this[kMaxRequests] = maxRequestsPerClient | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,7 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | 3 | const { maxNameValuePairSize, maxAttributeValueSize } = require('./constants') | |
| 4 | 4 | const { isCTLExcludingHtab } = require('./util') | |
| 5 | - const { collectASequenceOfCodePoints } = require('../fetch/dataURL') | ||
| 5 | + const { collectASequenceOfCodePointsFast } = require('../fetch/dataURL') | ||
| 6 | 6 | const assert = require('assert') | |
| 7 | 7 | ||
| 8 | 8 | /** | |
@@ -32,7 +32,7 @@ function parseSetCookie (header) { | |||
| 32 | 32 | // (including the %x3B (";") in question). | |
| 33 | 33 | const position = { position: 0 } | |
| 34 | 34 | ||
| 35 | - nameValuePair = collectASequenceOfCodePoints((char) => char !== ';', header, position) | ||
| 35 | + nameValuePair = collectASequenceOfCodePointsFast(';', header, position) | ||
| 36 | 36 | unparsedAttributes = header.slice(position.position) | |
| 37 | 37 | } else { | |
| 38 | 38 | // Otherwise: | |
@@ -54,8 +54,8 @@ function parseSetCookie (header) { | |||
| 54 | 54 | // empty) value string consists of the characters after the first | |
| 55 | 55 | // %x3D ("=") character. | |
| 56 | 56 | const position = { position: 0 } | |
| 57 | - name = collectASequenceOfCodePoints( | ||
| 58 | - (char) => char !== '=', | ||
| 57 | + name = collectASequenceOfCodePointsFast( | ||
| 58 | + '=', | ||
| 59 | 59 | nameValuePair, | |
| 60 | 60 | position | |
| 61 | 61 | ) | |
@@ -106,8 +106,8 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {}) | |||
| 106 | 106 | if (unparsedAttributes.includes(';')) { | |
| 107 | 107 | // 1. Consume the characters of the unparsed-attributes up to, but | |
| 108 | 108 | // not including, the first %x3B (";") character. | |
| 109 | - cookieAv = collectASequenceOfCodePoints( | ||
| 110 | - (char) => char !== ';', | ||
| 109 | + cookieAv = collectASequenceOfCodePointsFast( | ||
| 110 | + ';', | ||
| 111 | 111 | unparsedAttributes, | |
| 112 | 112 | { position: 0 } | |
| 113 | 113 | ) | |
@@ -134,8 +134,8 @@ function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {}) | |||
| 134 | 134 | // character. | |
| 135 | 135 | const position = { position: 0 } | |
| 136 | 136 | ||
| 137 | - attributeName = collectASequenceOfCodePoints( | ||
| 138 | - (char) => char !== '=', | ||
| 137 | + attributeName = collectASequenceOfCodePointsFast( | ||
| 138 | + '=', | ||
| 139 | 139 | cookieAv, | |
| 140 | 140 | position | |
| 141 | 141 | ) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -34,10 +34,6 @@ const channels = {} | |||
| 34 | 34 | ||
| 35 | 35 | let extractBody | |
| 36 | 36 | ||
| 37 | - const nodeVersion = process.versions.node.split('.') | ||
| 38 | - const nodeMajor = Number(nodeVersion[0]) | ||
| 39 | - const nodeMinor = Number(nodeVersion[1]) | ||
| 40 | - | ||
| 41 | 37 | try { | |
| 42 | 38 | const diagnosticsChannel = require('diagnostics_channel') | |
| 43 | 39 | channels.create = diagnosticsChannel.channel('undici:request:create') | |
@@ -172,7 +168,7 @@ class Request { | |||
| 172 | 168 | } | |
| 173 | 169 | ||
| 174 | 170 | if (util.isFormDataLike(this.body)) { | |
| 175 | - if (nodeMajor < 16 || (nodeMajor === 16 && nodeMinor < 8)) { | ||
| 171 | + if (util.nodeMajor < 16 || (util.nodeMajor === 16 && util.nodeMinor < 8)) { | ||
| 176 | 172 | throw new InvalidArgumentError('Form-Data bodies are only supported in node v16.8 and newer.') | |
| 177 | 173 | } | |
| 178 | 174 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,6 +10,8 @@ const { Blob } = require('buffer') | |||
| 10 | 10 | const nodeUtil = require('util') | |
| 11 | 11 | const { stringify } = require('querystring') | |
| 12 | 12 | ||
| 13 | + const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v)) | ||
| 14 | + | ||
| 13 | 15 | function nop () {} | |
| 14 | 16 | ||
| 15 | 17 | function isStream (obj) { | |
@@ -420,5 +422,8 @@ module.exports = { | |||
| 420 | 422 | validateHandler, | |
| 421 | 423 | getSocketInfo, | |
| 422 | 424 | isFormDataLike, | |
| 423 | - buildURL | ||
| 425 | + buildURL, | ||
| 426 | + nodeMajor, | ||
| 427 | + nodeMinor, | ||
| 428 | + nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13) | ||
| 424 | 429 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -556,6 +556,7 @@ module.exports = { | |||
| 556 | 556 | dataURLProcessor, | |
| 557 | 557 | URLSerializer, | |
| 558 | 558 | collectASequenceOfCodePoints, | |
| 559 | + collectASequenceOfCodePointsFast, | ||
| 559 | 560 | stringPercentDecode, | |
| 560 | 561 | parseMIMEType, | |
| 561 | 562 | collectAnHTTPQuotedString, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -75,6 +75,7 @@ class HeadersList { | |||
| 75 | 75 | if (init instanceof HeadersList) { | |
| 76 | 76 | this[kHeadersMap] = new Map(init[kHeadersMap]) | |
| 77 | 77 | this[kHeadersSortedMap] = init[kHeadersSortedMap] | |
| 78 | + this.cookies = init.cookies | ||
| 78 | 79 | } else { | |
| 79 | 80 | this[kHeadersMap] = new Map(init) | |
| 80 | 81 | this[kHeadersSortedMap] = null | |
| Back | FazBrowse Home | New Git URL |
0 commit comments