Skip to content

Commit

Permalink
break(url): remove toDecode parameter (#175)
Browse files Browse the repository at this point in the history
* break(url): remove `toDecode` param

* chore(url): update bench results
  • Loading branch information
lukeed committed Aug 27, 2021
1 parent 6ac6498 commit e45fe88
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 291 deletions.
83 changes: 10 additions & 73 deletions packages/url/bench/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const isVerbose = process.argv.includes('--verbose');

/**
* @typedef Contender
* @type {(req: Request, toDecode: boolean) => any}
* @type {(req: Request) => any}
*/

/**
Expand All @@ -26,22 +26,20 @@ const isVerbose = process.argv.includes('--verbose');
* @type {Record<string, Contender>}
*/
const contenders = {
'url.parse': (r, d) => {
'url.parse': r => {
let out = native.parse(r.url, true); // query as object
if (d) out.pathname = decodeURIComponent(out.pathname);
// returns `null` if no value
out.search = out.search || '';
return out;
},

'new URL()': (r, d) => {
'new URL()': r => {
let url = r.url;
let { pathname, search, searchParams } = new URL(url, 'http://x.com');
if (d) pathname = decodeURIComponent(pathname);
return { url, pathname, search, query: searchParams };
},

'parseurl': (r, d) => {
'parseurl': r => {
/** @type {Record<string, string>} */ // @ts-ignore
let out = parseurl(r);

Expand All @@ -51,35 +49,31 @@ const contenders = {
// @ts-ignore - always returns `query` as string|null
if (out.query) out.query = qs.parse(out.query);

// never decodes, do bare minimum for compat
if (d) out.pathname = decodeURIComponent(out.pathname);

return out;
},

'@polka/url': (r, d) => {
'@polka/url': r => {
// @ts-ignore - request
return polka(r, d);
return polka(r);
},
};

/**
* @param {object} config
* @param {string} config.url
* @param {boolean} config.decode
* @param {Record<string, unknown>} config.expect
* @param {boolean} [config.repeat]
*/
function runner(config) {
let { url, expect, repeat, decode=false } = config;
let title = repeat ? 'repeat' : decode ? 'decode' : 'normal';
let { url, expect, repeat } = config;
let title = repeat ? 'repeat' : 'normal';

console.log('\nValidation: (%s) "%s"', title, url);
Object.keys(contenders).forEach(name => {
let key, fn=contenders[name];

try {
let output = fn({ url }, decode);
let output = fn({ url });

for (key in expect) {
let tmp = output[key];
Expand Down Expand Up @@ -107,7 +101,7 @@ function runner(config) {
let req = repeat && { url };

bench.add(name + ' '.repeat(16 - name.length), () => {
fn(req || { url }, decode);
fn(req || { url });
}, {
minSamples: 100
});
Expand All @@ -120,7 +114,6 @@ function runner(config) {

runner({
url: '/foo/bar?user=tj&pet=fluffy',
decode: false,
expect: {
pathname: '/foo/bar',
search: '?user=tj&pet=fluffy',
Expand All @@ -134,7 +127,6 @@ runner({
runner({
repeat: true,
url: '/foo/bar?user=tj&pet=fluffy',
decode: false,
expect: {
pathname: '/foo/bar',
search: '?user=tj&pet=fluffy',
Expand All @@ -147,7 +139,6 @@ runner({

runner({
url: '/foo/bar',
decode: false,
expect: {
pathname: '/foo/bar',
search: '',
Expand All @@ -156,62 +147,8 @@ runner({

runner({
url: '/',
decode: false,
expect: {
pathname: '/',
search: '',
}
});

// DECODES

runner({
url: '/f%C3%B8%C3%B8%C3%9F%E2%88%82r',
decode: true,
expect: {
pathname: '/føøß∂r',
search: '',
}
});

runner({
url: '/f%C3%B8%C3%B8%C3%9F%E2%88%82r?phone=%2b393383123549',
decode: true,
expect: {
pathname: '/føøß∂r',
search: '?phone=%2b393383123549',
query: { phone: '+393383123549' },
}
});

runner({
repeat: true,
url: '/f%C3%B8%C3%B8%C3%9F%E2%88%82r?phone=%2b393383123549',
decode: true,
expect: {
pathname: '/føøß∂r',
search: '?phone=%2b393383123549',
query: { phone: '+393383123549' },
}
});

runner({
url: '/foo/bar?hello=123',
decode: true,
expect: {
pathname: '/foo/bar',
search: '?hello=123',
query: {
hello: '123',
}
}
});

runner({
url: '/foo/bar',
decode: true,
expect: {
pathname: '/foo/bar',
search: '',
}
});
123 changes: 43 additions & 80 deletions packages/url/bench/readme.md
Original file line number Diff line number Diff line change
@@ -1,124 +1,87 @@
## Benchmarks

> Running on Node v14.15.3
> Running on Node v16.8.0
***Modifications:***

Each candidate is _slightly_ modified to match the `pathname` and `query` values that `@polka/url` returns. This is done in an honest/best-effort manner to normalize all candidates (and pass the validation steps), especially considering that the typical user _wants_ `pathname`s to be normalized and _wants_ `query` values to be parsed (and decoded) into an object.
Each candidate is _slightly_ modified to match the `search` and `query` values that `@polka/url` returns. This is done in an honest/best-effort manner to normalize all candidates (and pass the validation steps), especially considering that the typical user wants `query` to be parsed (and decoded) into an object.

Please see the [Raw Performance](#raw-performance) benchmarks for results ***without*** any modifications. However, do note that the benchmark is effectively useless since all candidates do different things by default.
Please see the [Raw Performance](#raw-performance) benchmarks for results ***without*** any modifications. However, do note that the raw-benchmark is effectively useless since all candidates do different things by default.


### Without Decoding
## Normalized (minimal)

> **Important:** All candidates listed pass validation – sometimes due to normalization.
```
Benchmark: "/foo/bar?user=tj&pet=fluffy"
url.parse x 1,437,988 ops/sec ±0.15% (193 runs sampled)
new URL() x 258,158 ops/sec ±0.17% (193 runs sampled)
parseurl x 2,074,250 ops/sec ±0.22% (193 runs sampled)
@polka/url x 2,584,006 ops/sec ±0.26% (194 runs sampled)
url.parse x 1,766,192 ops/sec ±0.59% (189 runs sampled)
new URL() x 254,742 ops/sec ±0.93% (188 runs sampled)
parseurl x 2,296,634 ops/sec ±0.43% (188 runs sampled)
@polka/url x 3,096,770 ops/sec ±0.56% (189 runs sampled)
Benchmark: (REPEAT) "/foo/bar?user=tj&pet=fluffy"
url.parse x 1,457,258 ops/sec ±0.17% (193 runs sampled)
new URL() x 257,459 ops/sec ±0.24% (189 runs sampled)
parseurl x 26,687,772 ops/sec ±0.40% (192 runs sampled)
@polka/url x 117,737,558 ops/sec ±0.23% (193 runs sampled)
url.parse x 1,794,821 ops/sec ±1.01% (188 runs sampled)
new URL() x 258,587 ops/sec ±0.63% (189 runs sampled)
parseurl x 30,616,846 ops/sec ±0.47% (191 runs sampled)
@polka/url x 314,370,079 ops/sec ±0.36% (189 runs sampled)
Benchmark: "/foo/bar"
url.parse x 5,928,214 ops/sec ±0.33% (191 runs sampled)
new URL() x 290,799 ops/sec ±0.17% (192 runs sampled)
parseurl x 17,597,099 ops/sec ±0.62% (189 runs sampled)
@polka/url x 34,097,146 ops/sec ±0.55% (192 runs sampled)
url.parse x 7,373,531 ops/sec ±0.63% (188 runs sampled)
new URL() x 291,642 ops/sec ±0.83% (189 runs sampled)
parseurl x 21,946,341 ops/sec ±0.93% (186 runs sampled)
@polka/url x 48,697,030 ops/sec ±0.49% (189 runs sampled)
Benchmark: "/"
url.parse x 8,933,877 ops/sec ±0.70% (190 runs sampled)
new URL() x 333,268 ops/sec ±0.19% (193 runs sampled)
parseurl x 27,358,354 ops/sec ±0.76% (188 runs sampled)
@polka/url x 43,529,456 ops/sec ±1.99% (172 runs sampled)
```


### With Decoding

> **Important:** All candidates listed pass validation – sometimes due to normalization.
```
Benchmark: "/f%C3%B8%C3%B8%C3%9F%E2%88%82r"
url.parse x 1,038,724 ops/sec ±0.12% (193 runs sampled)
new URL() x 229,125 ops/sec ±0.17% (192 runs sampled)
parseurl x 1,370,300 ops/sec ±0.20% (192 runs sampled)
@polka/url x 1,540,894 ops/sec ±0.18% (192 runs sampled)
Benchmark: "/f%C3%B8%C3%B8%C3%9F%E2%88%82r?phone=%2b393383123549"
url.parse x 514,280 ops/sec ±0.40% (193 runs sampled)
new URL() x 187,672 ops/sec ±0.67% (192 runs sampled)
parseurl x 618,801 ops/sec ±0.10% (189 runs sampled)
@polka/url x 696,125 ops/sec ±0.14% (191 runs sampled)
Benchmark: (REPEAT) "/f%C3%B8%C3%B8%C3%9F%E2%88%82r?phone=%2b393383123549"
url.parse x 425,909 ops/sec ±0.29% (193 runs sampled)
new URL() x 187,735 ops/sec ±0.14% (194 runs sampled)
parseurl x 1,770,147 ops/sec ±0.15% (193 runs sampled)
@polka/url x 197,963,726 ops/sec ±0.18% (194 runs sampled)
Benchmark: "/foo/bar?hello=123"
url.parse x 1,133,709 ops/sec ±0.28% (190 runs sampled)
new URL() x 236,384 ops/sec ±0.20% (193 runs sampled)
parseurl x 1,431,879 ops/sec ±0.19% (191 runs sampled)
@polka/url x 3,883,489 ops/sec ±0.37% (192 runs sampled)
Benchmark: "/foo/bar"
url.parse x 1,824,376 ops/sec ±0.54% (192 runs sampled)
new URL() x 252,204 ops/sec ±0.18% (192 runs sampled)
parseurl x 2,329,132 ops/sec ±0.26% (193 runs sampled)
@polka/url x 19,972,200 ops/sec ±0.64% (188 runs sampled)
url.parse x 10,744,706 ops/sec ±0.47% (188 runs sampled)
new URL() x 315,725 ops/sec ±0.86% (184 runs sampled)
parseurl x 46,863,886 ops/sec ±1.06% (189 runs sampled)
@polka/url x 72,862,914 ops/sec ±0.54% (190 runs sampled)
```


## Raw Performance

These are the results of the _unmodified_ candidates. In other words, there is **zero consistency** in the candidates outputs. For example:

* `url.parse#1` uses [`url.parse`](https://nodejs.org/api/url.html#url_url_parse_urlstring_parsequerystring_slashesdenotehost) with `parseQueryString` enabled<br>_It decodes the `query` correctly, but all other segments are still encoded segments._
* `url.parse#1` uses [`url.parse`](https://nodejs.org/api/url.html#url_url_parse_urlstring_parsequerystring_slashesdenotehost) with `parseQueryString` enabled<br>_It converts the `query` into a decoded object, but everything else remains encoded._

* `url.parse#2` is the same as `url.parse#1`, except `parseQueryString` is disabled.<br>_It leaves the `query` as a string & does no decoding whatsoever._

* `new URL()` does what it describes :)<br>_The `pathname` is never decoded, but `searchParams` is always an decoded `URLSearchParams` instance._
* `new URL()` does what it describes :)<br>_Everything remains encoded, except for `searchParams`, which is always an `URLSearchParams` instance with decoded values._

* `parseurl` never decodes any value segments and `query` is always a string.

* `@polka/url` only decodes the `pathname` when asked and `query` is always a decoded object.
* `@polka/url` never decodes any value segments except `query`, which is always a decoded object.

***Results***

```
Benchmark: (normal) "/foo/bar?user=tj&pet=fluffy"
url.parse#1 x 1,462,942 ops/sec ±0.16% (193 runs sampled)
url.parse#2 x 3,059,883 ops/sec ±0.19% (193 runs sampled)
new URL() x 273,878 ops/sec ±0.11% (194 runs sampled)
parseurl x 7,742,605 ops/sec ±0.25% (192 runs sampled)
@polka/url x 2,611,970 ops/sec ±0.18% (190 runs sampled)
url.parse#1 x 1,757,099 ops/sec ±0.67% (188 runs sampled)
url.parse#2 x 4,487,853 ops/sec ±0.70% (185 runs sampled)
new URL() x 284,153 ops/sec ±0.68% (187 runs sampled)
parseurl x 9,848,571 ops/sec ±0.97% (186 runs sampled)
@polka/url x 3,040,460 ops/sec ±0.79% (188 runs sampled)
Benchmark: (repeat) "/foo/bar?user=tj&pet=fluffy"
url.parse#1 x 1,477,740 ops/sec ±0.28% (194 runs sampled)
url.parse#2 x 3,121,952 ops/sec ±0.12% (193 runs sampled)
new URL() x 271,608 ops/sec ±0.24% (192 runs sampled)
parseurl x 112,501,081 ops/sec ±3.88% (177 runs sampled)
@polka/url x 73,331,596 ops/sec ±2.44% (180 runs sampled)
url.parse#1 x 1,827,115 ops/sec ±0.41% (190 runs sampled)
url.parse#2 x 4,442,871 ops/sec ±0.72% (183 runs sampled)
new URL() x 286,803 ops/sec ±0.30% (189 runs sampled)
parseurl x 78,897,892 ops/sec ±1.51% (182 runs sampled)
@polka/url x 291,908,732 ops/sec ±4.97% (179 runs sampled)
Benchmark: (normal) "/foo/bar"
url.parse#1 x 6,109,524 ops/sec ±0.39% (190 runs sampled)
url.parse#2 x 6,741,743 ops/sec ±0.38% (190 runs sampled)
new URL() x 304,240 ops/sec ±0.15% (192 runs sampled)
parseurl x 17,809,555 ops/sec ±0.55% (190 runs sampled)
@polka/url x 28,314,612 ops/sec ±1.14% (175 runs sampled)
url.parse#1 x 7,824,747 ops/sec ±0.65% (186 runs sampled)
url.parse#2 x 9,015,704 ops/sec ±0.68% (188 runs sampled)
new URL() x 320,978 ops/sec ±0.41% (188 runs sampled)
parseurl x 25,611,676 ops/sec ±0.45% (189 runs sampled)
@polka/url x 48,554,610 ops/sec ±0.48% (190 runs sampled)
Benchmark: (normal) "/"
url.parse#1 x 9,370,629 ops/sec ±0.37% (187 runs sampled)
url.parse#2 x 11,248,825 ops/sec ±0.55% (190 runs sampled)
new URL() x 343,111 ops/sec ±0.18% (193 runs sampled)
parseurl x 28,455,610 ops/sec ±0.92% (187 runs sampled)
@polka/url x 41,677,905 ops/sec ±1.07% (188 runs sampled)
url.parse#1 x 11,682,323 ops/sec ±0.81% (187 runs sampled)
url.parse#2 x 15,679,363 ops/sec ±0.61% (188 runs sampled)
new URL() x 348,880 ops/sec ±0.35% (189 runs sampled)
parseurl x 34,522,603 ops/sec ±0.73% (190 runs sampled)
@polka/url x 71,136,459 ops/sec ±0.58% (189 runs sampled)
```
2 changes: 1 addition & 1 deletion packages/url/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ export interface ParsedURL {
raw: string;
}

export function parse(req: IncomingMessage, toDecode?: boolean): ParsedURL;
export function parse(req: IncomingMessage): ParsedURL;
17 changes: 3 additions & 14 deletions packages/url/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,19 @@ import * as qs from 'querystring';
/**
* @typedef Request
* @property {string} url
* @property {string} _decoded
* @property {ParsedURL} _parsedUrl
*/

/**
* @param {Request} req
* @param {boolean} [toDecode]
* @returns {ParsedURL|void}
*/
export function parse(req, toDecode) {
export function parse(req) {
let raw = req.url;
if (raw == null) return;

let prev=req._parsedUrl, encoded=!req._decoded;
if (prev && prev.raw === raw && !toDecode === encoded) return prev;
let prev = req._parsedUrl;
if (prev && prev.raw === raw) return prev;

let pathname=raw, search='', query;

Expand All @@ -36,15 +34,6 @@ export function parse(req, toDecode) {
query = qs.parse(search.substring(1));
}
}

if (!!toDecode && encoded) {
if (pathname.indexOf('%') === -1) {
req._decoded = pathname;
} else {
try { pathname = req._decoded = decodeURIComponent(pathname) }
catch (e) { /* URI malformed */ }
}
}
}

return req._parsedUrl = { pathname, search, query, raw };
Expand Down
Loading

0 comments on commit e45fe88

Please sign in to comment.