Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 54bb691

Browse filesBrowse files
authored
util: lazy parse mime parameters
PR-URL: #49889 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent d920b7c commit 54bb691
Copy full SHA for 54bb691

File tree

Expand file treeCollapse file tree

7 files changed

+271
-18
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

7 files changed

+271
-18
lines changed
Open diff view settings
Collapse file
+53Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const { MIMEType } = require('util');
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [1e5],
9+
value: [
10+
'application/ecmascript; ',
11+
'text/html;charset=gbk',
12+
`text/html;${'0123456789'.repeat(12)}=x;charset=gbk`,
13+
'text/html;test=\u00FF;charset=gbk',
14+
'x/x;\n\r\t x=x\n\r\t ;x=y',
15+
],
16+
}, {
17+
});
18+
19+
function main({ n, value }) {
20+
// Warm up.
21+
const length = 1024;
22+
const array = [];
23+
let errCase = false;
24+
25+
for (let i = 0; i < length; ++i) {
26+
try {
27+
array.push(new MIMEType(value));
28+
} catch (e) {
29+
errCase = true;
30+
array.push(e);
31+
}
32+
}
33+
34+
// console.log(`errCase: ${errCase}`);
35+
bench.start();
36+
37+
for (let i = 0; i < n; ++i) {
38+
const index = i % length;
39+
try {
40+
array[index] = new MIMEType(value);
41+
} catch (e) {
42+
array[index] = e;
43+
}
44+
}
45+
46+
bench.end(n);
47+
48+
// Verify the entries to prevent dead code elimination from making
49+
// the benchmark invalid.
50+
for (let i = 0; i < length; ++i) {
51+
assert.strictEqual(typeof array[i], errCase ? 'object' : 'object');
52+
}
53+
}
Collapse file
+55Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const { MIMEType } = require('util');
6+
7+
const bench = common.createBenchmark(main, {
8+
n: [1e5],
9+
value: [
10+
'application/ecmascript; ',
11+
'text/html;charset=gbk',
12+
`text/html;${'0123456789'.repeat(12)}=x;charset=gbk`,
13+
'text/html;test=\u00FF;charset=gbk',
14+
'x/x;\n\r\t x=x\n\r\t ;x=y',
15+
],
16+
}, {
17+
});
18+
19+
function main({ n, value }) {
20+
// Warm up.
21+
const length = 1024;
22+
const array = [];
23+
let errCase = false;
24+
25+
const mime = new MIMEType(value);
26+
27+
for (let i = 0; i < length; ++i) {
28+
try {
29+
array.push(mime.toString());
30+
} catch (e) {
31+
errCase = true;
32+
array.push(e);
33+
}
34+
}
35+
36+
// console.log(`errCase: ${errCase}`);
37+
bench.start();
38+
39+
for (let i = 0; i < n; ++i) {
40+
const index = i % length;
41+
try {
42+
array[index] = mime.toString();
43+
} catch (e) {
44+
array[index] = e;
45+
}
46+
}
47+
48+
bench.end(n);
49+
50+
// Verify the entries to prevent dead code elimination from making
51+
// the benchmark invalid.
52+
for (let i = 0; i < length; ++i) {
53+
assert.strictEqual(typeof array[i], errCase ? 'object' : 'string');
54+
}
55+
}
Collapse file
+53Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
6+
const bench = common.createBenchmark(main, {
7+
n: [1e7],
8+
value: [
9+
'application/ecmascript; ',
10+
'text/html;charset=gbk',
11+
// eslint-disable-next-line max-len
12+
'text/html;0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789=x;charset=gbk',
13+
],
14+
}, {
15+
flags: ['--expose-internals'],
16+
});
17+
18+
function main({ n, value }) {
19+
20+
const parseTypeAndSubtype = require('internal/mime').parseTypeAndSubtype;
21+
// Warm up.
22+
const length = 1024;
23+
const array = [];
24+
let errCase = false;
25+
26+
for (let i = 0; i < length; ++i) {
27+
try {
28+
array.push(parseTypeAndSubtype(value));
29+
} catch (e) {
30+
errCase = true;
31+
array.push(e);
32+
}
33+
}
34+
35+
// console.log(`errCase: ${errCase}`);
36+
bench.start();
37+
for (let i = 0; i < n; ++i) {
38+
const index = i % length;
39+
try {
40+
array[index] = parseTypeAndSubtype(value);
41+
} catch (e) {
42+
array[index] = e;
43+
}
44+
}
45+
46+
bench.end(n);
47+
48+
// Verify the entries to prevent dead code elimination from making
49+
// the benchmark invalid.
50+
for (let i = 0; i < length; ++i) {
51+
assert.strictEqual(typeof array[i], errCase ? 'object' : 'object');
52+
}
53+
}
Collapse file

‎benchmark/mime/to-ascii-lower.js‎

Copy file name to clipboard
+54Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
6+
const bench = common.createBenchmark(main, {
7+
n: [1e7],
8+
value: [
9+
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
10+
'UPPERCASE',
11+
'lowercase',
12+
'mixedCase',
13+
],
14+
}, {
15+
flags: ['--expose-internals'],
16+
});
17+
18+
function main({ n, value }) {
19+
20+
const toASCIILower = require('internal/mime').toASCIILower;
21+
// Warm up.
22+
const length = 1024;
23+
const array = [];
24+
let errCase = false;
25+
26+
for (let i = 0; i < length; ++i) {
27+
try {
28+
array.push(toASCIILower(value));
29+
} catch (e) {
30+
errCase = true;
31+
array.push(e);
32+
}
33+
}
34+
35+
// console.log(`errCase: ${errCase}`);
36+
bench.start();
37+
38+
for (let i = 0; i < n; ++i) {
39+
const index = i % length;
40+
try {
41+
array[index] = toASCIILower(value);
42+
} catch (e) {
43+
array[index] = e;
44+
}
45+
}
46+
47+
bench.end(n);
48+
49+
// Verify the entries to prevent dead code elimination from making
50+
// the benchmark invalid.
51+
for (let i = 0; i < length; ++i) {
52+
assert.strictEqual(typeof array[i], errCase ? 'object' : 'string');
53+
}
54+
}
Collapse file

‎lib/internal/mime.js‎

Copy file name to clipboardExpand all lines: lib/internal/mime.js
+42-18Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ function toASCIILower(str) {
3636

3737
const SOLIDUS = '/';
3838
const SEMICOLON = ';';
39+
3940
function parseTypeAndSubtype(str) {
4041
// Skip only HTTP whitespace from start
4142
let position = SafeStringPrototypeSearch(str, END_BEGINNING_WHITESPACE);
@@ -72,12 +73,11 @@ function parseTypeAndSubtype(str) {
7273
throw new ERR_INVALID_MIME_SYNTAX('subtype', str, trimmedSubtype);
7374
}
7475
const subtype = toASCIILower(trimmedSubtype);
75-
return {
76-
__proto__: null,
76+
return [
7777
type,
7878
subtype,
79-
parametersStringIndex: position,
80-
};
79+
position,
80+
];
8181
}
8282

8383
const EQUALS_SEMICOLON_OR_END = /[;=]|$/;
@@ -123,12 +123,29 @@ const encode = (value) => {
123123

124124
class MIMEParams {
125125
#data = new SafeMap();
126+
// We set the flag the MIMEParams instance as processed on initialization
127+
// to defer the parsing of a potentially large string.
128+
#processed = true;
129+
#string = null;
130+
131+
/**
132+
* Used to instantiate a MIMEParams object within the MIMEType class and
133+
* to allow it to be parsed lazily.
134+
*/
135+
static instantiateMimeParams(str) {
136+
const instance = new MIMEParams();
137+
instance.#string = str;
138+
instance.#processed = false;
139+
return instance;
140+
}
126141

127142
delete(name) {
143+
this.#parse();
128144
this.#data.delete(name);
129145
}
130146

131147
get(name) {
148+
this.#parse();
132149
const data = this.#data;
133150
if (data.has(name)) {
134151
return data.get(name);
@@ -137,10 +154,12 @@ class MIMEParams {
137154
}
138155

139156
has(name) {
157+
this.#parse();
140158
return this.#data.has(name);
141159
}
142160

143161
set(name, value) {
162+
this.#parse();
144163
const data = this.#data;
145164
name = `${name}`;
146165
value = `${value}`;
@@ -166,18 +185,22 @@ class MIMEParams {
166185
}
167186

168187
*entries() {
188+
this.#parse();
169189
yield* this.#data.entries();
170190
}
171191

172192
*keys() {
193+
this.#parse();
173194
yield* this.#data.keys();
174195
}
175196

176197
*values() {
198+
this.#parse();
177199
yield* this.#data.values();
178200
}
179201

180202
toString() {
203+
this.#parse();
181204
let ret = '';
182205
for (const { 0: key, 1: value } of this.#data) {
183206
const encoded = encode(value);
@@ -190,8 +213,11 @@ class MIMEParams {
190213

191214
// Used to act as a friendly class to stringifying stuff
192215
// not meant to be exposed to users, could inject invalid values
193-
static parseParametersString(str, position, params) {
194-
const paramsMap = params.#data;
216+
#parse() {
217+
if (this.#processed) return; // already parsed
218+
const paramsMap = this.#data;
219+
let position = 0;
220+
const str = this.#string;
195221
const endOfSource = SafeStringPrototypeSearch(
196222
StringPrototypeSlice(str, position),
197223
START_ENDING_WHITESPACE,
@@ -270,13 +296,14 @@ class MIMEParams {
270296
NOT_HTTP_TOKEN_CODE_POINT) === -1 &&
271297
SafeStringPrototypeSearch(parameterValue,
272298
NOT_HTTP_QUOTED_STRING_CODE_POINT) === -1 &&
273-
params.has(parameterString) === false
299+
paramsMap.has(parameterString) === false
274300
) {
275301
paramsMap.set(parameterString, parameterValue);
276302
}
277303
position++;
278304
}
279-
return paramsMap;
305+
this.#data = paramsMap;
306+
this.#processed = true;
280307
}
281308
}
282309
const MIMEParamsStringify = MIMEParams.prototype.toString;
@@ -293,8 +320,8 @@ ObjectDefineProperty(MIMEParams.prototype, 'toJSON', {
293320
writable: true,
294321
});
295322

296-
const { parseParametersString } = MIMEParams;
297-
delete MIMEParams.parseParametersString;
323+
const { instantiateMimeParams } = MIMEParams;
324+
delete MIMEParams.instantiateMimeParams;
298325

299326
class MIMEType {
300327
#type;
@@ -303,14 +330,9 @@ class MIMEType {
303330
constructor(string) {
304331
string = `${string}`;
305332
const data = parseTypeAndSubtype(string);
306-
this.#type = data.type;
307-
this.#subtype = data.subtype;
308-
this.#parameters = new MIMEParams();
309-
parseParametersString(
310-
string,
311-
data.parametersStringIndex,
312-
this.#parameters,
313-
);
333+
this.#type = data[0];
334+
this.#subtype = data[1];
335+
this.#parameters = instantiateMimeParams(StringPrototypeSlice(string, data[2]));
314336
}
315337

316338
get type() {
@@ -362,6 +384,8 @@ ObjectDefineProperty(MIMEType.prototype, 'toJSON', {
362384
});
363385

364386
module.exports = {
387+
toASCIILower,
388+
parseTypeAndSubtype,
365389
MIMEParams,
366390
MIMEType,
367391
};
Collapse file
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
require('../common');
4+
5+
const runBenchmark = require('../common/benchmark');
6+
7+
runBenchmark('mime', { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 });

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.