summaryrefslogtreecommitdiff
path: root/lib/querystring.js
diff options
context:
space:
mode:
authorBrian White <mscdex@mscdex.net>2017-01-18 04:19:03 -0500
committerBrian White <mscdex@mscdex.net>2017-01-25 02:05:26 -0500
commit2298bc4b1fdcdbe8f5fc8f405033e879fe9bcd4e (patch)
tree53b99a377efa2a5f06fe8c9ba353094bf29b7f80 /lib/querystring.js
parent9e0f6a5eb4754627f9abf892118bce95d10eb149 (diff)
downloadandroid-node-v8-2298bc4b1fdcdbe8f5fc8f405033e879fe9bcd4e.tar.gz
android-node-v8-2298bc4b1fdcdbe8f5fc8f405033e879fe9bcd4e.tar.bz2
android-node-v8-2298bc4b1fdcdbe8f5fc8f405033e879fe9bcd4e.zip
querystring: improve parse() performance
PR-URL: https://github.com/nodejs/node/pull/10874 Reviewed-By: James M Snell <jasnell@gmail.com>
Diffstat (limited to 'lib/querystring.js')
-rw-r--r--lib/querystring.js104
1 files changed, 62 insertions, 42 deletions
diff --git a/lib/querystring.js b/lib/querystring.js
index ff1b47fb51..c22c7c998d 100644
--- a/lib/querystring.js
+++ b/lib/querystring.js
@@ -1,8 +1,19 @@
-// Query String Utilities
-
'use strict';
-const QueryString = exports;
+const QueryString = module.exports = {
+ unescapeBuffer,
+ // `unescape()` is a JS global, so we need to use a different local name
+ unescape: qsUnescape,
+
+ // `escape()` is a JS global, so we need to use a different local name
+ escape: qsEscape,
+
+ stringify,
+ encode: stringify,
+
+ parse,
+ decode: parse
+};
const Buffer = require('buffer').Buffer;
// This constructor is used to store parsed query string values. Instantiating
@@ -13,7 +24,7 @@ ParsedQueryString.prototype = Object.create(null);
// a safe fast alternative to decodeURIComponent
-QueryString.unescapeBuffer = function(s, decodeSpaces) {
+function unescapeBuffer(s, decodeSpaces) {
var out = Buffer.allocUnsafe(s.length);
var state = 0;
var n, m, hexchar;
@@ -77,7 +88,7 @@ QueryString.unescapeBuffer = function(s, decodeSpaces) {
// TODO support returning arbitrary buffers.
return out.slice(0, outIndex - 1);
-};
+}
function qsUnescape(s, decodeSpaces) {
@@ -87,13 +98,12 @@ function qsUnescape(s, decodeSpaces) {
return QueryString.unescapeBuffer(s, decodeSpaces).toString();
}
}
-QueryString.unescape = qsUnescape;
var hexTable = new Array(256);
for (var i = 0; i < 256; ++i)
hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();
-QueryString.escape = function(str) {
+function qsEscape(str) {
// replaces encodeURIComponent
// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4
if (typeof str !== 'string') {
@@ -164,9 +174,9 @@ QueryString.escape = function(str) {
if (lastPos < str.length)
return out + str.slice(lastPos);
return out;
-};
+}
-var stringifyPrimitive = function(v) {
+function stringifyPrimitive(v) {
if (typeof v === 'string')
return v;
if (typeof v === 'number' && isFinite(v))
@@ -174,10 +184,10 @@ var stringifyPrimitive = function(v) {
if (typeof v === 'boolean')
return v ? 'true' : 'false';
return '';
-};
+}
-QueryString.stringify = QueryString.encode = function(obj, sep, eq, options) {
+function stringify(obj, sep, eq, options) {
sep = sep || '&';
eq = eq || '=';
@@ -215,34 +225,43 @@ QueryString.stringify = QueryString.encode = function(obj, sep, eq, options) {
return fields;
}
return '';
-};
+}
-// Parse a key/val string.
-QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
- sep = sep || '&';
- eq = eq || '=';
+function charCodes(str) {
+ if (str.length === 0) return [];
+ if (str.length === 1) return [str.charCodeAt(0)];
+ const ret = [];
+ for (var i = 0; i < str.length; ++i)
+ ret[ret.length] = str.charCodeAt(i);
+ return ret;
+}
+const defSepCodes = [38]; // &
+const defEqCodes = [61]; // =
+// Parse a key/val string.
+function parse(qs, sep, eq, options) {
const obj = new ParsedQueryString();
if (typeof qs !== 'string' || qs.length === 0) {
return obj;
}
- if (typeof sep !== 'string')
- sep += '';
-
- const eqLen = eq.length;
- const sepLen = sep.length;
+ var sepCodes = (!sep ? defSepCodes : charCodes(sep + ''));
+ var eqCodes = (!eq ? defEqCodes : charCodes(eq + ''));
+ const sepLen = sepCodes.length;
+ const eqLen = eqCodes.length;
- var maxKeys = 1000;
+ var pairs = 1000;
if (options && typeof options.maxKeys === 'number') {
- maxKeys = options.maxKeys;
+ // -1 is used in place of a value like Infinity for meaning
+ // "unlimited pairs" because of additional checks V8 (at least as of v5.4)
+ // has to do when using variables that contain values like Infinity. Since
+ // `pairs` is always decremented and checked explicitly for 0, -1 works
+ // effectively the same as Infinity, while providing a significant
+ // performance boost.
+ pairs = (options.maxKeys > 0 ? options.maxKeys : -1);
}
- var pairs = Infinity;
- if (maxKeys > 0)
- pairs = maxKeys;
-
var decode = QueryString.unescape;
if (options && typeof options.decodeURIComponent === 'function') {
decode = options.decodeURIComponent;
@@ -262,7 +281,7 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
const code = qs.charCodeAt(i);
// Try matching key/value pair separator (e.g. '&')
- if (code === sep.charCodeAt(sepIdx)) {
+ if (code === sepCodes[sepIdx]) {
if (++sepIdx === sepLen) {
// Key/value pair separator match!
const end = i - sepIdx + 1;
@@ -284,10 +303,10 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
keys[keys.length] = key;
} else {
const curValue = obj[key];
- // `instanceof Array` is used instead of Array.isArray() because it
- // is ~15-20% faster with v8 4.7 and is safe to use because we are
- // using it with values being created within this function
- if (curValue instanceof Array)
+ // A simple Array-specific property check is enough here to
+ // distinguish from a string value and is faster and still safe since
+ // we are generating all of the values being assigned.
+ if (curValue.pop)
curValue[curValue.length] = value;
else
obj[key] = [curValue, value];
@@ -322,7 +341,7 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
// Try matching key/value separator (e.g. '=') if we haven't already
if (eqIdx < eqLen) {
- if (code === eq.charCodeAt(eqIdx)) {
+ if (code === eqCodes[eqIdx]) {
if (++eqIdx === eqLen) {
// Key/value separator match!
const end = i - eqIdx + 1;
@@ -354,12 +373,12 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
if (code === 43/*+*/) {
if (eqIdx < eqLen) {
- if (i - lastPos > 0)
+ if (lastPos < i)
key += qs.slice(lastPos, i);
key += '%20';
keyEncoded = true;
} else {
- if (i - lastPos > 0)
+ if (lastPos < i)
value += qs.slice(lastPos, i);
value += '%20';
valEncoded = true;
@@ -369,7 +388,7 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
}
// Check if we have leftover key or value data
- if (pairs > 0 && (lastPos < qs.length || eqIdx > 0)) {
+ if (pairs !== 0 && (lastPos < qs.length || eqIdx > 0)) {
if (lastPos < qs.length) {
if (eqIdx < eqLen)
key += qs.slice(lastPos);
@@ -387,10 +406,10 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
keys[keys.length] = key;
} else {
const curValue = obj[key];
- // `instanceof Array` is used instead of Array.isArray() because it
- // is ~15-20% faster with v8 4.7 and is safe to use because we are
- // using it with values being created within this function
- if (curValue instanceof Array)
+ // A simple Array-specific property check is enough here to
+ // distinguish from a string value and is faster and still safe since
+ // we are generating all of the values being assigned.
+ if (curValue.pop)
curValue[curValue.length] = value;
else
obj[key] = [curValue, value];
@@ -398,11 +417,12 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
}
return obj;
-};
+}
// v8 does not optimize functions with try-catch blocks, so we isolate them here
-// to minimize the damage
+// to minimize the damage (Note: no longer true as of V8 5.4 -- but still will
+// not be inlined).
function decodeStr(s, decoder) {
try {
return decoder(s);