| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 6413769 commit 96f72ae
53 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -23,7 +23,7 @@ Returns: `Client` | |||
| 23 | 23 | * **headersTimeout** `number | null` (optional) - Default: `300e3` - The amount of time, in milliseconds, the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 300 seconds. | |
| 24 | 24 | * **keepAliveMaxTimeout** `number | null` (optional) - Default: `600e3` - The maximum allowed `keepAliveTimeout`, in milliseconds, when overridden by *keep-alive* hints from the server. Defaults to 10 minutes. | |
| 25 | 25 | * **keepAliveTimeout** `number | null` (optional) - Default: `4e3` - The timeout, in milliseconds, 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. | |
| 26 | - * **keepAliveTimeoutThreshold** `number | null` (optional) - Default: `1e3` - A number of milliseconds subtracted from server *keep-alive* hints when overriding `keepAliveTimeout` to account for timing inaccuracies caused by e.g. transport latency. Defaults to 1 second. | ||
| 26 | + * **keepAliveTimeoutThreshold** `number | null` (optional) - Default: `2e3` - A number of milliseconds subtracted from server *keep-alive* hints when overriding `keepAliveTimeout` to account for timing inaccuracies caused by e.g. transport latency. Defaults to 2 seconds. | ||
| 27 | 27 | * **maxHeaderSize** `number | null` (optional) - Default: `--max-http-header-size` or `16384` - The maximum length of request headers in bytes. Defaults to Node.js' --max-http-header-size or 16KiB. | |
| 28 | 28 | * **maxResponseSize** `number | null` (optional) - Default: `-1` - The maximum length of response body in bytes. Set to `-1` to disable. | |
| 29 | 29 | * **pipelining** `number | null` (optional) - Default: `1` - The amount of concurrent requests to be sent over the single TCP/TLS connection according to [RFC7230](https://tools.ietf.org/html/rfc7230#section-6.3.2). Carefully consider your workload and environment before enabling concurrent requests as pipelining may reduce performance if used incorrectly. Pipelining is sensitive to network stack settings as well as head of line blocking caused by e.g. long running requests. Set to `0` to disable keep-alive connections. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,7 @@ | |||
| 1 | 1 | # EventSource | |
| 2 | 2 | ||
| 3 | + > ⚠️ Warning: the EventSource API is experimental. | ||
| 4 | + | ||
| 3 | 5 | Undici exposes a WHATWG spec-compliant implementation of [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) | |
| 4 | 6 | for [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events). | |
| 5 | 7 | ||
@@ -11,11 +13,33 @@ follows: | |||
| 11 | 13 | ```mjs | |
| 12 | 14 | import { EventSource } from 'undici' | |
| 13 | 15 | ||
| 14 | - const evenSource = new EventSource('http://localhost:3000') | ||
| 15 | - evenSource.onmessage = (event) => { | ||
| 16 | + const eventSource = new EventSource('http://localhost:3000') | ||
| 17 | + eventSource.onmessage = (event) => { | ||
| 16 | 18 | console.log(event.data) | |
| 17 | 19 | } | |
| 18 | 20 | ``` | |
| 19 | 21 | ||
| 22 | + ## Using a custom Dispatcher | ||
| 23 | + | ||
| 24 | + undici allows you to set your own Dispatcher in the EventSource constructor. | ||
| 25 | + | ||
| 26 | + An example which allows you to modify the request headers is: | ||
| 27 | + | ||
| 28 | + ```mjs | ||
| 29 | + import { EventSource, Agent } from 'undici' | ||
| 30 | + | ||
| 31 | + class CustomHeaderAgent extends Agent { | ||
| 32 | + dispatch (opts) { | ||
| 33 | + opts.headers['x-custom-header'] = 'hello world' | ||
| 34 | + return super.dispatch(...arguments) | ||
| 35 | + } | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + const eventSource = new EventSource('http://localhost:3000', { | ||
| 39 | + dispatcher: new CustomHeaderAgent() | ||
| 40 | + }) | ||
| 41 | + | ||
| 42 | + ``` | ||
| 43 | + | ||
| 20 | 44 | More information about the EventSource API can be found on | |
| 21 | - [MDN](https://developer.mozilla.org/en-US/docs/Web/API/EventSource). | ||
| 45 | + [MDN](https://developer.mozilla.org/en-US/docs/Web/API/EventSource). | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,12 +4,6 @@ Undici exposes a fetch() method starts the process of fetching a resource from t | |||
| 4 | 4 | ||
| 5 | 5 | Documentation and examples can be found on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/fetch). | |
| 6 | 6 | ||
| 7 | - ## File | ||
| 8 | - | ||
| 9 | - This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/File) | ||
| 10 | - | ||
| 11 | - In Node versions v18.13.0 and above and v19.2.0 and above, undici will default to using Node's [File](https://nodejs.org/api/buffer.html#class-file) class. In versions where it's not available, it will default to the undici one. | ||
| 12 | - | ||
| 13 | 7 | ## FormData | |
| 14 | 8 | ||
| 15 | 9 | This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/FormData). | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,7 @@ | |||
| 1 | 1 | 'use strict' | |
| 2 | 2 | ||
| 3 | + const { getGlobalDispatcher, setGlobalDispatcher } = require('./lib/global') | ||
| 4 | + const EnvHttpProxyAgent = require('./lib/dispatcher/env-http-proxy-agent') | ||
| 3 | 5 | const fetchImpl = require('./lib/web/fetch').fetch | |
| 4 | 6 | ||
| 5 | 7 | module.exports.fetch = function fetch (resource, init = undefined) { | |
@@ -15,7 +17,16 @@ module.exports.Headers = require('./lib/web/fetch/headers').Headers | |||
| 15 | 17 | module.exports.Response = require('./lib/web/fetch/response').Response | |
| 16 | 18 | module.exports.Request = require('./lib/web/fetch/request').Request | |
| 17 | 19 | ||
| 20 | + const { CloseEvent, ErrorEvent, MessageEvent, createFastMessageEvent } = require('./lib/web/websocket/events') | ||
| 18 | 21 | module.exports.WebSocket = require('./lib/web/websocket/websocket').WebSocket | |
| 19 | - module.exports.MessageEvent = require('./lib/web/websocket/events').MessageEvent | ||
| 22 | + module.exports.CloseEvent = CloseEvent | ||
| 23 | + module.exports.ErrorEvent = ErrorEvent | ||
| 24 | + module.exports.MessageEvent = MessageEvent | ||
| 25 | + module.exports.createFastMessageEvent = createFastMessageEvent | ||
| 20 | 26 | ||
| 21 | 27 | module.exports.EventSource = require('./lib/web/eventsource/eventsource').EventSource | |
| 28 | + | ||
| 29 | + // Expose the fetch implementation to be enabled in Node.js core via a flag | ||
| 30 | + module.exports.EnvHttpProxyAgent = EnvHttpProxyAgent | ||
| 31 | + module.exports.getGlobalDispatcher = getGlobalDispatcher | ||
| 32 | + module.exports.setGlobalDispatcher = setGlobalDispatcher | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,6 +6,7 @@ const Pool = require('./lib/dispatcher/pool') | |||
| 6 | 6 | const BalancedPool = require('./lib/dispatcher/balanced-pool') | |
| 7 | 7 | const Agent = require('./lib/dispatcher/agent') | |
| 8 | 8 | const ProxyAgent = require('./lib/dispatcher/proxy-agent') | |
| 9 | + const EnvHttpProxyAgent = require('./lib/dispatcher/env-http-proxy-agent') | ||
| 9 | 10 | const RetryAgent = require('./lib/dispatcher/retry-agent') | |
| 10 | 11 | const errors = require('./lib/core/errors') | |
| 11 | 12 | const util = require('./lib/core/util') | |
@@ -30,6 +31,7 @@ module.exports.Pool = Pool | |||
| 30 | 31 | module.exports.BalancedPool = BalancedPool | |
| 31 | 32 | module.exports.Agent = Agent | |
| 32 | 33 | module.exports.ProxyAgent = ProxyAgent | |
| 34 | + module.exports.EnvHttpProxyAgent = EnvHttpProxyAgent | ||
| 33 | 35 | module.exports.RetryAgent = RetryAgent | |
| 34 | 36 | module.exports.RetryHandler = RetryHandler | |
| 35 | 37 | ||
@@ -116,7 +118,7 @@ module.exports.Headers = require('./lib/web/fetch/headers').Headers | |||
| 116 | 118 | module.exports.Response = require('./lib/web/fetch/response').Response | |
| 117 | 119 | module.exports.Request = require('./lib/web/fetch/request').Request | |
| 118 | 120 | module.exports.FormData = require('./lib/web/fetch/formdata').FormData | |
| 119 | - module.exports.File = require('./lib/web/fetch/file').File | ||
| 121 | + module.exports.File = globalThis.File ?? require('node:buffer').File | ||
| 120 | 122 | module.exports.FileReader = require('./lib/web/fileapi/filereader').FileReader | |
| 121 | 123 | ||
| 122 | 124 | const { setGlobalOrigin, getGlobalOrigin } = require('./lib/web/fetch/global') | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,18 +12,25 @@ async function getResolveErrorBodyCallback ({ callback, body, contentType, statu | |||
| 12 | 12 | let chunks = [] | |
| 13 | 13 | let length = 0 | |
| 14 | 14 | ||
| 15 | - for await (const chunk of body) { | ||
| 16 | - chunks.push(chunk) | ||
| 17 | - length += chunk.length | ||
| 18 | - if (length > CHUNK_LIMIT) { | ||
| 19 | - chunks = null | ||
| 20 | - break | ||
| 15 | + try { | ||
| 16 | + for await (const chunk of body) { | ||
| 17 | + chunks.push(chunk) | ||
| 18 | + length += chunk.length | ||
| 19 | + if (length > CHUNK_LIMIT) { | ||
| 20 | + chunks = [] | ||
| 21 | + length = 0 | ||
| 22 | + break | ||
| 23 | + } | ||
| 21 | 24 | } | |
| 25 | + } catch { | ||
| 26 | + chunks = [] | ||
| 27 | + length = 0 | ||
| 28 | + // Do nothing.... | ||
| 22 | 29 | } | |
| 23 | 30 | ||
| 24 | 31 | const message = `Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}` | |
| 25 | 32 | ||
| 26 | - if (statusCode === 204 || !contentType || !chunks) { | ||
| 33 | + if (statusCode === 204 || !contentType || !length) { | ||
| 27 | 34 | queueMicrotask(() => callback(new ResponseStatusCodeError(message, statusCode, headers))) | |
| 28 | 35 | return | |
| 29 | 36 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,7 +7,7 @@ const { | |||
| 7 | 7 | const assert = require('node:assert') | |
| 8 | 8 | const { | |
| 9 | 9 | isValidHTTPToken, | |
| 10 | - isValidHeaderChar, | ||
| 10 | + isValidHeaderValue, | ||
| 11 | 11 | isStream, | |
| 12 | 12 | destroy, | |
| 13 | 13 | isBuffer, | |
@@ -336,7 +336,7 @@ function processHeader (request, key, val) { | |||
| 336 | 336 | const arr = [] | |
| 337 | 337 | for (let i = 0; i < val.length; i++) { | |
| 338 | 338 | if (typeof val[i] === 'string') { | |
| 339 | - if (!isValidHeaderChar(val[i])) { | ||
| 339 | + if (!isValidHeaderValue(val[i])) { | ||
| 340 | 340 | throw new InvalidArgumentError(`invalid ${key} header`) | |
| 341 | 341 | } | |
| 342 | 342 | arr.push(val[i]) | |
@@ -350,13 +350,11 @@ function processHeader (request, key, val) { | |||
| 350 | 350 | } | |
| 351 | 351 | val = arr | |
| 352 | 352 | } else if (typeof val === 'string') { | |
| 353 | - if (!isValidHeaderChar(val)) { | ||
| 353 | + if (!isValidHeaderValue(val)) { | ||
| 354 | 354 | throw new InvalidArgumentError(`invalid ${key} header`) | |
| 355 | 355 | } | |
| 356 | 356 | } else if (val === null) { | |
| 357 | 357 | val = '' | |
| 358 | - } else if (typeof val === 'object') { | ||
| 359 | - throw new InvalidArgumentError(`invalid ${key} header`) | ||
| 360 | 358 | } else { | |
| 361 | 359 | val = `${val}` | |
| 362 | 360 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -60,5 +60,8 @@ module.exports = { | |||
| 60 | 60 | kConstruct: Symbol('constructable'), | |
| 61 | 61 | kListeners: Symbol('listeners'), | |
| 62 | 62 | kHTTPContext: Symbol('http context'), | |
| 63 | - kMaxConcurrentStreams: Symbol('max concurrent streams') | ||
| 63 | + kMaxConcurrentStreams: Symbol('max concurrent streams'), | ||
| 64 | + kNoProxyAgent: Symbol('no proxy agent'), | ||
| 65 | + kHttpProxyAgent: Symbol('http proxy agent'), | ||
| 66 | + kHttpsProxyAgent: Symbol('https proxy agent') | ||
| 64 | 67 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -52,11 +52,37 @@ function buildURL (url, queryParams) { | |||
| 52 | 52 | return url | |
| 53 | 53 | } | |
| 54 | 54 | ||
| 55 | + function isValidPort (port) { | ||
| 56 | + const value = parseInt(port, 10) | ||
| 57 | + return ( | ||
| 58 | + value === Number(port) && | ||
| 59 | + value >= 0 && | ||
| 60 | + value <= 65535 | ||
| 61 | + ) | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + function isHttpOrHttpsPrefixed (value) { | ||
| 65 | + return ( | ||
| 66 | + value != null && | ||
| 67 | + value[0] === 'h' && | ||
| 68 | + value[1] === 't' && | ||
| 69 | + value[2] === 't' && | ||
| 70 | + value[3] === 'p' && | ||
| 71 | + ( | ||
| 72 | + value[4] === ':' || | ||
| 73 | + ( | ||
| 74 | + value[4] === 's' && | ||
| 75 | + value[5] === ':' | ||
| 76 | + ) | ||
| 77 | + ) | ||
| 78 | + ) | ||
| 79 | + } | ||
| 80 | + | ||
| 55 | 81 | function parseURL (url) { | |
| 56 | 82 | if (typeof url === 'string') { | |
| 57 | 83 | url = new URL(url) | |
| 58 | 84 | ||
| 59 | - if (!/^https?:/.test(url.origin || url.protocol)) { | ||
| 85 | + if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) { | ||
| 60 | 86 | throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.') | |
| 61 | 87 | } | |
| 62 | 88 | ||
@@ -67,12 +93,8 @@ function parseURL (url) { | |||
| 67 | 93 | throw new InvalidArgumentError('Invalid URL: The URL argument must be a non-null object.') | |
| 68 | 94 | } | |
| 69 | 95 | ||
| 70 | - if (!/^https?:/.test(url.origin || url.protocol)) { | ||
| 71 | - throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.') | ||
| 72 | - } | ||
| 73 | - | ||
| 74 | 96 | if (!(url instanceof URL)) { | |
| 75 | - if (url.port != null && url.port !== '' && !Number.isFinite(parseInt(url.port))) { | ||
| 97 | + if (url.port != null && url.port !== '' && isValidPort(url.port) === false) { | ||
| 76 | 98 | throw new InvalidArgumentError('Invalid URL: port must be a valid integer or a string representation of an integer.') | |
| 77 | 99 | } | |
| 78 | 100 | ||
@@ -92,28 +114,36 @@ function parseURL (url) { | |||
| 92 | 114 | throw new InvalidArgumentError('Invalid URL origin: the origin must be a string or null/undefined.') | |
| 93 | 115 | } | |
| 94 | 116 | ||
| 117 | + if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) { | ||
| 118 | + throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.') | ||
| 119 | + } | ||
| 120 | + | ||
| 95 | 121 | const port = url.port != null | |
| 96 | 122 | ? url.port | |
| 97 | 123 | : (url.protocol === 'https:' ? 443 : 80) | |
| 98 | 124 | let origin = url.origin != null | |
| 99 | 125 | ? url.origin | |
| 100 | - : `${url.protocol}//${url.hostname}:${port}` | ||
| 126 | + : `${url.protocol || ''}//${url.hostname || ''}:${port}` | ||
| 101 | 127 | let path = url.path != null | |
| 102 | 128 | ? url.path | |
| 103 | 129 | : `${url.pathname || ''}${url.search || ''}` | |
| 104 | 130 | ||
| 105 | - if (origin.endsWith('/')) { | ||
| 106 | - origin = origin.substring(0, origin.length - 1) | ||
| 131 | + if (origin[origin.length - 1] === '/') { | ||
| 132 | + origin = origin.slice(0, origin.length - 1) | ||
| 107 | 133 | } | |
| 108 | 134 | ||
| 109 | - if (path && !path.startsWith('/')) { | ||
| 135 | + if (path && path[0] !== '/') { | ||
| 110 | 136 | path = `/${path}` | |
| 111 | 137 | } | |
| 112 | 138 | // new URL(path, origin) is unsafe when `path` contains an absolute URL | |
| 113 | 139 | // From https://developer.mozilla.org/en-US/docs/Web/API/URL/URL: | |
| 114 | 140 | // If first parameter is a relative URL, second param is required, and will be used as the base URL. | |
| 115 | 141 | // If first parameter is an absolute URL, a given second param will be ignored. | |
| 116 | - url = new URL(origin + path) | ||
| 142 | + return new URL(`${origin}${path}`) | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) { | ||
| 146 | + throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.') | ||
| 117 | 147 | } | |
| 118 | 148 | ||
| 119 | 149 | return url | |
@@ -193,11 +223,6 @@ function isDestroyed (body) { | |||
| 193 | 223 | return body && !!(body.destroyed || body[kDestroyed] || (stream.isDestroyed?.(body))) | |
| 194 | 224 | } | |
| 195 | 225 | ||
| 196 | - function isReadableAborted (stream) { | ||
| 197 | - const state = stream?._readableState | ||
| 198 | - return isDestroyed(stream) && state && !state.endEmitted | ||
| 199 | - } | ||
| 200 | - | ||
| 201 | 226 | function destroy (stream, err) { | |
| 202 | 227 | if (stream == null || !isStream(stream) || isDestroyed(stream)) { | |
| 203 | 228 | return | |
@@ -522,7 +547,7 @@ const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/ | |||
| 522 | 547 | /** | |
| 523 | 548 | * @param {string} characters | |
| 524 | 549 | */ | |
| 525 | - function isValidHeaderChar (characters) { | ||
| 550 | + function isValidHeaderValue (characters) { | ||
| 526 | 551 | return !headerCharRegex.test(characters) | |
| 527 | 552 | } | |
| 528 | 553 | ||
@@ -575,7 +600,6 @@ module.exports = { | |||
| 575 | 600 | isReadable, | |
| 576 | 601 | toUSVString, | |
| 577 | 602 | isUSVString, | |
| 578 | - isReadableAborted, | ||
| 579 | 603 | isBlobLike, | |
| 580 | 604 | parseOrigin, | |
| 581 | 605 | parseURL, | |
@@ -603,11 +627,12 @@ module.exports = { | |||
| 603 | 627 | buildURL, | |
| 604 | 628 | addAbortListener, | |
| 605 | 629 | isValidHTTPToken, | |
| 606 | - isValidHeaderChar, | ||
| 630 | + isValidHeaderValue, | ||
| 607 | 631 | isTokenCharCode, | |
| 608 | 632 | parseRangeHeader, | |
| 633 | + isValidPort, | ||
| 634 | + isHttpOrHttpsPrefixed, | ||
| 609 | 635 | nodeMajor, | |
| 610 | 636 | nodeMinor, | |
| 611 | - nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13), | ||
| 612 | 637 | safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE'] | |
| 613 | 638 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -208,10 +208,9 @@ function onHttp2SessionEnd () { | |||
| 208 | 208 | * This is the root cause of #3011 | |
| 209 | 209 | * We need to handle GOAWAY frames properly, and trigger the session close | |
| 210 | 210 | * along with the socket right away | |
| 211 | - * Find a way to trigger the close cycle from here on. | ||
| 212 | 211 | */ | |
| 213 | 212 | function onHTTP2GoAway (code) { | |
| 214 | - const err = new InformationalError(`HTTP/2: "GOAWAY" frame received with code ${code}`) | ||
| 213 | + const err = new RequestAbortedError(`HTTP/2: "GOAWAY" frame received with code ${code}`) | ||
| 215 | 214 | ||
| 216 | 215 | // We need to trigger the close cycle right away | |
| 217 | 216 | // We need to destroy the session and the socket | |
@@ -220,8 +219,7 @@ function onHTTP2GoAway (code) { | |||
| 220 | 219 | this[kClient][kOnError](err) | |
| 221 | 220 | ||
| 222 | 221 | this.unref() | |
| 223 | - // We send the GOAWAY frame response as no error | ||
| 224 | - this.destroy() | ||
| 222 | + | ||
| 225 | 223 | util.destroy(this[kSocket], err) | |
| 226 | 224 | } | |
| 227 | 225 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments