Skip to content

Commit

Permalink
Add benches and tests for normalization, improve normalization perf
Browse files Browse the repository at this point in the history
  • Loading branch information
bantic committed May 18, 2016
1 parent 5bf1311 commit 0861ce2
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 39 deletions.
2 changes: 1 addition & 1 deletion Brocfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function buildTestSuite (libTree) {
var jsHintLib = jsHint(libTree);

var testTree = new Funnel( 'tests', {
files: ['recognizer-tests.js', 'router-tests.js'],
files: ['recognizer-tests.js', 'router-tests.js', 'normalizer-tests.js'],
destDir: destination
});

Expand Down
28 changes: 28 additions & 0 deletions bench/benches/normalize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
var RouteRecognizer = require('../../dist/route-recognizer');
var Normalizer = RouteRecognizer.Normalizer;

var router = new RouteRecognizer();

var paths = {
complex: "/foo/" + encodeURIComponent("http://example.com/index.html?foo=bar&baz=faz#hashtag"),
simple: "/post/123",
medium: "/abc%3Adef"

};

module.exports = [{
name: 'Normalize Complex',
fn: function() {
Normalizer.normalizePath(paths.complex);
}
}, {
name: 'Normalize Simple',
fn: function() {
Normalizer.normalizePath(paths.simple);
}
}, {
name: 'Normalize Medium',
fn: function() {
Normalizer.normalizePath(paths.medium);
}
}];
14 changes: 11 additions & 3 deletions bench/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ var glob = require('glob');
var path = require('path');
var bench = require('do-you-even-bench');

bench(glob.sync( './bench/benches/*.js' ).map( function( file ) {
return require( path.resolve( file ) );
}));
var suites = [];
glob.sync( './bench/benches/*.js' ).forEach(function(file) {
var exported = require( path.resolve( file ) );
if (Array.isArray(exported)) {
suites = suites.concat(exported);
} else {
suites.push(exported);
}
});

bench(suites);
8 changes: 5 additions & 3 deletions lib/route-recognizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import map from './route-recognizer/dsl';
import Normalizer from './route-recognizer/normalizer';

var normalizePath = Normalizer.normalizePath;
var normalizeRouteSegment = Normalizer.normalizeRouteSegment;
var normalizeSegment = Normalizer.normalizeSegment;

var specials = [
'/', '.', '*', '+', '?', '|',
Expand Down Expand Up @@ -32,7 +32,7 @@ function isArray(test) {
// * `invalidChars`: a String with a list of all invalid characters
// * `repeat`: true if the character specification can repeat

function StaticSegment(string) { this.string = normalizeRouteSegment(string); }
function StaticSegment(string) { this.string = normalizeSegment(string); }
StaticSegment.prototype = {
eachChar: function(currentState) {
var string = this.string, ch;
Expand All @@ -54,7 +54,7 @@ StaticSegment.prototype = {
}
};

function DynamicSegment(name) { this.name = normalizeRouteSegment(name); }
function DynamicSegment(name) { this.name = normalizeSegment(name); }
DynamicSegment.prototype = {
eachChar: function(currentState) {
return currentState.put({ invalidChars: "/", repeat: true, validChars: undefined });
Expand Down Expand Up @@ -559,4 +559,6 @@ RouteRecognizer.VERSION = 'VERSION_STRING_PLACEHOLDER';
// See https://github.com/tildeio/route-recognizer/pull/55
RouteRecognizer.ENCODE_AND_DECODE_PATH_SEGMENTS = true;

RouteRecognizer.Normalizer = Normalizer;

export default RouteRecognizer;
72 changes: 40 additions & 32 deletions lib/route-recognizer/normalizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function percentEncodedValuesToUpper(string) {
// Safe to call multiple times on the same path.
function normalizePath(path) {
return path.split('/')
.map(normalizePathSegment)
.map(normalizeSegment)
.join('/');
}

Expand All @@ -26,48 +26,56 @@ function charToHex(char) {
}

// Decodes percent-encoded values in the string except those
// characters in `reservedSet`
function decodeURIComponentExcept(string, reservedSet) {
// characters in `reservedHex`, where `reservedHex` is an array of 2-character
// percent-encodings
function decodeURIComponentExcept(string, reservedHex) {
if (string.indexOf('%') === -1) {
// If there is no percent char, there is no decoding that needs to
// be done and we exit early
return string;
}
string = percentEncodedValuesToUpper(string);
var replacements = {};

for (var i=0; i < reservedSet.length; i++) {
var char = reservedSet[i];
var pChar = percentEncode(char);
if (string.indexOf(pChar) !== -1) {
var replacement = "__" + charToHex(char) + "__";
replacements[pChar] = replacement;

var pCharRegex = new RegExp(pChar, 'g');
string = string.replace(pCharRegex, replacement);
var result = '';
var buffer = '';
var idx = 0;
while (idx < string.length) {
var pIdx = string.indexOf('%', idx);

if (pIdx === -1) { // no percent char
buffer += string.slice(idx);
break;
} else { // found percent char
buffer += string.slice(idx, pIdx);
idx = pIdx + 3;

var hex = string.slice(pIdx + 1, pIdx + 3);
var encoded = '%' + hex;

if (reservedHex.indexOf(hex) === -1) {
// encoded is not in reserved set, add to buffer
buffer += encoded;
} else {
result += decodeURIComponent(buffer);
buffer = '';
result += encoded;
}
}
}
string = decodeURIComponent(string);

Object.keys(replacements).forEach(function(pChar) {
var replacement = replacements[pChar];
var replacementRegex = new RegExp(replacement, 'g');

string = string.replace(replacementRegex, pChar);
});

return string;
result += decodeURIComponent(buffer);
return result;
}

// Leave these characters in encoded state in segments
var reservedRouteSegmentChars = ['%', '/'];
var reservedPathSegmentChars = ['%', '/'];

function normalizeRouteSegment(segment) {
return decodeURIComponentExcept(segment, reservedRouteSegmentChars);
}
var reservedSegmentChars = ['%', '/'];
var reservedHex = reservedSegmentChars.map(charToHex);

function normalizePathSegment(segment) {
return decodeURIComponentExcept(segment, reservedPathSegmentChars);
function normalizeSegment(segment) {
return decodeURIComponentExcept(segment, reservedHex);
}

var Normalizer = {
normalizeRouteSegment: normalizeRouteSegment,
normalizeSegment: normalizeSegment,
normalizePath: normalizePath
};

Expand Down
44 changes: 44 additions & 0 deletions tests/normalizer-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* globals QUnit */

import RouteRecognizer from 'route-recognizer';

var Normalizer = RouteRecognizer.Normalizer;

module("Normalization");

var expectations = [{
paths: ["/foo/bar"],
normalized: "/foo/bar"
}, {
paths: ["/foo%3Abar", "/foo%3abar"],
normalized: "/foo:bar"
}, {
paths: ["/foo%2fbar", "/foo%2Fbar"],
normalized: "/foo%2Fbar"
}, {
paths: ["/café", "/caf%C3%A9", "/caf%c3%a9"],
normalized: "/café"
}, {
paths: ["/abc%25def"],
normalized: "/abc%25def"
}, {
paths: ["/" + encodeURIComponent("http://example.com/index.html?foo=100%&baz=boo#hash")],
normalized: "/http:%2F%2Fexample.com%2Findex.html?foo=100%25&baz=boo#hash"
}, {
paths: ["/%25%25%25%25"],
normalized: "/%25%25%25%25"
}, {
paths: ["/%25%25%25%25%3A%3a%2F%2f%2f"],
normalized: "/%25%25%25%25::%2F%2F%2F"
}];

expectations.forEach(function(expectation) {
var paths = expectation.paths;
var normalized = expectation.normalized;

paths.forEach(function(path) {
test("the path '" + path + "' is normalized to '" + normalized + "'", function() {
equal(Normalizer.normalizePath(path), normalized);
});
});
});

0 comments on commit 0861ce2

Please sign in to comment.