diff --git a/lib/fetch/index.js b/lib/fetch/index.js index 4471ee5d57c..b3b6e71eb79 100644 --- a/lib/fetch/index.js +++ b/lib/fetch/index.js @@ -9,7 +9,7 @@ const { filterResponse, makeResponse } = require('./response') -const { Headers } = require('./headers') +const { Headers, HeadersList } = require('./headers') const { Request, makeRequest } = require('./request') const zlib = require('zlib') const { @@ -2075,7 +2075,7 @@ async function httpNetworkFetch ( // 20. Return response. return response - async function dispatch ({ body }) { + function dispatch ({ body }) { const url = requestCurrentURL(request) /** @type {import('../..').Agent} */ const agent = fetchParams.controller.dispatcher @@ -2085,7 +2085,7 @@ async function httpNetworkFetch ( path: url.pathname + url.search, origin: url.origin, method: request.method, - body: fetchParams.controller.dispatcher.isMockActive ? request.body && (request.body.source || request.body.stream) : body, + body: agent.isMockActive ? request.body && (request.body.source || request.body.stream) : body, headers: request.headersList.entries, maxRedirections: 0, upgrade: request.mode === 'websocket' ? 'websocket' : undefined @@ -2106,59 +2106,57 @@ async function httpNetworkFetch ( } }, - onHeaders (status, headersList, resume, statusText) { + onHeaders (status, rawHeaders, resume, statusText) { if (status < 200) { return } + /** @type {string[]} */ let codings = [] let location = '' - const headers = new Headers() + const headersList = new HeadersList() - // For H2, the headers are a plain JS object + // For H2, the rawHeaders are a plain JS object // We distinguish between them and iterate accordingly - if (Array.isArray(headersList)) { - for (let n = 0; n < headersList.length; n += 2) { - const key = headersList[n + 0].toString('latin1') - const val = headersList[n + 1].toString('latin1') - if (key.toLowerCase() === 'content-encoding') { - // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1 - // "All content-coding values are case-insensitive..." - codings = val.toLowerCase().split(',').map((x) => x.trim()) - } else if (key.toLowerCase() === 'location') { - location = val - } - - headers[kHeadersList].append(key, val) + if (Array.isArray(rawHeaders)) { + for (let i = 0; i < rawHeaders.length; i += 2) { + headersList.append(rawHeaders[i].toString('latin1'), rawHeaders[i + 1].toString('latin1')) } + const contentEncoding = headersList.get('content-encoding') + if (contentEncoding) { + // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1 + // "All content-coding values are case-insensitive..." + codings = contentEncoding.toLowerCase().split(',').map((x) => x.trim()) + } + location = headersList.get('location') } else { - const keys = Object.keys(headersList) - for (const key of keys) { - const val = headersList[key] - if (key.toLowerCase() === 'content-encoding') { - // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1 - // "All content-coding values are case-insensitive..." - codings = val.toLowerCase().split(',').map((x) => x.trim()).reverse() - } else if (key.toLowerCase() === 'location') { - location = val - } - - headers[kHeadersList].append(key, val) + const keys = Object.keys(rawHeaders) + for (let i = 0; i < keys.length; ++i) { + headersList.append(keys[i], rawHeaders[keys[i]]) + } + // For H2, The header names are already in lowercase, + // so we can avoid the `HeadersList#get` call here. + const contentEncoding = rawHeaders['content-encoding'] + if (contentEncoding) { + // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1 + // "All content-coding values are case-insensitive..." + codings = contentEncoding.toLowerCase().split(',').map((x) => x.trim()).reverse() } + location = rawHeaders.location } this.body = new Readable({ read: resume }) const decoders = [] - const willFollow = request.redirect === 'follow' && - location && + const willFollow = location && request.redirect === 'follow' && redirectStatusSet.has(status) // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding if (request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) { - for (const coding of codings) { + for (let i = 0; i < codings.length; ++i) { + const coding = codings[i] // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2 if (coding === 'x-gzip' || coding === 'gzip') { decoders.push(zlib.createGunzip({ @@ -2183,7 +2181,7 @@ async function httpNetworkFetch ( resolve({ status, statusText, - headersList: headers[kHeadersList], + headersList, body: decoders.length ? pipeline(this.body, ...decoders, () => { }) : this.body.on('error', () => {}) @@ -2237,24 +2235,21 @@ async function httpNetworkFetch ( reject(error) }, - onUpgrade (status, headersList, socket) { + onUpgrade (status, rawHeaders, socket) { if (status !== 101) { return } - const headers = new Headers() - - for (let n = 0; n < headersList.length; n += 2) { - const key = headersList[n + 0].toString('latin1') - const val = headersList[n + 1].toString('latin1') + const headersList = new HeadersList() - headers[kHeadersList].append(key, val) + for (let i = 0; i < rawHeaders.length; i += 2) { + headersList.append(rawHeaders[i].toString('latin1'), rawHeaders[i + 1].toString('latin1')) } resolve({ status, statusText: STATUS_CODES[status], - headersList: headers[kHeadersList], + headersList, socket })