| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d2a98dc commit 517f17b
16 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -185,13 +185,12 @@ Help us improve the test coverage by following instructions at [nodejs/undici/#9 | |||
| 185 | 185 | Basic usage example: | |
| 186 | 186 | ||
| 187 | 187 | ```js | |
| 188 | - import {fetch} from 'undici'; | ||
| 188 | + import { fetch } from 'undici'; | ||
| 189 | 189 | ||
| 190 | - async function fetchJson() { | ||
| 191 | - const res = await fetch('https://example.com') | ||
| 192 | - const json = await res.json() | ||
| 193 | - console.log(json); | ||
| 194 | - } | ||
| 190 | + | ||
| 191 | + const res = await fetch('https://example.com') | ||
| 192 | + const json = await res.json() | ||
| 193 | + console.log(json); | ||
| 195 | 194 | ``` | |
| 196 | 195 | ||
| 197 | 196 | You can pass an optional dispatcher to `fetch` as: | |
@@ -235,24 +234,20 @@ const data = { | |||
| 235 | 234 | }, | |
| 236 | 235 | }; | |
| 237 | 236 | ||
| 238 | - (async () => { | ||
| 239 | - await fetch("https://example.com", { body: data, method: 'POST' }); | ||
| 240 | - })(); | ||
| 237 | + await fetch("https://example.com", { body: data, method: 'POST' }); | ||
| 241 | 238 | ``` | |
| 242 | 239 | ||
| 243 | 240 | #### `response.body` | |
| 244 | 241 | ||
| 245 | 242 | Nodejs has two kinds of streams: [web streams](https://nodejs.org/dist/latest-v16.x/docs/api/webstreams.html), which follow the API of the WHATWG web standard found in browsers, and an older Node-specific [streams API](https://nodejs.org/api/stream.html). `response.body` returns a readable web stream. If you would prefer to work with a Node stream you can convert a web stream using `.fromWeb()`. | |
| 246 | 243 | ||
| 247 | 244 | ```js | |
| 248 | - import {fetch} from 'undici'; | ||
| 249 | - import {Readable} from 'node:stream'; | ||
| 245 | + import { fetch } from 'undici'; | ||
| 246 | + import { Readable } from 'node:stream'; | ||
| 250 | 247 | ||
| 251 | - async function fetchStream() { | ||
| 252 | - const response = await fetch('https://example.com') | ||
| 253 | - const readableWebStream = response.body; | ||
| 254 | - const readableNodeStream = Readable.fromWeb(readableWebStream); | ||
| 255 | - } | ||
| 248 | + const response = await fetch('https://example.com') | ||
| 249 | + const readableWebStream = response.body; | ||
| 250 | + const readableNodeStream = Readable.fromWeb(readableWebStream); | ||
| 256 | 251 | ``` | |
| 257 | 252 | ||
| 258 | 253 | #### Specification Compliance | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -15,7 +15,7 @@ class ConnectHandler extends AsyncResource { | |||
| 15 | 15 | throw new InvalidArgumentError('invalid callback') | |
| 16 | 16 | } | |
| 17 | 17 | ||
| 18 | - const { signal, opaque, responseHeaders } = opts | ||
| 18 | + const { signal, opaque, responseHeaders, httpTunnel } = opts | ||
| 19 | 19 | ||
| 20 | 20 | if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') { | |
| 21 | 21 | throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') | |
@@ -27,6 +27,7 @@ class ConnectHandler extends AsyncResource { | |||
| 27 | 27 | this.responseHeaders = responseHeaders || null | |
| 28 | 28 | this.callback = callback | |
| 29 | 29 | this.abort = null | |
| 30 | + this.httpTunnel = httpTunnel | ||
| 30 | 31 | ||
| 31 | 32 | addSignal(this, signal) | |
| 32 | 33 | } | |
@@ -40,8 +41,23 @@ class ConnectHandler extends AsyncResource { | |||
| 40 | 41 | this.context = context | |
| 41 | 42 | } | |
| 42 | 43 | ||
| 43 | - onHeaders () { | ||
| 44 | - throw new SocketError('bad connect', null) | ||
| 44 | + onHeaders (statusCode) { | ||
| 45 | + // when httpTunnel headers are allowed | ||
| 46 | + if (this.httpTunnel) { | ||
| 47 | + const { callback, opaque } = this | ||
| 48 | + if (statusCode !== 200) { | ||
| 49 | + if (callback) { | ||
| 50 | + this.callback = null | ||
| 51 | + const err = new RequestAbortedError('Proxy response !== 200 when HTTP Tunneling') | ||
| 52 | + queueMicrotask(() => { | ||
| 53 | + this.runInAsyncScope(callback, null, err, { opaque }) | ||
| 54 | + }) | ||
| 55 | + } | ||
| 56 | + return 1 | ||
| 57 | + } | ||
| 58 | + } else { | ||
| 59 | + throw new SocketError('bad connect', null) | ||
| 60 | + } | ||
| 45 | 61 | } | |
| 46 | 62 | ||
| 47 | 63 | onUpgrade (statusCode, rawHeaders, socket) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -21,7 +21,7 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) { | |||
| 21 | 21 | timeout = timeout == null ? 10e3 : timeout | |
| 22 | 22 | maxCachedSessions = maxCachedSessions == null ? 100 : maxCachedSessions | |
| 23 | 23 | ||
| 24 | - return function connect ({ hostname, host, protocol, port, servername }, callback) { | ||
| 24 | + return function connect ({ hostname, host, protocol, port, servername, httpSocket }, callback) { | ||
| 25 | 25 | let socket | |
| 26 | 26 | if (protocol === 'https:') { | |
| 27 | 27 | if (!tls) { | |
@@ -39,6 +39,7 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) { | |||
| 39 | 39 | ...options, | |
| 40 | 40 | servername, | |
| 41 | 41 | session, | |
| 42 | + socket: httpSocket, // upgrade socket connection | ||
| 42 | 43 | port: port || 443, | |
| 43 | 44 | host: hostname | |
| 44 | 45 | }) | |
@@ -65,6 +66,7 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) { | |||
| 65 | 66 | } | |
| 66 | 67 | }) | |
| 67 | 68 | } else { | |
| 69 | + assert(!httpSocket, 'httpSocket can only be sent on TLS update') | ||
| 68 | 70 | socket = net.connect({ | |
| 69 | 71 | highWaterMark: 64 * 1024, // Same as nodejs fs streams. | |
| 70 | 72 | ...options, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -48,7 +48,11 @@ class Request { | |||
| 48 | 48 | }, handler) { | |
| 49 | 49 | if (typeof path !== 'string') { | |
| 50 | 50 | throw new InvalidArgumentError('path must be a string') | |
| 51 | - } else if (path[0] !== '/' && !(path.startsWith('http://') || path.startsWith('https://'))) { | ||
| 51 | + } else if ( | ||
| 52 | + path[0] !== '/' && | ||
| 53 | + !(path.startsWith('http://') || path.startsWith('https://')) && | ||
| 54 | + method !== 'CONNECT' | ||
| 55 | + ) { | ||
| 52 | 56 | throw new InvalidArgumentError('path must be an absolute URL or start with a slash') | |
| 53 | 57 | } | |
| 54 | 58 | ||
@@ -80,13 +84,12 @@ class Request { | |||
| 80 | 84 | this.body = null | |
| 81 | 85 | } else if (util.isStream(body)) { | |
| 82 | 86 | this.body = body | |
| 83 | - } else if (body instanceof DataView) { | ||
| 84 | - // TODO: Why is DataView special? | ||
| 85 | - this.body = body.buffer.byteLength ? Buffer.from(body.buffer) : null | ||
| 86 | - } else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) { | ||
| 87 | - this.body = body.byteLength ? Buffer.from(body) : null | ||
| 88 | 87 | } else if (util.isBuffer(body)) { | |
| 89 | 88 | this.body = body.byteLength ? body : null | |
| 89 | + } else if (ArrayBuffer.isView(body)) { | ||
| 90 | + this.body = body.buffer.byteLength ? Buffer.from(body.buffer, body.byteOffset, body.byteLength) : null | ||
| 91 | + } else if (body instanceof ArrayBuffer) { | ||
| 92 | + this.body = body.byteLength ? Buffer.from(body) : null | ||
| 90 | 93 | } else if (typeof body === 'string') { | |
| 91 | 94 | this.body = body.length ? Buffer.from(body) : null | |
| 92 | 95 | } else if (util.isFormDataLike(body) || util.isIterable(body) || util.isBlobLike(body)) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,7 +9,7 @@ const { kBodyUsed } = require('../core/symbols') | |||
| 9 | 9 | const assert = require('assert') | |
| 10 | 10 | const { NotSupportedError } = require('../core/errors') | |
| 11 | 11 | const { isErrored } = require('../core/util') | |
| 12 | - const { isUint8Array } = require('util/types') | ||
| 12 | + const { isUint8Array, isArrayBuffer } = require('util/types') | ||
| 13 | 13 | ||
| 14 | 14 | let ReadableStream | |
| 15 | 15 | ||
@@ -61,7 +61,7 @@ function extractBody (object, keepalive = false) { | |||
| 61 | 61 | ||
| 62 | 62 | // Set Content-Type to `application/x-www-form-urlencoded;charset=UTF-8`. | |
| 63 | 63 | contentType = 'application/x-www-form-urlencoded;charset=UTF-8' | |
| 64 | - } else if (object instanceof ArrayBuffer || ArrayBuffer.isView(object)) { | ||
| 64 | + } else if (isArrayBuffer(object) || ArrayBuffer.isView(object)) { | ||
| 65 | 65 | // BufferSource | |
| 66 | 66 | ||
| 67 | 67 | if (object instanceof DataView) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,6 @@ | |||
| 1 | 1 | 'use strict' | |
| 2 | 2 | ||
| 3 | - const { isBlobLike, isFileLike, toUSVString } = require('./util') | ||
| 3 | + const { isBlobLike, isFileLike, toUSVString, makeIterator } = require('./util') | ||
| 4 | 4 | const { kState } = require('./symbols') | |
| 5 | 5 | const { File, FileLike } = require('./file') | |
| 6 | 6 | const { Blob } = require('buffer') | |
@@ -187,45 +187,68 @@ class FormData { | |||
| 187 | 187 | return this.constructor.name | |
| 188 | 188 | } | |
| 189 | 189 | ||
| 190 | - * entries () { | ||
| 190 | + entries () { | ||
| 191 | 191 | if (!(this instanceof FormData)) { | |
| 192 | 192 | throw new TypeError('Illegal invocation') | |
| 193 | 193 | } | |
| 194 | 194 | ||
| 195 | - for (const pair of this) { | ||
| 196 | - yield pair | ||
| 197 | - } | ||
| 195 | + return makeIterator( | ||
| 196 | + makeIterable(this[kState], 'entries'), | ||
| 197 | + 'FormData' | ||
| 198 | + ) | ||
| 198 | 199 | } | |
| 199 | 200 | ||
| 200 | - * keys () { | ||
| 201 | + keys () { | ||
| 201 | 202 | if (!(this instanceof FormData)) { | |
| 202 | 203 | throw new TypeError('Illegal invocation') | |
| 203 | 204 | } | |
| 204 | 205 | ||
| 205 | - for (const [key] of this) { | ||
| 206 | - yield key | ||
| 206 | + return makeIterator( | ||
| 207 | + makeIterable(this[kState], 'keys'), | ||
| 208 | + 'FormData' | ||
| 209 | + ) | ||
| 210 | + } | ||
| 211 | + | ||
| 212 | + values () { | ||
| 213 | + if (!(this instanceof FormData)) { | ||
| 214 | + throw new TypeError('Illegal invocation') | ||
| 207 | 215 | } | |
| 216 | + | ||
| 217 | + return makeIterator( | ||
| 218 | + makeIterable(this[kState], 'values'), | ||
| 219 | + 'FormData' | ||
| 220 | + ) | ||
| 208 | 221 | } | |
| 209 | 222 | ||
| 210 | - * values () { | ||
| 223 | + /** | ||
| 224 | + * @param {(value: string, key: string, self: FormData) => void} callbackFn | ||
| 225 | + * @param {unknown} thisArg | ||
| 226 | + */ | ||
| 227 | + forEach (callbackFn, thisArg = globalThis) { | ||
| 211 | 228 | if (!(this instanceof FormData)) { | |
| 212 | 229 | throw new TypeError('Illegal invocation') | |
| 213 | 230 | } | |
| 214 | 231 | ||
| 215 | - for (const [, value] of this) { | ||
| 216 | - yield value | ||
| 232 | + if (arguments.length < 1) { | ||
| 233 | + throw new TypeError( | ||
| 234 | + `Failed to execute 'forEach' on 'FormData': 1 argument required, but only ${arguments.length} present.` | ||
| 235 | + ) | ||
| 217 | 236 | } | |
| 218 | - } | ||
| 219 | 237 | ||
| 220 | - * [Symbol.iterator] () { | ||
| 221 | - // The value pairs to iterate over are this’s entry list’s entries with | ||
| 222 | - // the key being the name and the value being the value. | ||
| 223 | - for (const { name, value } of this[kState]) { | ||
| 224 | - yield [name, value] | ||
| 238 | + if (typeof callbackFn !== 'function') { | ||
| 239 | + throw new TypeError( | ||
| 240 | + "Failed to execute 'forEach' on 'FormData': parameter 1 is not of type 'Function'." | ||
| 241 | + ) | ||
| 242 | + } | ||
| 243 | + | ||
| 244 | + for (const [key, value] of this) { | ||
| 245 | + callbackFn.apply(thisArg, [value, key, this]) | ||
| 225 | 246 | } | |
| 226 | 247 | } | |
| 227 | 248 | } | |
| 228 | 249 | ||
| 250 | + FormData.prototype[Symbol.iterator] = FormData.prototype.entries | ||
| 251 | + | ||
| 229 | 252 | function makeEntry (name, value, filename) { | |
| 230 | 253 | // To create an entry for name, value, and optionally a filename, run these | |
| 231 | 254 | // steps: | |
@@ -267,4 +290,18 @@ function makeEntry (name, value, filename) { | |||
| 267 | 290 | return entry | |
| 268 | 291 | } | |
| 269 | 292 | ||
| 293 | + function * makeIterable (entries, type) { | ||
| 294 | + // The value pairs to iterate over are this’s entry list’s entries | ||
| 295 | + // with the key being the name and the value being the value. | ||
| 296 | + for (const { name, value } of entries) { | ||
| 297 | + if (type === 'entries') { | ||
| 298 | + yield [name, value] | ||
| 299 | + } else if (type === 'values') { | ||
| 300 | + yield value | ||
| 301 | + } else { | ||
| 302 | + yield name | ||
| 303 | + } | ||
| 304 | + } | ||
| 305 | + } | ||
| 306 | + | ||
| 270 | 307 | module.exports = { FormData } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,6 +6,7 @@ const { validateHeaderName, validateHeaderValue } = require('http') | |||
| 6 | 6 | const { kHeadersList } = require('../core/symbols') | |
| 7 | 7 | const { kGuard } = require('./symbols') | |
| 8 | 8 | const { kEnumerableProperty } = require('../core/util') | |
| 9 | + const { makeIterator } = require('./util') | ||
| 9 | 10 | ||
| 10 | 11 | const kHeadersMap = Symbol('headers map') | |
| 11 | 12 | const kHeadersSortedMap = Symbol('headers map sorted') | |
@@ -73,33 +74,6 @@ function fill (headers, object) { | |||
| 73 | 74 | } | |
| 74 | 75 | } | |
| 75 | 76 | ||
| 76 | - // https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object | ||
| 77 | - const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())) | ||
| 78 | - | ||
| 79 | - // https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object | ||
| 80 | - function makeHeadersIterator (iterator) { | ||
| 81 | - const i = { | ||
| 82 | - next () { | ||
| 83 | - if (Object.getPrototypeOf(this) !== i) { | ||
| 84 | - throw new TypeError( | ||
| 85 | - '\'next\' called on an object that does not implement interface Headers Iterator.' | ||
| 86 | - ) | ||
| 87 | - } | ||
| 88 | - | ||
| 89 | - return iterator.next() | ||
| 90 | - }, | ||
| 91 | - // The class string of an iterator prototype object for a given interface is the | ||
| 92 | - // result of concatenating the identifier of the interface and the string " Iterator". | ||
| 93 | - [Symbol.toStringTag]: 'Headers Iterator' | ||
| 94 | - } | ||
| 95 | - | ||
| 96 | - // The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%. | ||
| 97 | - Object.setPrototypeOf(i, esIteratorPrototype) | ||
| 98 | - // esIteratorPrototype needs to be the prototype of i | ||
| 99 | - // which is the prototype of an empty object. Yes, it's confusing. | ||
| 100 | - return Object.setPrototypeOf({}, i) | ||
| 101 | - } | ||
| 102 | - | ||
| 103 | 77 | class HeadersList { | |
| 104 | 78 | constructor (init) { | |
| 105 | 79 | if (init instanceof HeadersList) { | |
@@ -306,23 +280,23 @@ class Headers { | |||
| 306 | 280 | throw new TypeError('Illegal invocation') | |
| 307 | 281 | } | |
| 308 | 282 | ||
| 309 | - return makeHeadersIterator(this[kHeadersSortedMap].keys()) | ||
| 283 | + return makeIterator(this[kHeadersSortedMap].keys(), 'Headers') | ||
| 310 | 284 | } | |
| 311 | 285 | ||
| 312 | 286 | values () { | |
| 313 | 287 | if (!(this instanceof Headers)) { | |
| 314 | 288 | throw new TypeError('Illegal invocation') | |
| 315 | 289 | } | |
| 316 | 290 | ||
| 317 | - return makeHeadersIterator(this[kHeadersSortedMap].values()) | ||
| 291 | + return makeIterator(this[kHeadersSortedMap].values(), 'Headers') | ||
| 318 | 292 | } | |
| 319 | 293 | ||
| 320 | 294 | entries () { | |
| 321 | 295 | if (!(this instanceof Headers)) { | |
| 322 | 296 | throw new TypeError('Illegal invocation') | |
| 323 | 297 | } | |
| 324 | 298 | ||
| 325 | - return makeHeadersIterator(this[kHeadersSortedMap].entries()) | ||
| 299 | + return makeIterator(this[kHeadersSortedMap].entries(), 'Headers') | ||
| 326 | 300 | } | |
| 327 | 301 | ||
| 328 | 302 | /** | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1164,7 +1164,7 @@ async function httpRedirectFetch (fetchParams, response) { | |||
| 1164 | 1164 | if ( | |
| 1165 | 1165 | ([301, 302].includes(actualResponse.status) && request.method === 'POST') || | |
| 1166 | 1166 | (actualResponse.status === 303 && | |
| 1167 | - !['GET', 'HEADER'].includes(request.method)) | ||
| 1167 | + !['GET', 'HEAD'].includes(request.method)) | ||
| 1168 | 1168 | ) { | |
| 1169 | 1169 | // then: | |
| 1170 | 1170 | // 1. Set request’s method to `GET` and request’s body to null. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -361,6 +361,33 @@ function serializeJavascriptValueToJSONString (value) { | |||
| 361 | 361 | return result | |
| 362 | 362 | } | |
| 363 | 363 | ||
| 364 | + // https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object | ||
| 365 | + const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())) | ||
| 366 | + | ||
| 367 | + // https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object | ||
| 368 | + function makeIterator (iterator, name) { | ||
| 369 | + const i = { | ||
| 370 | + next () { | ||
| 371 | + if (Object.getPrototypeOf(this) !== i) { | ||
| 372 | + throw new TypeError( | ||
| 373 | + `'next' called on an object that does not implement interface ${name} Iterator.` | ||
| 374 | + ) | ||
| 375 | + } | ||
| 376 | + | ||
| 377 | + return iterator.next() | ||
| 378 | + }, | ||
| 379 | + // The class string of an iterator prototype object for a given interface is the | ||
| 380 | + // result of concatenating the identifier of the interface and the string " Iterator". | ||
| 381 | + [Symbol.toStringTag]: `${name} Iterator` | ||
| 382 | + } | ||
| 383 | + | ||
| 384 | + // The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%. | ||
| 385 | + Object.setPrototypeOf(i, esIteratorPrototype) | ||
| 386 | + // esIteratorPrototype needs to be the prototype of i | ||
| 387 | + // which is the prototype of an empty object. Yes, it's confusing. | ||
| 388 | + return Object.setPrototypeOf({}, i) | ||
| 389 | + } | ||
| 390 | + | ||
| 364 | 391 | module.exports = { | |
| 365 | 392 | isAborted, | |
| 366 | 393 | isCancelled, | |
@@ -390,5 +417,6 @@ module.exports = { | |||
| 390 | 417 | isValidReasonPhrase, | |
| 391 | 418 | sameOrigin, | |
| 392 | 419 | normalizeMethod, | |
| 393 | - serializeJavascriptValueToJSONString | ||
| 420 | + serializeJavascriptValueToJSONString, | ||
| 421 | + makeIterator | ||
| 394 | 422 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments