| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -24,8 +24,10 @@ Once you call `buildConnector`, it will return a connector function, which takes | |||
| 24 | 24 | * **hostname** `string` (required) | |
| 25 | 25 | * **host** `string` (optional) | |
| 26 | 26 | * **protocol** `string` (required) | |
| 27 | - * **port** `number` (required) | ||
| 27 | + * **port** `string` (required) | ||
| 28 | 28 | * **servername** `string` (optional) | |
| 29 | + * **localAddress** `string | null` (optional) Local address the socket should connect from. | ||
| 30 | + * **httpSocket** `Socket` (optional) Establish secure connection on a given socket rather than creating a new socket. It can only be sent on TLS update. | ||
| 29 | 31 | ||
| 30 | 32 | ### Basic example | |
| 31 | 33 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,22 +4,81 @@ const net = require('net') | |||
| 4 | 4 | const assert = require('assert') | |
| 5 | 5 | const util = require('./util') | |
| 6 | 6 | const { InvalidArgumentError, ConnectTimeoutError } = require('./errors') | |
| 7 | + | ||
| 7 | 8 | let tls // include tls conditionally since it is not always available | |
| 8 | 9 | ||
| 9 | 10 | // TODO: session re-use does not wait for the first | |
| 10 | 11 | // connection to resolve the session and might therefore | |
| 11 | 12 | // resolve the same servername multiple times even when | |
| 12 | 13 | // re-use is enabled. | |
| 13 | 14 | ||
| 15 | + let SessionCache | ||
| 16 | + if (global.FinalizationRegistry) { | ||
| 17 | + SessionCache = class WeakSessionCache { | ||
| 18 | + constructor (maxCachedSessions) { | ||
| 19 | + this._maxCachedSessions = maxCachedSessions | ||
| 20 | + this._sessionCache = new Map() | ||
| 21 | + this._sessionRegistry = new global.FinalizationRegistry((key) => { | ||
| 22 | + if (this._sessionCache.size < this._maxCachedSessions) { | ||
| 23 | + return | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + const ref = this._sessionCache.get(key) | ||
| 27 | + if (ref !== undefined && ref.deref() === undefined) { | ||
| 28 | + this._sessionCache.delete(key) | ||
| 29 | + } | ||
| 30 | + }) | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + get (sessionKey) { | ||
| 34 | + const ref = this._sessionCache.get(sessionKey) | ||
| 35 | + return ref ? ref.deref() : null | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + set (sessionKey, session) { | ||
| 39 | + if (this._maxCachedSessions === 0) { | ||
| 40 | + return | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + this._sessionCache.set(sessionKey, new WeakRef(session)) | ||
| 44 | + this._sessionRegistry.register(session, sessionKey) | ||
| 45 | + } | ||
| 46 | + } | ||
| 47 | + } else { | ||
| 48 | + SessionCache = class SimpleSessionCache { | ||
| 49 | + constructor (maxCachedSessions) { | ||
| 50 | + this._maxCachedSessions = maxCachedSessions | ||
| 51 | + this._sessionCache = new Map() | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + get (sessionKey) { | ||
| 55 | + return this._sessionCache.get(sessionKey) | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + set (sessionKey, session) { | ||
| 59 | + if (this._maxCachedSessions === 0) { | ||
| 60 | + return | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + if (this._sessionCache.size >= this._maxCachedSessions) { | ||
| 64 | + // remove the oldest session | ||
| 65 | + const { value: oldestKey } = this._sessionCache.keys().next() | ||
| 66 | + this._sessionCache.delete(oldestKey) | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + this._sessionCache.set(sessionKey, session) | ||
| 70 | + } | ||
| 71 | + } | ||
| 72 | + } | ||
| 73 | + | ||
| 14 | 74 | function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) { | |
| 15 | 75 | if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) { | |
| 16 | 76 | throw new InvalidArgumentError('maxCachedSessions must be a positive integer or zero') | |
| 17 | 77 | } | |
| 18 | 78 | ||
| 19 | 79 | const options = { path: socketPath, ...opts } | |
| 20 | - const sessionCache = new Map() | ||
| 80 | + const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions) | ||
| 21 | 81 | timeout = timeout == null ? 10e3 : timeout | |
| 22 | - maxCachedSessions = maxCachedSessions == null ? 100 : maxCachedSessions | ||
| 23 | 82 | ||
| 24 | 83 | return function connect ({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) { | |
| 25 | 84 | let socket | |
@@ -47,25 +106,9 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) { | |||
| 47 | 106 | ||
| 48 | 107 | socket | |
| 49 | 108 | .on('session', function (session) { | |
| 50 | - // cache is disabled | ||
| 51 | - if (maxCachedSessions === 0) { | ||
| 52 | - return | ||
| 53 | - } | ||
| 54 | - | ||
| 55 | - if (sessionCache.size >= maxCachedSessions) { | ||
| 56 | - // remove the oldest session | ||
| 57 | - const { value: oldestKey } = sessionCache.keys().next() | ||
| 58 | - sessionCache.delete(oldestKey) | ||
| 59 | - } | ||
| 60 | - | ||
| 109 | + // TODO (fix): Can a session become invalid once established? Don't think so? | ||
| 61 | 110 | sessionCache.set(sessionKey, session) | |
| 62 | 111 | }) | |
| 63 | - .on('error', function (err) { | ||
| 64 | - if (sessionKey && err.code !== 'UND_ERR_INFO') { | ||
| 65 | - // TODO (fix): Only delete for session related errors. | ||
| 66 | - sessionCache.delete(sessionKey) | ||
| 67 | - } | ||
| 68 | - }) | ||
| 69 | 112 | } else { | |
| 70 | 113 | assert(!httpSocket, 'httpSocket can only be sent on TLS update') | |
| 71 | 114 | socket = net.connect({ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,17 +7,19 @@ const { FormData } = require('./formdata') | |||
| 7 | 7 | const { kState } = require('./symbols') | |
| 8 | 8 | const { webidl } = require('./webidl') | |
| 9 | 9 | const { DOMException, structuredClone } = require('./constants') | |
| 10 | - const { Blob } = require('buffer') | ||
| 10 | + const { Blob, File: NativeFile } = require('buffer') | ||
| 11 | 11 | const { kBodyUsed } = require('../core/symbols') | |
| 12 | 12 | const assert = require('assert') | |
| 13 | 13 | const { isErrored } = require('../core/util') | |
| 14 | 14 | const { isUint8Array, isArrayBuffer } = require('util/types') | |
| 15 | - const { File } = require('./file') | ||
| 15 | + const { File: UndiciFile } = require('./file') | ||
| 16 | 16 | const { StringDecoder } = require('string_decoder') | |
| 17 | 17 | const { parseMIMEType, serializeAMimeType } = require('./dataURL') | |
| 18 | 18 | ||
| 19 | - /** @type {globalThis['ReadableStream']} */ | ||
| 20 | - let ReadableStream | ||
| 19 | + let ReadableStream = globalThis.ReadableStream | ||
| 20 | + | ||
| 21 | + /** @type {globalThis['File']} */ | ||
| 22 | + const File = NativeFile ?? UndiciFile | ||
| 21 | 23 | ||
| 22 | 24 | // https://fetch.spec.whatwg.org/#concept-bodyinit-extract | |
| 23 | 25 | function extractBody (object, keepalive = false) { | |
@@ -142,7 +144,33 @@ function extractBody (object, keepalive = false) { | |||
| 142 | 144 | source = object | |
| 143 | 145 | ||
| 144 | 146 | // Set length to unclear, see html/6424 for improving this. | |
| 145 | - // TODO | ||
| 147 | + length = (() => { | ||
| 148 | + const prefixLength = prefix.length | ||
| 149 | + const boundaryLength = boundary.length | ||
| 150 | + let bodyLength = 0 | ||
| 151 | + | ||
| 152 | + for (const [name, value] of object) { | ||
| 153 | + if (typeof value === 'string') { | ||
| 154 | + bodyLength += | ||
| 155 | + prefixLength + | ||
| 156 | + Buffer.byteLength(`; name="${escape(normalizeLinefeeds(name))}"\r\n\r\n${normalizeLinefeeds(value)}\r\n`) | ||
| 157 | + } else { | ||
| 158 | + bodyLength += | ||
| 159 | + prefixLength + | ||
| 160 | + Buffer.byteLength(`; name="${escape(normalizeLinefeeds(name))}"` + (value.name ? `; filename="${escape(value.name)}"` : '')) + | ||
| 161 | + 2 + // \r\n | ||
| 162 | + `Content-Type: ${ | ||
| 163 | + value.type || 'application/octet-stream' | ||
| 164 | + }\r\n\r\n`.length | ||
| 165 | + | ||
| 166 | + // value is a Blob or File, and \r\n | ||
| 167 | + bodyLength += value.size + 2 | ||
| 168 | + } | ||
| 169 | + } | ||
| 170 | + | ||
| 171 | + bodyLength += boundaryLength + 4 // --boundary-- | ||
| 172 | + return bodyLength | ||
| 173 | + })() | ||
| 146 | 174 | ||
| 147 | 175 | // Set type to `multipart/form-data; boundary=`, | |
| 148 | 176 | // followed by the multipart/form-data boundary string generated | |
@@ -348,7 +376,10 @@ function bodyMixinMethods (instance) { | |||
| 348 | 376 | let busboy | |
| 349 | 377 | ||
| 350 | 378 | try { | |
| 351 | - busboy = Busboy({ headers }) | ||
| 379 | + busboy = Busboy({ | ||
| 380 | + headers, | ||
| 381 | + defParamCharset: 'utf8' | ||
| 382 | + }) | ||
| 352 | 383 | } catch (err) { | |
| 353 | 384 | // Error due to headers: | |
| 354 | 385 | throw Object.assign(new TypeError(), { cause: err }) | |
@@ -361,7 +392,7 @@ function bodyMixinMethods (instance) { | |||
| 361 | 392 | const { filename, encoding, mimeType } = info | |
| 362 | 393 | const chunks = [] | |
| 363 | 394 | ||
| 364 | - if (encoding.toLowerCase() === 'base64') { | ||
| 395 | + if (encoding === 'base64' || encoding.toLowerCase() === 'base64') { | ||
| 365 | 396 | let base64chunk = '' | |
| 366 | 397 | ||
| 367 | 398 | value.on('data', (chunk) => { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,6 @@ | |||
| 1 | 1 | 'use strict' | |
| 2 | 2 | ||
| 3 | - const { Blob } = require('buffer') | ||
| 3 | + const { Blob, File: NativeFile } = require('buffer') | ||
| 4 | 4 | const { types } = require('util') | |
| 5 | 5 | const { kState } = require('./symbols') | |
| 6 | 6 | const { isBlobLike } = require('./util') | |
@@ -329,11 +329,14 @@ function convertLineEndingsNative (s) { | |||
| 329 | 329 | // rollup) will warn about circular dependencies. See: | |
| 330 | 330 | // https://github.com/nodejs/undici/issues/1629 | |
| 331 | 331 | function isFileLike (object) { | |
| 332 | - return object instanceof File || ( | ||
| 333 | - object && | ||
| 334 | - (typeof object.stream === 'function' || | ||
| 335 | - typeof object.arrayBuffer === 'function') && | ||
| 336 | - object[Symbol.toStringTag] === 'File' | ||
| 332 | + return ( | ||
| 333 | + (NativeFile && object instanceof NativeFile) || | ||
| 334 | + object instanceof File || ( | ||
| 335 | + object && | ||
| 336 | + (typeof object.stream === 'function' || | ||
| 337 | + typeof object.arrayBuffer === 'function') && | ||
| 338 | + object[Symbol.toStringTag] === 'File' | ||
| 339 | + ) | ||
| 337 | 340 | ) | |
| 338 | 341 | } | |
| 339 | 342 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,9 +2,12 @@ | |||
| 2 | 2 | ||
| 3 | 3 | const { isBlobLike, toUSVString, makeIterator } = require('./util') | |
| 4 | 4 | const { kState } = require('./symbols') | |
| 5 | - const { File, FileLike, isFileLike } = require('./file') | ||
| 5 | + const { File: UndiciFile, FileLike, isFileLike } = require('./file') | ||
| 6 | 6 | const { webidl } = require('./webidl') | |
| 7 | - const { Blob } = require('buffer') | ||
| 7 | + const { Blob, File: NativeFile } = require('buffer') | ||
| 8 | + | ||
| 9 | + /** @type {globalThis['File']} */ | ||
| 10 | + const File = NativeFile ?? UndiciFile | ||
| 8 | 11 | ||
| 9 | 12 | // https://xhr.spec.whatwg.org/#formdata | |
| 10 | 13 | class FormData { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,7 +3,7 @@ | |||
| 3 | 3 | 'use strict' | |
| 4 | 4 | ||
| 5 | 5 | const { kHeadersList } = require('../core/symbols') | |
| 6 | - const { kGuard } = require('./symbols') | ||
| 6 | + const { kGuard, kHeadersCaseInsensitive } = require('./symbols') | ||
| 7 | 7 | const { kEnumerableProperty } = require('../core/util') | |
| 8 | 8 | const { | |
| 9 | 9 | makeIterator, | |
@@ -96,27 +96,27 @@ class HeadersList { | |||
| 96 | 96 | ||
| 97 | 97 | // 1. If list contains name, then set name to the first such | |
| 98 | 98 | // header’s name. | |
| 99 | - name = name.toLowerCase() | ||
| 100 | - const exists = this[kHeadersMap].get(name) | ||
| 99 | + const lowercaseName = name.toLowerCase() | ||
| 100 | + const exists = this[kHeadersMap].get(lowercaseName) | ||
| 101 | 101 | ||
| 102 | 102 | // 2. Append (name, value) to list. | |
| 103 | 103 | if (exists) { | |
| 104 | - this[kHeadersMap].set(name, `${exists}, ${value}`) | ||
| 104 | + this[kHeadersMap].set(lowercaseName, { name: exists.name, value: `${exists.value}, ${value}` }) | ||
| 105 | 105 | } else { | |
| 106 | - this[kHeadersMap].set(name, `${value}`) | ||
| 106 | + this[kHeadersMap].set(lowercaseName, { name, value }) | ||
| 107 | 107 | } | |
| 108 | 108 | } | |
| 109 | 109 | ||
| 110 | 110 | // https://fetch.spec.whatwg.org/#concept-header-list-set | |
| 111 | 111 | set (name, value) { | |
| 112 | 112 | this[kHeadersSortedMap] = null | |
| 113 | - name = name.toLowerCase() | ||
| 113 | + const lowercaseName = name.toLowerCase() | ||
| 114 | 114 | ||
| 115 | 115 | // 1. If list contains name, then set the value of | |
| 116 | 116 | // the first such header to value and remove the | |
| 117 | 117 | // others. | |
| 118 | 118 | // 2. Otherwise, append header (name, value) to list. | |
| 119 | - return this[kHeadersMap].set(name, value) | ||
| 119 | + return this[kHeadersMap].set(lowercaseName, { name, value }) | ||
| 120 | 120 | } | |
| 121 | 121 | ||
| 122 | 122 | // https://fetch.spec.whatwg.org/#concept-header-list-delete | |
@@ -137,14 +137,26 @@ class HeadersList { | |||
| 137 | 137 | // 2. Return the values of all headers in list whose name | |
| 138 | 138 | // is a byte-case-insensitive match for name, | |
| 139 | 139 | // separated from each other by 0x2C 0x20, in order. | |
| 140 | - return this[kHeadersMap].get(name.toLowerCase()) ?? null | ||
| 140 | + return this[kHeadersMap].get(name.toLowerCase())?.value ?? null | ||
| 141 | 141 | } | |
| 142 | 142 | ||
| 143 | 143 | * [Symbol.iterator] () { | |
| 144 | - for (const pair of this[kHeadersMap]) { | ||
| 145 | - yield pair | ||
| 144 | + // use the lowercased name | ||
| 145 | + for (const [name, { value }] of this[kHeadersMap]) { | ||
| 146 | + yield [name, value] | ||
| 146 | 147 | } | |
| 147 | 148 | } | |
| 149 | + | ||
| 150 | + get [kHeadersCaseInsensitive] () { | ||
| 151 | + /** @type {string[]} */ | ||
| 152 | + const flatList = [] | ||
| 153 | + | ||
| 154 | + for (const { name, value } of this[kHeadersMap].values()) { | ||
| 155 | + flatList.push(name, value) | ||
| 156 | + } | ||
| 157 | + | ||
| 158 | + return flatList | ||
| 159 | + } | ||
| 148 | 160 | } | |
| 149 | 161 | ||
| 150 | 162 | // https://fetch.spec.whatwg.org/#headers-class | |
| Back | FazBrowse Home | New Git URL |
0 commit comments