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:
zhangyongsheng 2021-01-06 16:10:28 +08:00
parent f658bd1e8f
commit 7efada695f
7 changed files with 95 additions and 46 deletions

View File

@ -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

View File

@ -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;

View File

@ -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') {

View File

@ -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

View File

@ -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,
}; };

View 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);

View File

@ -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',