diff --git a/docs/api/Dispatcher.md b/docs/api/Dispatcher.md index d2c4228a8f9..56b34275209 100644 --- a/docs/api/Dispatcher.md +++ b/docs/api/Dispatcher.md @@ -193,7 +193,7 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo * **path** `string` * **method** `string` * **body** `string | Buffer | Uint8Array | stream.Readable | Iterable | AsyncIterable | null` (optional) - Default: `null` -* **headers** `UndiciHeaders` (optional) - Default: `null` +* **headers** `UndiciHeaders | string[]` (optional) - Default: `null`. * **idempotent** `boolean` (optional) - Default: `true` if `method` is `'HEAD'` or `'GET'` - Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceding requests in the pipeline has completed. * **blocking** `boolean` (optional) - Default: `false` - Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received. * **upgrade** `string | null` (optional) - Default: `null` - Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`. diff --git a/lib/proxy-agent.js b/lib/proxy-agent.js index ee674df646f..2da6c7a5036 100644 --- a/lib/proxy-agent.js +++ b/lib/proxy-agent.js @@ -23,7 +23,7 @@ class ProxyAgent extends DispatcherBase { origin: this[kProxy].uri, path: opts.origin + opts.path, headers: { - ...opts.headers, + ...buildHeaders(opts.headers), host } }, @@ -55,4 +55,25 @@ function buildProxyOptions (opts) { } } +/** + * @param {string[] | Record} headers + * @returns {Record} + */ +function buildHeaders (headers) { + // When using undici.fetch, the headers list is stored + // as an array. + if (Array.isArray(headers)) { + /** @type {Record} */ + const headersPair = {} + + for (let i = 0; i < headers.length; i += 2) { + headersPair[headers[i]] = headers[i + 1] + } + + return headersPair + } + + return headers +} + module.exports = ProxyAgent diff --git a/test/proxy-agent.js b/test/proxy-agent.js index 080fc26088e..d484be3a35e 100644 --- a/test/proxy-agent.js +++ b/test/proxy-agent.js @@ -7,6 +7,9 @@ const { InvalidArgumentError } = require('../lib/core/errors') const ProxyAgent = require('../lib/proxy-agent') const { createServer } = require('http') const proxy = require('proxy') +const { once } = require('events') + +const nodeMajor = Number(process.versions.node.split('.', 1)[0]) test('should throw error when no uri is provided', (t) => { t.plan(2) @@ -183,6 +186,39 @@ test('use proxy-agent with setGlobalDispatcher', async (t) => { proxyAgent.close() }) +test('ProxyAgent correctly sends headers when using fetch - #1355', { skip: nodeMajor < 16 }, async (t) => { + const { getGlobalDispatcher, setGlobalDispatcher, fetch } = require('../index') + + const expectedHeaders = { + host: '127.0.0.1:9000', + connection: 'keep-alive', + 'test-header': 'value', + accept: '*/*', + 'accept-language': '*', + 'sec-fetch-mode': 'cors', + 'user-agent': 'undici', + 'accept-encoding': 'gzip, deflate' + } + + const oldDispatcher = getGlobalDispatcher() + const server = createServer((req, res) => { + t.same(req.headers, expectedHeaders) + res.end('goodbye') + }).listen(0) + t.teardown(server.close.bind(server)) + + await once(server, 'listening') + + setGlobalDispatcher(new ProxyAgent(`http://localhost:${server.address().port}`)) + + await fetch('http://127.0.0.1:9000', { + headers: { 'Test-header': 'value' } + }) + + setGlobalDispatcher(oldDispatcher) + t.end() +}) + function buildServer () { return new Promise((resolve) => { const server = createServer() @@ -190,9 +226,11 @@ function buildServer () { }) } -function buildProxy () { +function buildProxy (listener) { return new Promise((resolve) => { - const server = proxy(createServer()) + const server = listener + ? proxy(createServer(listener)) + : proxy(createServer()) server.listen(0, () => resolve(server)) }) }