diff --git a/doc/api/url.md b/doc/api/url.md index 593c6ce6a22bc9..ab8b4154ff023e 100644 --- a/doc/api/url.md +++ b/doc/api/url.md @@ -1226,6 +1226,11 @@ pathToFileURL('/some/path%.c'); // Correct: file:///some/path%25.c (POSI added: - v15.7.0 - v14.18.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/46989 + description: The returned object will also contain all the own enumerable + properties of the `url` argument. --> * `url` {URL} The [WHATWG URL][] object to convert to an options object. diff --git a/lib/internal/url.js b/lib/internal/url.js index 193965b76d346d..89a7ea4c31b2dd 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -1126,26 +1126,32 @@ function domainToUnicode(domain) { return _domainToUnicode(`${domain}`); } -// Utility function that converts a URL object into an ordinary -// options object as expected by the http.request and https.request -// APIs. +/** + * Utility function that converts a URL object into an ordinary options object + * as expected by the `http.request` and `https.request` APIs. + * @param {URL} url + * @returns {Record} + */ function urlToHttpOptions(url) { + const { hostname, pathname, port, username, password, search } = url; const options = { + __proto__: null, + ...url, // In case the url object was extended by the user. protocol: url.protocol, - hostname: url.hostname && StringPrototypeStartsWith(url.hostname, '[') ? - StringPrototypeSlice(url.hostname, 1, -1) : - url.hostname, + hostname: hostname && StringPrototypeStartsWith(hostname, '[') ? + StringPrototypeSlice(hostname, 1, -1) : + hostname, hash: url.hash, - search: url.search, - pathname: url.pathname, - path: `${url.pathname || ''}${url.search || ''}`, + search: search, + pathname: pathname, + path: `${pathname || ''}${search || ''}`, href: url.href, }; - if (url.port !== '') { - options.port = Number(url.port); + if (port !== '') { + options.port = Number(port); } - if (url.username || url.password) { - options.auth = `${decodeURIComponent(url.username)}:${decodeURIComponent(url.password)}`; + if (username || password) { + options.auth = `${decodeURIComponent(username)}:${decodeURIComponent(password)}`; } return options; } diff --git a/test/parallel/test-http-client-request-options.js b/test/parallel/test-http-client-request-options.js new file mode 100644 index 00000000000000..e642ef4fa94d01 --- /dev/null +++ b/test/parallel/test-http-client-request-options.js @@ -0,0 +1,27 @@ +'use strict'; + +const common = require('../common'); +const assert = require('node:assert'); +const http = require('node:http'); + +const headers = { foo: 'Bar' }; +const server = http.createServer(common.mustCall((req, res) => { + assert.strictEqual(req.url, '/ping?q=term'); + assert.strictEqual(req.headers?.foo, headers.foo); + req.resume(); + req.on('end', () => { + res.writeHead(200); + res.end('pong'); + }); +})); + +server.listen(0, common.localhostIPv4, () => { + const { address, port } = server.address(); + const url = new URL(`http://${address}:${port}/ping?q=term`); + url.headers = headers; + const clientReq = http.request(url); + clientReq.on('close', common.mustCall(() => { + server.close(); + })); + clientReq.end(); +});