url: expose urlToHttpOptions utility
PR-URL: https://github.com/nodejs/node/pull/35960 Fixes: https://github.com/nodejs/node/issues/34349 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com>
This commit is contained in:
parent
f658bd1e8f
commit
7efada695f
@ -1029,6 +1029,50 @@ new URL('/some/path%.c', 'file:'); // Incorrect: file:///some/path%.c
|
|||||||
pathToFileURL('/some/path%.c'); // Correct: file:///some/path%25.c (POSIX)
|
pathToFileURL('/some/path%.c'); // Correct: file:///some/path%25.c (POSIX)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `url.urlToHttpOptions(url)`
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `url` {URL} The [WHATWG URL][] object to convert to an options object.
|
||||||
|
* Returns: {Object} Options object
|
||||||
|
* `protocol` {string} Protocol to use.
|
||||||
|
* `hostname` {string} A domain name or IP address of the server to issue the
|
||||||
|
request to.
|
||||||
|
* `hash` {string} The fragment portion of the URL.
|
||||||
|
* `search` {string} The serialized query portion of the URL.
|
||||||
|
* `pathname` {string} The path portion of the URL.
|
||||||
|
* `path` {string} Request path. Should include query string if any.
|
||||||
|
E.G. `'/index.html?page=12'`. An exception is thrown when the request path
|
||||||
|
contains illegal characters. Currently, only spaces are rejected but that
|
||||||
|
may change in the future.
|
||||||
|
* `href` {string} The serialized URL.
|
||||||
|
* `port` {number} Port of remote server.
|
||||||
|
* `auth` {string} Basic authentication i.e. `'user:password'` to compute an
|
||||||
|
Authorization header.
|
||||||
|
|
||||||
|
This utility function converts a URL object into an ordinary options object as
|
||||||
|
expected by the [`http.request()`][] and [`https.request()`][] APIs.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { urlToHttpOptions } = require('url');
|
||||||
|
const myURL = new URL('https://a:b@測試?abc#foo');
|
||||||
|
|
||||||
|
console.log(urlToHttpOptions(myUrl));
|
||||||
|
/**
|
||||||
|
{
|
||||||
|
protocol: 'https:',
|
||||||
|
hostname: 'xn--g6w251d',
|
||||||
|
hash: '#foo',
|
||||||
|
search: '?abc',
|
||||||
|
pathname: '/',
|
||||||
|
path: '/?abc',
|
||||||
|
href: 'https://a:b@xn--g6w251d/?abc#foo',
|
||||||
|
auth: 'a:b'
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
```
|
||||||
|
|
||||||
## Legacy URL API
|
## Legacy URL API
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
deprecated: v11.0.0
|
deprecated: v11.0.0
|
||||||
@ -1388,6 +1432,8 @@ console.log(myURL.origin);
|
|||||||
[`TypeError`]: errors.md#errors_class_typeerror
|
[`TypeError`]: errors.md#errors_class_typeerror
|
||||||
[`URLSearchParams`]: #url_class_urlsearchparams
|
[`URLSearchParams`]: #url_class_urlsearchparams
|
||||||
[`array.toString()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString
|
[`array.toString()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString
|
||||||
|
[`http.request()`]: http.md#http_http_request_options_callback
|
||||||
|
[`https.request()`]: https.md#https_https_request_options_callback
|
||||||
[`new URL()`]: #url_new_url_input_base
|
[`new URL()`]: #url_new_url_input_base
|
||||||
[`querystring`]: querystring.md
|
[`querystring`]: querystring.md
|
||||||
[`require('url').format()`]: #url_url_format_url_options
|
[`require('url').format()`]: #url_url_format_url_options
|
||||||
|
@ -58,7 +58,7 @@ const { OutgoingMessage } = require('_http_outgoing');
|
|||||||
const Agent = require('_http_agent');
|
const Agent = require('_http_agent');
|
||||||
const { Buffer } = require('buffer');
|
const { Buffer } = require('buffer');
|
||||||
const { defaultTriggerAsyncIdScope } = require('internal/async_hooks');
|
const { defaultTriggerAsyncIdScope } = require('internal/async_hooks');
|
||||||
const { URL, urlToOptions, searchParamsSymbol } = require('internal/url');
|
const { URL, urlToHttpOptions, searchParamsSymbol } = require('internal/url');
|
||||||
const { kOutHeaders, kNeedDrain } = require('internal/http');
|
const { kOutHeaders, kNeedDrain } = require('internal/http');
|
||||||
const { connResetException, codes } = require('internal/errors');
|
const { connResetException, codes } = require('internal/errors');
|
||||||
const {
|
const {
|
||||||
@ -105,7 +105,7 @@ function ClientRequest(input, options, cb) {
|
|||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
const urlStr = input;
|
const urlStr = input;
|
||||||
try {
|
try {
|
||||||
input = urlToOptions(new URL(urlStr));
|
input = urlToHttpOptions(new URL(urlStr));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
input = url.parse(urlStr);
|
input = url.parse(urlStr);
|
||||||
if (!input.hostname) {
|
if (!input.hostname) {
|
||||||
@ -122,7 +122,7 @@ function ClientRequest(input, options, cb) {
|
|||||||
} else if (input && input[searchParamsSymbol] &&
|
} else if (input && input[searchParamsSymbol] &&
|
||||||
input[searchParamsSymbol][searchParamsSymbol]) {
|
input[searchParamsSymbol][searchParamsSymbol]) {
|
||||||
// url.URL instance
|
// url.URL instance
|
||||||
input = urlToOptions(input);
|
input = urlToHttpOptions(input);
|
||||||
} else {
|
} else {
|
||||||
cb = options;
|
cb = options;
|
||||||
options = input;
|
options = input;
|
||||||
|
@ -48,7 +48,7 @@ const { ClientRequest } = require('_http_client');
|
|||||||
let debug = require('internal/util/debuglog').debuglog('https', (fn) => {
|
let debug = require('internal/util/debuglog').debuglog('https', (fn) => {
|
||||||
debug = fn;
|
debug = fn;
|
||||||
});
|
});
|
||||||
const { URL, urlToOptions, searchParamsSymbol } = require('internal/url');
|
const { URL, urlToHttpOptions, searchParamsSymbol } = require('internal/url');
|
||||||
const { IncomingMessage, ServerResponse } = require('http');
|
const { IncomingMessage, ServerResponse } = require('http');
|
||||||
const { kIncomingMessage } = require('_http_common');
|
const { kIncomingMessage } = require('_http_common');
|
||||||
|
|
||||||
@ -303,7 +303,7 @@ function request(...args) {
|
|||||||
if (typeof args[0] === 'string') {
|
if (typeof args[0] === 'string') {
|
||||||
const urlStr = ArrayPrototypeShift(args);
|
const urlStr = ArrayPrototypeShift(args);
|
||||||
try {
|
try {
|
||||||
options = urlToOptions(new URL(urlStr));
|
options = urlToHttpOptions(new URL(urlStr));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
options = url.parse(urlStr);
|
options = url.parse(urlStr);
|
||||||
if (!options.hostname) {
|
if (!options.hostname) {
|
||||||
@ -320,7 +320,7 @@ function request(...args) {
|
|||||||
} else if (args[0] && args[0][searchParamsSymbol] &&
|
} else if (args[0] && args[0][searchParamsSymbol] &&
|
||||||
args[0][searchParamsSymbol][searchParamsSymbol]) {
|
args[0][searchParamsSymbol][searchParamsSymbol]) {
|
||||||
// url.URL instance
|
// url.URL instance
|
||||||
options = urlToOptions(ArrayPrototypeShift(args));
|
options = urlToHttpOptions(ArrayPrototypeShift(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args[0] && typeof args[0] !== 'function') {
|
if (args[0] && typeof args[0] !== 'function') {
|
||||||
|
@ -1295,7 +1295,7 @@ function domainToUnicode(domain) {
|
|||||||
// Utility function that converts a URL object into an ordinary
|
// Utility function that converts a URL object into an ordinary
|
||||||
// options object as expected by the http.request and https.request
|
// options object as expected by the http.request and https.request
|
||||||
// APIs.
|
// APIs.
|
||||||
function urlToOptions(url) {
|
function urlToHttpOptions(url) {
|
||||||
const options = {
|
const options = {
|
||||||
protocol: url.protocol,
|
protocol: url.protocol,
|
||||||
hostname: typeof url.hostname === 'string' &&
|
hostname: typeof url.hostname === 'string' &&
|
||||||
@ -1494,7 +1494,7 @@ module.exports = {
|
|||||||
URLSearchParams,
|
URLSearchParams,
|
||||||
domainToASCII,
|
domainToASCII,
|
||||||
domainToUnicode,
|
domainToUnicode,
|
||||||
urlToOptions,
|
urlToHttpOptions,
|
||||||
formatSymbol: kFormat,
|
formatSymbol: kFormat,
|
||||||
searchParamsSymbol: searchParams,
|
searchParamsSymbol: searchParams,
|
||||||
encodeStr
|
encodeStr
|
||||||
|
@ -47,9 +47,10 @@ const {
|
|||||||
URLSearchParams,
|
URLSearchParams,
|
||||||
domainToASCII,
|
domainToASCII,
|
||||||
domainToUnicode,
|
domainToUnicode,
|
||||||
|
fileURLToPath,
|
||||||
formatSymbol,
|
formatSymbol,
|
||||||
pathToFileURL,
|
pathToFileURL,
|
||||||
fileURLToPath
|
urlToHttpOptions,
|
||||||
} = require('internal/url');
|
} = require('internal/url');
|
||||||
|
|
||||||
// Original url.parse() API
|
// Original url.parse() API
|
||||||
@ -987,5 +988,6 @@ module.exports = {
|
|||||||
|
|
||||||
// Utilities
|
// Utilities
|
||||||
pathToFileURL,
|
pathToFileURL,
|
||||||
fileURLToPath
|
fileURLToPath,
|
||||||
|
urlToHttpOptions,
|
||||||
};
|
};
|
||||||
|
37
test/parallel/test-url-urltooptions.js
Normal file
37
test/parallel/test-url-urltooptions.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
'use strict';
|
||||||
|
require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const { urlToHttpOptions } = require('url');
|
||||||
|
|
||||||
|
// Test urlToHttpOptions
|
||||||
|
const urlObj = new URL('http://user:pass@foo.bar.com:21/aaa/zzz?l=24#test');
|
||||||
|
const opts = urlToHttpOptions(urlObj);
|
||||||
|
assert.strictEqual(opts instanceof URL, false);
|
||||||
|
assert.strictEqual(opts.protocol, 'http:');
|
||||||
|
assert.strictEqual(opts.auth, 'user:pass');
|
||||||
|
assert.strictEqual(opts.hostname, 'foo.bar.com');
|
||||||
|
assert.strictEqual(opts.port, 21);
|
||||||
|
assert.strictEqual(opts.path, '/aaa/zzz?l=24');
|
||||||
|
assert.strictEqual(opts.pathname, '/aaa/zzz');
|
||||||
|
assert.strictEqual(opts.search, '?l=24');
|
||||||
|
assert.strictEqual(opts.hash, '#test');
|
||||||
|
|
||||||
|
const { hostname } = urlToHttpOptions(new URL('http://[::1]:21'));
|
||||||
|
assert.strictEqual(hostname, '::1');
|
||||||
|
|
||||||
|
// If a WHATWG URL object is copied, it is possible that the resulting copy
|
||||||
|
// contains the Symbols that Node uses for brand checking, but not the data
|
||||||
|
// properties, which are getters. Verify that urlToHttpOptions() can handle
|
||||||
|
// such a case.
|
||||||
|
const copiedUrlObj = { ...urlObj };
|
||||||
|
const copiedOpts = urlToHttpOptions(copiedUrlObj);
|
||||||
|
assert.strictEqual(copiedOpts instanceof URL, false);
|
||||||
|
assert.strictEqual(copiedOpts.protocol, undefined);
|
||||||
|
assert.strictEqual(copiedOpts.auth, undefined);
|
||||||
|
assert.strictEqual(copiedOpts.hostname, undefined);
|
||||||
|
assert.strictEqual(copiedOpts.port, NaN);
|
||||||
|
assert.strictEqual(copiedOpts.path, '');
|
||||||
|
assert.strictEqual(copiedOpts.pathname, undefined);
|
||||||
|
assert.strictEqual(copiedOpts.search, undefined);
|
||||||
|
assert.strictEqual(copiedOpts.hash, undefined);
|
||||||
|
assert.strictEqual(copiedOpts.href, undefined);
|
@ -6,7 +6,6 @@
|
|||||||
require('../common');
|
require('../common');
|
||||||
const URL = require('url').URL;
|
const URL = require('url').URL;
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const urlToOptions = require('internal/url').urlToOptions;
|
|
||||||
|
|
||||||
const url = new URL('http://user:pass@foo.bar.com:21/aaa/zzz?l=24#test');
|
const url = new URL('http://user:pass@foo.bar.com:21/aaa/zzz?l=24#test');
|
||||||
const oldParams = url.searchParams; // For test of [SameObject]
|
const oldParams = url.searchParams; // For test of [SameObject]
|
||||||
@ -131,41 +130,6 @@ assert.strictEqual(url.toString(),
|
|||||||
assert.strictEqual((delete url.searchParams), true);
|
assert.strictEqual((delete url.searchParams), true);
|
||||||
assert.strictEqual(url.searchParams, oldParams);
|
assert.strictEqual(url.searchParams, oldParams);
|
||||||
|
|
||||||
// Test urlToOptions
|
|
||||||
{
|
|
||||||
const urlObj = new URL('http://user:pass@foo.bar.com:21/aaa/zzz?l=24#test');
|
|
||||||
const opts = urlToOptions(urlObj);
|
|
||||||
assert.strictEqual(opts instanceof URL, false);
|
|
||||||
assert.strictEqual(opts.protocol, 'http:');
|
|
||||||
assert.strictEqual(opts.auth, 'user:pass');
|
|
||||||
assert.strictEqual(opts.hostname, 'foo.bar.com');
|
|
||||||
assert.strictEqual(opts.port, 21);
|
|
||||||
assert.strictEqual(opts.path, '/aaa/zzz?l=24');
|
|
||||||
assert.strictEqual(opts.pathname, '/aaa/zzz');
|
|
||||||
assert.strictEqual(opts.search, '?l=24');
|
|
||||||
assert.strictEqual(opts.hash, '#test');
|
|
||||||
|
|
||||||
const { hostname } = urlToOptions(new URL('http://[::1]:21'));
|
|
||||||
assert.strictEqual(hostname, '::1');
|
|
||||||
|
|
||||||
// If a WHATWG URL object is copied, it is possible that the resulting copy
|
|
||||||
// contains the Symbols that Node uses for brand checking, but not the data
|
|
||||||
// properties, which are getters. Verify that urlToOptions() can handle such
|
|
||||||
// a case.
|
|
||||||
const copiedUrlObj = { ...urlObj };
|
|
||||||
const copiedOpts = urlToOptions(copiedUrlObj);
|
|
||||||
assert.strictEqual(copiedOpts instanceof URL, false);
|
|
||||||
assert.strictEqual(copiedOpts.protocol, undefined);
|
|
||||||
assert.strictEqual(copiedOpts.auth, undefined);
|
|
||||||
assert.strictEqual(copiedOpts.hostname, undefined);
|
|
||||||
assert.strictEqual(copiedOpts.port, NaN);
|
|
||||||
assert.strictEqual(copiedOpts.path, '');
|
|
||||||
assert.strictEqual(copiedOpts.pathname, undefined);
|
|
||||||
assert.strictEqual(copiedOpts.search, undefined);
|
|
||||||
assert.strictEqual(copiedOpts.hash, undefined);
|
|
||||||
assert.strictEqual(copiedOpts.href, undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test special origins
|
// Test special origins
|
||||||
[
|
[
|
||||||
{ expected: 'https://whatwg.org',
|
{ expected: 'https://whatwg.org',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user