Skip to content

Commit

Permalink
fix: ProxyAgent dispatch options receives a header array when using f…
Browse files Browse the repository at this point in the history
…etch (nodejs#1363)

* fix: don't spread array

* Update lib/proxy-agent.js

Co-authored-by: Rafael Silva <rafael.nunu@hotmail.com>

* test: improve test

Co-authored-by: Rafael Silva <rafael.nunu@hotmail.com>
  • Loading branch information
2 people authored and metcoder95 committed Dec 26, 2022
1 parent 90ca847 commit 9cbe8e1
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 4 deletions.
2 changes: 1 addition & 1 deletion docs/api/Dispatcher.md
Expand Up @@ -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'`.
Expand Down
23 changes: 22 additions & 1 deletion lib/proxy-agent.js
Expand Up @@ -23,7 +23,7 @@ class ProxyAgent extends DispatcherBase {
origin: this[kProxy].uri,
path: opts.origin + opts.path,
headers: {
...opts.headers,
...buildHeaders(opts.headers),
host
}
},
Expand Down Expand Up @@ -55,4 +55,25 @@ function buildProxyOptions (opts) {
}
}

/**
* @param {string[] | Record<string, string>} headers
* @returns {Record<string, string>}
*/
function buildHeaders (headers) {
// When using undici.fetch, the headers list is stored
// as an array.
if (Array.isArray(headers)) {
/** @type {Record<string, string>} */
const headersPair = {}

for (let i = 0; i < headers.length; i += 2) {
headersPair[headers[i]] = headers[i + 1]
}

return headersPair
}

return headers
}

module.exports = ProxyAgent
42 changes: 40 additions & 2 deletions test/proxy-agent.js
Expand Up @@ -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)
Expand Down Expand Up @@ -183,16 +186,51 @@ 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()
server.listen(0, () => resolve(server))
})
}

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))
})
}

0 comments on commit 9cbe8e1

Please sign in to comment.