Visitar URL original
perf(fetch): expose inner fetch primitives for Node.js · nodejs/undici@b409624 · GitHub
Skip to content

Commit b409624

Browse files
perf(fetch): expose inner fetch primitives for Node.js
Expose HeadersList, getResponseState, makeRequest and fromInnerRequest so embedders can construct and inspect inner request/response state cheaply, taking advantage of the lazy header/URL/body plumbing. These are not part of undici's public API.
1 parent 986bbbf commit b409624

4 files changed

Lines changed: 41 additions & 14 deletions

File tree

‎index-fetch.js‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,20 @@ module.exports.fetch = function fetch (init, options = undefined) {
4343
})
4444
}
4545
module.exports.FormData = require('./lib/web/fetch/formdata').FormData
46-
module.exports.Headers = require('./lib/web/fetch/headers').Headers
47-
module.exports.Response = require('./lib/web/fetch/response').Response
48-
module.exports.Request = require('./lib/web/fetch/request').Request
46+
const { Headers, HeadersList } = require('./lib/web/fetch/headers')
47+
module.exports.Headers = Headers
48+
module.exports.HeadersList = HeadersList
49+
const { Response, getResponseState } = require('./lib/web/fetch/response')
50+
module.exports.Response = Response
51+
module.exports.getResponseState = getResponseState
52+
const {
53+
Request,
54+
makeRequest,
55+
fromInnerRequest
56+
} = require('./lib/web/fetch/request')
57+
module.exports.Request = Request
58+
module.exports.makeRequest = makeRequest
59+
module.exports.fromInnerRequest = fromInnerRequest
4960

5061
const { CloseEvent, ErrorEvent, MessageEvent, createFastMessageEvent } = require('./lib/web/websocket/events')
5162
module.exports.WebSocket = require('./lib/web/websocket/websocket').WebSocket

‎lib/web/cache/cache.js‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ const assert = require('node:assert')
44

55
const { kConstruct } = require('../../core/symbols')
66
const { urlEquals, getFieldValues } = require('./util')
7-
const { kEnumerableProperty, isDisturbed } = require('../../core/util')
7+
const { kEnumerableProperty } = require('../../core/util')
88
const { webidl } = require('../webidl')
99
const { cloneResponse, fromInnerResponse, getResponseState } = require('../fetch/response')
10+
const { isBodyStreamDisturbedOrLocked } = require('../fetch/body')
1011
const { Request, fromInnerRequest, getRequestState } = require('../fetch/request')
1112
const { fetching } = require('../fetch/index')
1213
const { urlIsHttpHttpsScheme, readAllBytes } = require('../fetch/util')
@@ -312,11 +313,9 @@ class Cache {
312313
}
313314

314315
// 8.
315-
// A lazily-materialized response body (e.g. `new Response('...')`) has a
316-
// null stream until it is first observed; an unobserved body cannot be
317-
// disturbed or locked, so treat a null stream as neither. The subsequent
318-
// clone (step 9) materializes the stream before it is read in step 11.
319-
if (innerResponse.body && innerResponse.body.stream != null && (isDisturbed(innerResponse.body.stream) || innerResponse.body.stream.locked)) {
316+
// The subsequent clone (step 9) materializes a lazy body's stream before it
317+
// is read in step 11, so an as-yet-unobserved (null) stream is fine here.
318+
if (isBodyStreamDisturbedOrLocked(innerResponse.body)) {
320319
throw webidl.errors.exception({
321320
header: prefix,
322321
message: 'Response body is locked or disturbed'

‎lib/web/fetch/body.js‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,21 @@ function consumeBody (object, convertBytesToJSValue, instance, getInternalState)
616616
return promise.promise
617617
}
618618

619+
/**
620+
* Whether a body's stream is disturbed or locked.
621+
*
622+
* A lazily-materialized body (e.g. `new Response('...')`) has a null stream
623+
* until it is first observed; an unobserved stream can be neither disturbed nor
624+
* locked, so a null stream counts as neither.
625+
*
626+
* @param {{ stream: ReadableStream | null } | null | undefined} body a body, or null
627+
* @returns {boolean}
628+
*/
629+
function isBodyStreamDisturbedOrLocked (body) {
630+
return body != null && body.stream != null &&
631+
(util.isDisturbed(body.stream) || body.stream.locked)
632+
}
633+
619634
/**
620635
* @see https://fetch.spec.whatwg.org/#body-unusable
621636
* @param {any} object internal state
@@ -665,6 +680,7 @@ module.exports = {
665680
extractBody,
666681
safelyExtractBody,
667682
makeLazyByteStreamBody,
683+
isBodyStreamDisturbedOrLocked,
668684
cloneBody,
669685
mixinBody,
670686
streamRegistry,

‎lib/web/fetch/response.js‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,12 @@ class Response {
251251
get bodyUsed () {
252252
webidl.brandCheck(this, Response)
253253

254-
// If the stream was never materialized it cannot have been disturbed, so a
255-
// null stream means the body is unused (avoids forcing stream creation).
256-
return !!this.#state.body &&
257-
this.#state.body.stream != null &&
258-
util.isDisturbed(this.#state.body.stream)
254+
// `consumed` covers lazy bodies read via an embedder fast path where the stream is never built;
255+
// otherwise fall back to the stream's disturbed state once it exists. A never-materialized
256+
// stream cannot have been disturbed, so it reads as unused.
257+
const body = this.#state.body
258+
return !!body && (body.consumed === true ||
259+
(body.stream != null && util.isDisturbed(body.stream)))
259260
}
260261

261262
// Returns a clone of response.

0 commit comments

Comments
 (0)