diff --git a/docs/docs/api/DiagnosticsChannel.md b/docs/docs/api/DiagnosticsChannel.md index 099c072f6c6..dc7951eb92c 100644 --- a/docs/docs/api/DiagnosticsChannel.md +++ b/docs/docs/api/DiagnosticsChannel.md @@ -21,7 +21,7 @@ diagnosticsChannel.channel('undici:request:create').subscribe(({ request }) => { console.log('path', request.path) console.log('headers') // array of strings, e.g: ['foo', 'bar'] request.addHeader('hello', 'world') - console.log('headers', request.headers) // e.g. ['foo', 'bar', 'hello', 'world'] + console.log('headers', request.headers) // e.g. {'foo': 'bar', 'hello': 'world'] }) ``` @@ -49,8 +49,8 @@ diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, respo // request is the same object undici:request:create console.log('statusCode', response.statusCode) console.log(response.statusText) - // response.headers are buffers. - console.log(response.headers.map((x) => x.toString())) + // response.headers are an object. + console.log(response.headers) }) ``` @@ -64,8 +64,8 @@ import diagnosticsChannel from 'diagnostics_channel' diagnosticsChannel.channel('undici:request:trailers').subscribe(({ request, trailers }) => { // request is the same object undici:request:create console.log('completed', request.completed) - // trailers are buffers. - console.log(trailers.map((x) => x.toString())) + // trailers are an object. + console.log(trailers) }) ``` diff --git a/docs/docs/api/Dispatcher.md b/docs/docs/api/Dispatcher.md index 67819ecd525..06813e907ce 100644 --- a/docs/docs/api/Dispatcher.md +++ b/docs/docs/api/Dispatcher.md @@ -208,9 +208,9 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo * **onConnect** `(abort: () => void, context: object) => void` - Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails. * **onError** `(error: Error) => void` - Invoked when an error has occurred. May not throw. -* **onUpgrade** `(statusCode: number, headers: Buffer[], socket: Duplex) => void` (optional) - Invoked when request is upgraded. Required if `DispatchOptions.upgrade` is defined or `DispatchOptions.method === 'CONNECT'`. +* **onUpgrade** `(statusCode: number, headers: Record, socket: Duplex) => void` (optional) - Invoked when request is upgraded. Required if `DispatchOptions.upgrade` is defined or `DispatchOptions.method === 'CONNECT'`. * **onResponseStarted** `() => void` (optional) - Invoked when response is received, before headers have been read. -* **onHeaders** `(statusCode: number, headers: Buffer[], resume: () => void, statusText: string) => boolean` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests. +* **onHeaders** `(statusCode: number, headers: Record, resume: () => void, statusText: string) => boolean` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests. * **onData** `(chunk: Buffer) => boolean` - Invoked when response payload data is received. Not required for `upgrade` requests. * **onComplete** `(trailers: Buffer[]) => void` - Invoked when response payload and trailers have been received and the request has completed. Not required for `upgrade` requests. * **onBodySent** `(chunk: string | Buffer | Uint8Array) => void` - Invoked when a body chunk is sent to the server. Not required. For a stream or iterable body this will be invoked for every chunk. For other body types, it will be invoked once after the body is sent. @@ -975,7 +975,7 @@ const client = new Client("http://example.com").compose( }) ); -// or +// or client.dispatch( { path: "/", diff --git a/lib/api/api-connect.js b/lib/api/api-connect.js index c8b86dd7d53..d1b4416d6d5 100644 --- a/lib/api/api-connect.js +++ b/lib/api/api-connect.js @@ -3,7 +3,6 @@ const assert = require('node:assert') const { AsyncResource } = require('node:async_hooks') const { InvalidArgumentError, SocketError } = require('../core/errors') -const util = require('../core/util') const { addSignal, removeSignal } = require('./abort-signal') class ConnectHandler extends AsyncResource { @@ -22,10 +21,13 @@ class ConnectHandler extends AsyncResource { throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') } + if (responseHeaders != null) { + throw new InvalidArgumentError('responseHeaders must be nully') + } + super('UNDICI_CONNECT') this.opaque = opaque || null - this.responseHeaders = responseHeaders || null this.callback = callback this.abort = null @@ -48,19 +50,13 @@ class ConnectHandler extends AsyncResource { throw new SocketError('bad connect', null) } - onUpgrade (statusCode, rawHeaders, socket) { + onUpgrade (statusCode, headers, socket) { const { callback, opaque, context } = this removeSignal(this) this.callback = null - let headers = rawHeaders - // Indicates is an HTTP2Session - if (headers != null) { - headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) - } - this.runInAsyncScope(callback, null, null, { statusCode, headers, diff --git a/lib/api/api-pipeline.js b/lib/api/api-pipeline.js index 1516e5dbd51..917085e814c 100644 --- a/lib/api/api-pipeline.js +++ b/lib/api/api-pipeline.js @@ -79,6 +79,10 @@ class PipelineHandler extends AsyncResource { throw new InvalidArgumentError('invalid method') } + if (responseHeaders != null) { + throw new InvalidArgumentError('responseHeaders must be nully') + } + if (onInfo && typeof onInfo !== 'function') { throw new InvalidArgumentError('invalid onInfo callback') } @@ -86,7 +90,6 @@ class PipelineHandler extends AsyncResource { super('UNDICI_PIPELINE') this.opaque = opaque || null - this.responseHeaders = responseHeaders || null this.handler = handler this.abort = null this.context = null @@ -158,12 +161,11 @@ class PipelineHandler extends AsyncResource { this.context = context } - onHeaders (statusCode, rawHeaders, resume) { + onHeaders (statusCode, headers, resume) { const { opaque, handler, context } = this if (statusCode < 200) { if (this.onInfo) { - const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) this.onInfo({ statusCode, headers }) } return @@ -174,7 +176,6 @@ class PipelineHandler extends AsyncResource { let body try { this.handler = null - const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) body = this.runInAsyncScope(handler, null, { statusCode, headers, diff --git a/lib/api/api-request.js b/lib/api/api-request.js index b988c2e7496..4b28b999841 100644 --- a/lib/api/api-request.js +++ b/lib/api/api-request.js @@ -36,6 +36,10 @@ class RequestHandler extends AsyncResource { throw new InvalidArgumentError('invalid onInfo callback') } + if (responseHeaders != null) { + throw new InvalidArgumentError('responseHeaders must be nully') + } + super('UNDICI_REQUEST') } catch (err) { if (util.isStream(body)) { @@ -45,7 +49,6 @@ class RequestHandler extends AsyncResource { } this.method = method - this.responseHeaders = responseHeaders || null this.opaque = opaque || null this.callback = callback this.res = null @@ -85,10 +88,8 @@ class RequestHandler extends AsyncResource { this.context = context } - onHeaders (statusCode, rawHeaders, resume, statusMessage) { - const { callback, opaque, abort, context, responseHeaders, highWaterMark } = this - - const headers = responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + onHeaders (statusCode, headers, resume, statusMessage) { + const { callback, opaque, abort, context, highWaterMark } = this if (statusCode < 200) { if (this.onInfo) { @@ -97,9 +98,8 @@ class RequestHandler extends AsyncResource { return } - const parsedHeaders = responseHeaders === 'raw' ? util.parseHeaders(rawHeaders) : headers - const contentType = parsedHeaders['content-type'] - const contentLength = parsedHeaders['content-length'] + const contentType = headers['content-type'] + const contentLength = headers['content-length'] const res = new Readable({ resume, abort, @@ -140,7 +140,7 @@ class RequestHandler extends AsyncResource { } onComplete (trailers) { - util.parseHeaders(trailers, this.trailers) + Object.assign(this.trailers, trailers) this.res.push(null) } diff --git a/lib/api/api-stream.js b/lib/api/api-stream.js index 50f61632365..9bf535e1471 100644 --- a/lib/api/api-stream.js +++ b/lib/api/api-stream.js @@ -37,6 +37,10 @@ class StreamHandler extends AsyncResource { throw new InvalidArgumentError('invalid onInfo callback') } + if (responseHeaders != null) { + throw new InvalidArgumentError('responseHeaders must be nully') + } + super('UNDICI_STREAM') } catch (err) { if (util.isStream(body)) { @@ -45,7 +49,6 @@ class StreamHandler extends AsyncResource { throw err } - this.responseHeaders = responseHeaders || null this.opaque = opaque || null this.factory = factory this.callback = callback @@ -78,10 +81,8 @@ class StreamHandler extends AsyncResource { this.context = context } - onHeaders (statusCode, rawHeaders, resume, statusMessage) { - const { factory, opaque, context, callback, responseHeaders } = this - - const headers = responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + onHeaders (statusCode, headers, resume, statusMessage) { + const { factory, opaque, context, callback } = this if (statusCode < 200) { if (this.onInfo) { @@ -95,8 +96,7 @@ class StreamHandler extends AsyncResource { let res if (this.throwOnError && statusCode >= 400) { - const parsedHeaders = responseHeaders === 'raw' ? util.parseHeaders(rawHeaders) : headers - const contentType = parsedHeaders['content-type'] + const contentType = headers['content-type'] res = new PassThrough() this.callback = null @@ -168,7 +168,7 @@ class StreamHandler extends AsyncResource { return } - this.trailers = util.parseHeaders(trailers) + this.trailers = trailers res.end() } diff --git a/lib/api/api-upgrade.js b/lib/api/api-upgrade.js index 6c2076d5ac1..24f9f1a2ee7 100644 --- a/lib/api/api-upgrade.js +++ b/lib/api/api-upgrade.js @@ -3,7 +3,6 @@ const { InvalidArgumentError, SocketError } = require('../core/errors') const { AsyncResource } = require('node:async_hooks') const assert = require('node:assert') -const util = require('../core/util') const { addSignal, removeSignal } = require('./abort-signal') class UpgradeHandler extends AsyncResource { @@ -22,9 +21,12 @@ class UpgradeHandler extends AsyncResource { throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') } + if (responseHeaders != null) { + throw new InvalidArgumentError('responseHeaders must be nully') + } + super('UNDICI_UPGRADE') - this.responseHeaders = responseHeaders || null this.opaque = opaque || null this.callback = callback this.abort = null @@ -49,7 +51,7 @@ class UpgradeHandler extends AsyncResource { throw new SocketError('bad upgrade', null) } - onUpgrade (statusCode, rawHeaders, socket) { + onUpgrade (statusCode, headers, socket) { const { callback, opaque, context } = this assert.strictEqual(statusCode, 101) @@ -57,7 +59,6 @@ class UpgradeHandler extends AsyncResource { removeSignal(this) this.callback = null - const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) this.runInAsyncScope(callback, null, null, { headers, socket, diff --git a/lib/core/request.js b/lib/core/request.js index 78003038ba9..e93441f66c2 100644 --- a/lib/core/request.js +++ b/lib/core/request.js @@ -17,7 +17,8 @@ const { buildURL, validateHandler, getServerName, - normalizedMethodRecords + normalizedMethodRecords, + parseHeaders } = require('./util') const { channels } = require('./diagnostics.js') const { headerNameLowerCasedRecord } = require('./constants') @@ -232,10 +233,12 @@ class Request { return this[kHandler].onResponseStarted?.() } - onHeaders (statusCode, headers, resume, statusText) { + onHeaders (statusCode, rawHeaders, resume, statusText) { assert(!this.aborted) assert(!this.completed) + const headers = parseHeaders(rawHeaders) + if (channels.headers.hasSubscribers) { channels.headers.publish({ request: this, response: { statusCode, headers, statusText } }) } @@ -259,18 +262,20 @@ class Request { } } - onUpgrade (statusCode, headers, socket) { + onUpgrade (statusCode, rawHeaders, socket) { assert(!this.aborted) assert(!this.completed) - return this[kHandler].onUpgrade(statusCode, headers, socket) + return this[kHandler].onUpgrade(statusCode, parseHeaders(rawHeaders), socket) } - onComplete (trailers) { + onComplete (rawTrailers) { this.onFinally() assert(!this.aborted) + const trailers = parseHeaders(rawTrailers) + this.completed = true if (channels.trailers.hasSubscribers) { channels.trailers.publish({ request: this, trailers }) diff --git a/lib/core/util.js b/lib/core/util.js index 9b566ef2962..2b9dad0e52b 100644 --- a/lib/core/util.js +++ b/lib/core/util.js @@ -332,6 +332,7 @@ function bufferToLowerCasedHeaderName (value) { */ function parseHeaders (headers, obj) { if (obj === undefined) obj = {} + if (headers == null) return for (let i = 0; i < headers.length; i += 2) { const key = headerNameToString(headers[i]) let val = obj[key] @@ -341,13 +342,13 @@ function parseHeaders (headers, obj) { val = [val] obj[key] = val } - val.push(headers[i + 1].toString('utf8')) + val.push(headers[i + 1].toString('latin1')) } else { const headersValue = headers[i + 1] if (typeof headersValue === 'string') { obj[key] = headersValue } else { - obj[key] = Array.isArray(headersValue) ? headersValue.map(x => x.toString('utf8')) : headersValue.toString('utf8') + obj[key] = Array.isArray(headersValue) ? headersValue.map(x => x.toString('latin1')) : headersValue.toString('latin1') } } } diff --git a/lib/handler/redirect-handler.js b/lib/handler/redirect-handler.js index 3db7a476d45..066d8e9a66c 100644 --- a/lib/handler/redirect-handler.js +++ b/lib/handler/redirect-handler.js @@ -191,15 +191,9 @@ class RedirectHandler { } function parseLocation (statusCode, headers) { - if (redirectableStatusCodes.indexOf(statusCode) === -1) { - return null - } - - for (let i = 0; i < headers.length; i += 2) { - if (headers[i].length === 8 && util.headerNameToString(headers[i]) === 'location') { - return headers[i + 1] - } - } + return redirectableStatusCodes.indexOf(statusCode) === -1 + ? null + : headers.location } // https://tools.ietf.org/html/rfc7231#section-6.4.4 diff --git a/lib/handler/retry-handler.js b/lib/handler/retry-handler.js index e00e82faf08..5704bf7267f 100644 --- a/lib/handler/retry-handler.js +++ b/lib/handler/retry-handler.js @@ -5,7 +5,6 @@ const { kRetryHandlerDefaultRetry } = require('../core/symbols') const { RequestRetryError } = require('../core/errors') const { isDisturbed, - parseHeaders, parseRangeHeader, wrapRequestBody } = require('../core/util') @@ -162,16 +161,14 @@ class RetryHandler { setTimeout(() => cb(null), retryTimeout) } - onHeaders (statusCode, rawHeaders, resume, statusMessage) { - const headers = parseHeaders(rawHeaders) - + onHeaders (statusCode, headers, resume, statusMessage) { this.retryCount += 1 if (statusCode >= 300) { if (this.retryOpts.statusCodes.includes(statusCode) === false) { return this.handler.onHeaders( statusCode, - rawHeaders, + headers, resume, statusMessage ) @@ -246,7 +243,7 @@ class RetryHandler { if (range == null) { return this.handler.onHeaders( statusCode, - rawHeaders, + headers, resume, statusMessage ) @@ -287,7 +284,7 @@ class RetryHandler { return this.handler.onHeaders( statusCode, - rawHeaders, + headers, resume, statusMessage ) @@ -309,9 +306,9 @@ class RetryHandler { return this.handler.onData(chunk) } - onComplete (rawTrailers) { + onComplete (trailers) { this.retryCount = 0 - return this.handler.onComplete(rawTrailers) + return this.handler.onComplete(trailers) } onError (err) { diff --git a/lib/interceptor/dump.js b/lib/interceptor/dump.js index fc9cacb198d..6b5938ba259 100644 --- a/lib/interceptor/dump.js +++ b/lib/interceptor/dump.js @@ -1,6 +1,5 @@ 'use strict' -const util = require('../core/util') const { InvalidArgumentError, RequestAbortedError } = require('../core/errors') const DecoratorHandler = require('../handler/decorator-handler') @@ -36,8 +35,7 @@ class DumpHandler extends DecoratorHandler { } // TODO: will require adjustment after new hooks are out - onHeaders (statusCode, rawHeaders, resume, statusMessage) { - const headers = util.parseHeaders(rawHeaders) + onHeaders (statusCode, headers, resume, statusMessage) { const contentLength = headers['content-length'] if (contentLength != null && contentLength > this.#maxSize) { @@ -54,7 +52,7 @@ class DumpHandler extends DecoratorHandler { return this.#handler.onHeaders( statusCode, - rawHeaders, + headers, resume, statusMessage ) diff --git a/lib/mock/mock-symbols.js b/lib/mock/mock-symbols.js index 8c4cbb60e16..e2a9132af9e 100644 --- a/lib/mock/mock-symbols.js +++ b/lib/mock/mock-symbols.js @@ -4,6 +4,7 @@ module.exports = { kAgent: Symbol('agent'), kOptions: Symbol('options'), kFactory: Symbol('factory'), + kOriginalDispatch: Symbol('original dispatch'), kDispatches: Symbol('dispatches'), kDispatchKey: Symbol('dispatch key'), kDefaultHeaders: Symbol('default headers'), diff --git a/lib/mock/mock-utils.js b/lib/mock/mock-utils.js index f3c284d7891..85fd1d79727 100644 --- a/lib/mock/mock-utils.js +++ b/lib/mock/mock-utils.js @@ -8,7 +8,7 @@ const { kOrigin, kGetNetConnect } = require('./mock-symbols') -const { buildURL } = require('../core/util') +const { buildURL, parseHeaders } = require('../core/util') const { STATUS_CODES } = require('node:http') const { types: { @@ -282,8 +282,8 @@ function mockDispatch (opts, handler) { } const responseData = getResponseData(body) - const responseHeaders = generateKeyValues(headers) - const responseTrailers = generateKeyValues(trailers) + const responseHeaders = parseHeaders(generateKeyValues(headers)) + const responseTrailers = parseHeaders(generateKeyValues(trailers)) handler.onConnect?.(err => handler.onError(err), null) handler.onHeaders?.(statusCode, responseHeaders, resume, getStatusText(statusCode)) diff --git a/lib/web/fetch/index.js b/lib/web/fetch/index.js index 406ed7f31cb..e8450659756 100644 --- a/lib/web/fetch/index.js +++ b/lib/web/fetch/index.js @@ -59,7 +59,7 @@ const { } = require('./constants') const EE = require('node:events') const { Readable, pipeline, finished } = require('node:stream') -const { addAbortListener, isErrored, isReadable, bufferToLowerCasedHeaderName } = require('../../core/util') +const { addAbortListener, isErrored, isReadable } = require('../../core/util') const { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require('./data-url') const { getGlobalDispatcher } = require('../../global') const { webidl } = require('./webidl') @@ -2106,7 +2106,7 @@ async function httpNetworkFetch ( timingInfo.finalNetworkResponseStartTime = coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability) }, - onHeaders (status, rawHeaders, resume, statusText) { + onHeaders (status, headers, resume, statusText) { if (status < 200) { return } @@ -2117,9 +2117,16 @@ async function httpNetworkFetch ( const headersList = new HeadersList() - for (let i = 0; i < rawHeaders.length; i += 2) { - headersList.append(bufferToLowerCasedHeaderName(rawHeaders[i]), rawHeaders[i + 1].toString('latin1'), true) + for (const [key, val] of Object.entries(headers)) { + if (Array.isArray(val)) { + for (const x of val) { + headersList.append(key, x, true) + } + } else { + headersList.append(key, val, true) + } } + const contentEncoding = headersList.get('content-encoding', true) if (contentEncoding) { // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1 @@ -2221,15 +2228,21 @@ async function httpNetworkFetch ( reject(error) }, - onUpgrade (status, rawHeaders, socket) { + onUpgrade (status, headers, socket) { if (status !== 101) { return } const headersList = new HeadersList() - for (let i = 0; i < rawHeaders.length; i += 2) { - headersList.append(bufferToLowerCasedHeaderName(rawHeaders[i]), rawHeaders[i + 1].toString('latin1'), true) + for (const [key, val] of Object.entries(headers)) { + if (Array.isArray(val)) { + for (const x of val) { + headersList.append(key, x, true) + } + } else { + headersList.append(key, val, true) + } } resolve({ diff --git a/test/client-request.js b/test/client-request.js index c67cecdb7f3..c29b56f919f 100644 --- a/test/client-request.js +++ b/test/client-request.js @@ -881,43 +881,6 @@ test('request onInfo callback headers parsing', async (t) => { t.ok(true, 'pass') }) -test('request raw responseHeaders', async (t) => { - t = tspl(t, { plan: 4 }) - const infos = [] - - const server = net.createServer((socket) => { - const lines = [ - 'HTTP/1.1 103 Early Hints', - 'Link: ; rel=preload; as=style', - '', - 'HTTP/1.1 200 OK', - 'Date: Sat, 09 Oct 2010 14:28:02 GMT', - 'Connection: close', - '', - 'the body' - ] - socket.end(lines.join('\r\n')) - }) - after(() => server.close()) - - await promisify(server.listen.bind(server))(0) - - const client = new Client(`http://localhost:${server.address().port}`) - after(() => client.close()) - - const { body, headers } = await client.request({ - path: '/', - method: 'GET', - responseHeaders: 'raw', - onInfo: (x) => { infos.push(x) } - }) - await body.dump() - t.strictEqual(infos.length, 1) - t.deepStrictEqual(infos[0].headers, ['Link', '; rel=preload; as=style']) - t.deepStrictEqual(headers, ['Date', 'Sat, 09 Oct 2010 14:28:02 GMT', 'Connection', 'close']) - t.ok(true, 'pass') -}) - test('request formData', async (t) => { t = tspl(t, { plan: 1 }) diff --git a/test/http2.js b/test/http2.js index a43700574b8..62dcad3c198 100644 --- a/test/http2.js +++ b/test/http2.js @@ -819,7 +819,7 @@ test('Should handle h2 request with body (string or buffer) - dispatch', async t stream.end('hello h2!') }) - t = tspl(t, { plan: 9 }) + t = tspl(t, { plan: 7 }) server.listen(0, () => { const client = new Client(`https://localhost:${server.address().port}`, { @@ -851,10 +851,8 @@ test('Should handle h2 request with body (string or buffer) - dispatch', async t }, onHeaders (statusCode, headers) { t.strictEqual(statusCode, 200) - t.strictEqual(headers[0].toString('utf-8'), 'content-type') - t.strictEqual(headers[1].toString('utf-8'), 'text/plain; charset=utf-8') - t.strictEqual(headers[2].toString('utf-8'), 'x-custom-h2') - t.strictEqual(headers[3].toString('utf-8'), 'foo') + t.strictEqual(headers['content-type'], 'text/plain; charset=utf-8') + t.strictEqual(headers['x-custom-h2'], 'foo') }, onData (chunk) { response.push(chunk) diff --git a/test/node-test/client-dispatch.js b/test/node-test/client-dispatch.js index f9ed888d44b..370ed1a9af5 100644 --- a/test/node-test/client-dispatch.js +++ b/test/node-test/client-dispatch.js @@ -126,13 +126,13 @@ test('basic dispatch get', async (t) => { }, onHeaders (statusCode, headers) { p.strictEqual(statusCode, 200) - p.strictEqual(Array.isArray(headers), true) + p.ok(Object.keys(headers).length > 0) }, onData (buf) { bufs.push(buf) }, onComplete (trailers) { - p.deepStrictEqual(trailers, []) + p.deepStrictEqual(trailers, {}) p.strictEqual('hello', Buffer.concat(bufs).toString('utf8')) }, onError () { @@ -145,7 +145,7 @@ test('basic dispatch get', async (t) => { }) test('trailers dispatch get', async (t) => { - const p = tspl(t, { plan: 12 }) + const p = tspl(t, { plan: 11 }) const server = http.createServer((req, res) => { p.strictEqual('/', req.url) @@ -180,21 +180,14 @@ test('trailers dispatch get', async (t) => { }, onHeaders (statusCode, headers) { p.strictEqual(statusCode, 200) - p.strictEqual(Array.isArray(headers), true) - { - const contentTypeIdx = headers.findIndex(x => x.toString() === 'Content-Type') - p.strictEqual(headers[contentTypeIdx + 1].toString(), 'text/plain') - } + p.ok(Object.keys(headers).length > 0) + p.strictEqual(headers['content-type'], 'text/plain') }, onData (buf) { bufs.push(buf) }, onComplete (trailers) { - p.strictEqual(Array.isArray(trailers), true) - { - const contentMD5Idx = trailers.findIndex(x => x.toString() === 'Content-MD5') - p.strictEqual(trailers[contentMD5Idx + 1].toString(), 'test') - } + p.strictEqual(trailers['content-md5'], 'test') p.strictEqual('hello', Buffer.concat(bufs).toString('utf8')) }, onError () { diff --git a/test/node-test/diagnostics-channel/get.js b/test/node-test/diagnostics-channel/get.js index 397dfa3bc5f..64e5713af06 100644 --- a/test/node-test/diagnostics-channel/get.js +++ b/test/node-test/diagnostics-channel/get.js @@ -87,20 +87,14 @@ test('Diagnostics channel - get', (t) => { diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, response }) => { assert.equal(_req, request) assert.equal(response.statusCode, 200) - const expectedHeaders = [ - Buffer.from('Content-Type'), - Buffer.from('text/plain'), - Buffer.from('trailer'), - Buffer.from('foo'), - Buffer.from('Date'), - response.headers[5], // This is a date - Buffer.from('Connection'), - Buffer.from('keep-alive'), - Buffer.from('Keep-Alive'), - Buffer.from('timeout=5'), - Buffer.from('Transfer-Encoding'), - Buffer.from('chunked') - ] + const expectedHeaders = { + 'content-type': 'text/plain', + trailer: 'foo', + date: response.headers.date, // This is a date + connection: 'keep-alive', + 'keep-alive': 'timeout=5', + 'transfer-encoding': 'chunked' + } assert.deepStrictEqual(response.headers, expectedHeaders) assert.equal(response.statusText, 'OK') }) @@ -114,7 +108,7 @@ test('Diagnostics channel - get', (t) => { // This event is emitted after the last chunk has been added to the body stream, // not when it was consumed by the application assert.equal(endEmitted, false) - assert.deepStrictEqual(trailers, [Buffer.from('foo'), Buffer.from('oof')]) + assert.deepStrictEqual(trailers, { foo: 'oof' }) resolve() }) diff --git a/test/node-test/diagnostics-channel/post-stream.js b/test/node-test/diagnostics-channel/post-stream.js index 881873a7c1c..659e8d2539c 100644 --- a/test/node-test/diagnostics-channel/post-stream.js +++ b/test/node-test/diagnostics-channel/post-stream.js @@ -89,20 +89,14 @@ test('Diagnostics channel - post stream', (t) => { diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, response }) => { assert.equal(_req, request) assert.equal(response.statusCode, 200) - const expectedHeaders = [ - Buffer.from('Content-Type'), - Buffer.from('text/plain'), - Buffer.from('trailer'), - Buffer.from('foo'), - Buffer.from('Date'), - response.headers[5], // This is a date - Buffer.from('Connection'), - Buffer.from('keep-alive'), - Buffer.from('Keep-Alive'), - Buffer.from('timeout=5'), - Buffer.from('Transfer-Encoding'), - Buffer.from('chunked') - ] + const expectedHeaders = { + 'content-type': 'text/plain', + trailer: 'foo', + date: response.headers.date, // This is a date + connection: 'keep-alive', + 'keep-alive': 'timeout=5', + 'transfer-encoding': 'chunked' + } assert.deepStrictEqual(response.headers, expectedHeaders) assert.equal(response.statusText, 'OK') }) @@ -120,7 +114,7 @@ test('Diagnostics channel - post stream', (t) => { // This event is emitted after the last chunk has been added to the body stream, // not when it was consumed by the application assert.equal(endEmitted, false) - assert.deepStrictEqual(trailers, [Buffer.from('foo'), Buffer.from('oof')]) + assert.deepStrictEqual(trailers, { foo: 'oof' }) resolve() }) diff --git a/test/node-test/diagnostics-channel/post.js b/test/node-test/diagnostics-channel/post.js index 1408ffbf023..cfffc57afd8 100644 --- a/test/node-test/diagnostics-channel/post.js +++ b/test/node-test/diagnostics-channel/post.js @@ -87,20 +87,14 @@ test('Diagnostics channel - post', (t) => { diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, response }) => { assert.equal(_req, request) assert.equal(response.statusCode, 200) - const expectedHeaders = [ - Buffer.from('Content-Type'), - Buffer.from('text/plain'), - Buffer.from('trailer'), - Buffer.from('foo'), - Buffer.from('Date'), - response.headers[5], // This is a date - Buffer.from('Connection'), - Buffer.from('keep-alive'), - Buffer.from('Keep-Alive'), - Buffer.from('timeout=5'), - Buffer.from('Transfer-Encoding'), - Buffer.from('chunked') - ] + const expectedHeaders = { + 'content-type': 'text/plain', + trailer: 'foo', + date: response.headers.date, // This is a date + connection: 'keep-alive', + 'keep-alive': 'timeout=5', + 'transfer-encoding': 'chunked' + } assert.deepStrictEqual(response.headers, expectedHeaders) assert.equal(response.statusText, 'OK') }) @@ -118,7 +112,7 @@ test('Diagnostics channel - post', (t) => { // This event is emitted after the last chunk has been added to the body stream, // not when it was consumed by the application assert.equal(endEmitted, false) - assert.deepStrictEqual(trailers, [Buffer.from('foo'), Buffer.from('oof')]) + assert.deepStrictEqual(trailers, { foo: 'oof' }) resolve() }) diff --git a/test/types/dispatcher.test-d.ts b/test/types/dispatcher.test-d.ts index 303abd0ade7..aaedbe7c9d7 100644 --- a/test/types/dispatcher.test-d.ts +++ b/test/types/dispatcher.test-d.ts @@ -47,8 +47,6 @@ expectAssignable(new Dispatcher()) expectAssignable(err) expectAssignable(data) })) - expectAssignable>(dispatcher.connect({ origin: '', path: '', responseHeaders: 'raw' })) - expectAssignable>(dispatcher.connect({ origin: '', path: '', responseHeaders: null })) // request expectAssignable>(dispatcher.request({ origin: '', path: '', method: 'GET', maxRedirections: 0 })) @@ -64,8 +62,6 @@ expectAssignable(new Dispatcher()) expectAssignable(err) expectAssignable(data) })) - expectAssignable>(dispatcher.request({ origin: '', path: '', method: 'GET', responseHeaders: 'raw' })) - expectAssignable>(dispatcher.request({ origin: '', path: '', method: 'GET', responseHeaders: null })) expectAssignable>>(dispatcher.request({ origin: '', path: '', method: 'GET', opaque: { example: '' } })) // pipeline @@ -77,14 +73,6 @@ expectAssignable(new Dispatcher()) expectAssignable(data) return new Readable() })) - expectAssignable(dispatcher.pipeline({ origin: '', path: '', method: 'GET', responseHeaders: 'raw' }, data => { - expectAssignable(data) - return new Readable() - })) - expectAssignable(dispatcher.pipeline({ origin: '', path: '', method: 'GET', responseHeaders: null }, data => { - expectAssignable(data) - return new Readable() - })) expectAssignable(dispatcher.pipeline({ origin: '', path: '', method: 'GET', opaque: { example: '' } }, data => { expectAssignable>(data) expectType<{ example: string }>(data.opaque) @@ -138,14 +126,6 @@ expectAssignable(new Dispatcher()) expectType<{ example: string }>(data.opaque) } )) - expectAssignable>(dispatcher.stream({ origin: '', path: '', method: 'GET', responseHeaders: 'raw' }, data => { - expectAssignable(data) - return new Writable() - })) - expectAssignable>(dispatcher.stream({ origin: '', path: '', method: 'GET', responseHeaders: null }, data => { - expectAssignable(data) - return new Writable() - })) // upgrade expectAssignable>(dispatcher.upgrade({ path: '', maxRedirections: 0 })) @@ -153,8 +133,6 @@ expectAssignable(new Dispatcher()) expectAssignable(err) expectAssignable(data) })) - expectAssignable>(dispatcher.upgrade({ path: '', responseHeaders: 'raw' })) - expectAssignable>(dispatcher.upgrade({ path: '', responseHeaders: null })) // close expectAssignable>(dispatcher.close()) diff --git a/types/diagnostics-channel.d.ts b/types/diagnostics-channel.d.ts index a037d1e0b2c..aabdda6dada 100644 --- a/types/diagnostics-channel.d.ts +++ b/types/diagnostics-channel.d.ts @@ -9,12 +9,12 @@ declare namespace DiagnosticsChannel { completed: boolean; method?: Dispatcher.HttpMethod; path: string; - headers: any; + headers: Record; } interface Response { statusCode: number; statusText: string; - headers: Array; + headers: Record; } type Error = unknown; interface ConnectParams { @@ -37,7 +37,7 @@ declare namespace DiagnosticsChannel { } export interface RequestTrailersMessage { request: Request; - trailers: Array; + trailers: Record; } export interface RequestErrorMessage { request: Request; diff --git a/types/dispatcher.d.ts b/types/dispatcher.d.ts index 6f33adc4a1d..0f603695572 100644 --- a/types/dispatcher.d.ts +++ b/types/dispatcher.d.ts @@ -175,8 +175,6 @@ declare namespace Dispatcher { maxRedirections?: number; /** Default: false */ redirectionLimitReached?: boolean; - /** Default: `null` */ - responseHeaders?: 'raw' | null; } export interface ConnectData { statusCode: number; @@ -221,15 +219,15 @@ declare namespace Dispatcher { /** Invoked when an error has occurred. */ onError?(err: Error): void; /** Invoked when request is upgraded either due to a `Upgrade` header or `CONNECT` method. */ - onUpgrade?(statusCode: number, headers: Buffer[] | string[] | null, socket: Duplex): void; + onUpgrade?(statusCode: number, headers: Record, socket: Duplex): void; /** Invoked when response is received, before headers have been read. **/ onResponseStarted?(): void; /** Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. */ - onHeaders?(statusCode: number, headers: Buffer[], resume: () => void, statusText: string): boolean; + onHeaders?(statusCode: number, headers: Record, resume: () => void, statusText: string): boolean; /** Invoked when response payload data is received. */ onData?(chunk: Buffer): boolean; /** Invoked when response payload and trailers have been received and the request has completed. */ - onComplete?(trailers: string[] | null): void; + onComplete?(trailers: Record): void; /** Invoked when a body chunk is sent to the server. May be invoked multiple times for chunked requests */ onBodySent?(chunkSize: number, totalBytesSent: number): void; }