Skip to content

Commit cae6ad7

Browse files
committed
util: lazy parse mime parameters
1 parent fef7927 commit cae6ad7

File tree

7 files changed

+269
-24
lines changed

7 files changed

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

benchmark/mime/to-ascii-lower.js

Lines 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+
}

lib/internal/mime.js

Lines changed: 34 additions & 19 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 = /[;=]|$/;
@@ -122,13 +122,24 @@ const encode = (value) => {
122122
};
123123

124124
class MIMEParams {
125-
#data = new SafeMap();
125+
#data = null;
126+
#string = null;
127+
128+
constructor(str) {
129+
if (str !== undefined) {
130+
this.#string = str;
131+
} else {
132+
this.#data = new SafeMap();
133+
}
134+
}
126135

127136
delete(name) {
137+
this.#parse();
128138
this.#data.delete(name);
129139
}
130140

131141
get(name) {
142+
this.#parse();
132143
const data = this.#data;
133144
if (data.has(name)) {
134145
return data.get(name);
@@ -137,10 +148,12 @@ class MIMEParams {
137148
}
138149

139150
has(name) {
151+
this.#parse();
140152
return this.#data.has(name);
141153
}
142154

143155
set(name, value) {
156+
this.#parse();
144157
const data = this.#data;
145158
name = `${name}`;
146159
value = `${value}`;
@@ -166,18 +179,22 @@ class MIMEParams {
166179
}
167180

168181
*entries() {
182+
this.#parse();
169183
yield* this.#data.entries();
170184
}
171185

172186
*keys() {
187+
this.#parse();
173188
yield* this.#data.keys();
174189
}
175190

176191
*values() {
192+
this.#parse();
177193
yield* this.#data.values();
178194
}
179195

180196
toString() {
197+
this.#parse();
181198
let ret = '';
182199
for (const { 0: key, 1: value } of this.#data) {
183200
const encoded = encode(value);
@@ -190,8 +207,12 @@ class MIMEParams {
190207

191208
// Used to act as a friendly class to stringifying stuff
192209
// not meant to be exposed to users, could inject invalid values
193-
static parseParametersString(str, position, params) {
194-
const paramsMap = params.#data;
210+
#parse() {
211+
if (this.#data !== null) return; // already parsed
212+
this.#data = new SafeMap();
213+
const paramsMap = this.#data;
214+
let position = 0;
215+
const str = this.#string;
195216
const endOfSource = SafeStringPrototypeSearch(
196217
StringPrototypeSlice(str, position),
197218
START_ENDING_WHITESPACE,
@@ -270,7 +291,7 @@ class MIMEParams {
270291
NOT_HTTP_TOKEN_CODE_POINT) === -1 &&
271292
SafeStringPrototypeSearch(parameterValue,
272293
NOT_HTTP_QUOTED_STRING_CODE_POINT) === -1 &&
273-
params.has(parameterString) === false
294+
paramsMap.has(parameterString) === false
274295
) {
275296
paramsMap.set(parameterString, parameterValue);
276297
}
@@ -293,24 +314,16 @@ ObjectDefineProperty(MIMEParams.prototype, 'toJSON', {
293314
writable: true,
294315
});
295316

296-
const { parseParametersString } = MIMEParams;
297-
delete MIMEParams.parseParametersString;
298-
299317
class MIMEType {
300318
#type;
301319
#subtype;
302320
#parameters;
303321
constructor(string) {
304322
string = `${string}`;
305323
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-
);
324+
this.#type = data[0];
325+
this.#subtype = data[1];
326+
this.#parameters = new MIMEParams(string.slice(data[2]));
314327
}
315328

316329
get type() {
@@ -362,6 +375,8 @@ ObjectDefineProperty(MIMEType.prototype, 'toJSON', {
362375
});
363376

364377
module.exports = {
378+
toASCIILower,
379+
parseTypeAndSubtype,
365380
MIMEParams,
366381
MIMEType,
367382
};
Lines 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)