From 6b27b844ff2fbc2725facdbb8537330035537475 Mon Sep 17 00:00:00 2001 From: Jonatan Nilsson Date: Fri, 11 Oct 2019 19:41:53 +0000 Subject: [PATCH] Add fast parse and remove parseurl --- lib/fastparse.js | 57 ++++++++++++++++++++++++++++++++++++++ lib/request.js | 17 ++++++++---- package.json | 1 - test/parseurl/fastparse.js | 43 ++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 lib/fastparse.js create mode 100644 test/parseurl/fastparse.js diff --git a/lib/fastparse.js b/lib/fastparse.js new file mode 100644 index 0000000..6a2e1d1 --- /dev/null +++ b/lib/fastparse.js @@ -0,0 +1,57 @@ +const url = require('url'); + +/** + * Parse the `str` url with fast-path short-cut. + * + * @param {string} str + * @return {Object} + * @private + */ + +module.exports = function fastparse(str) { + if (typeof str !== 'string' || str.charCodeAt(0) !== 0x2f /* / */) { + return url.parse(str); + } + + let pathname = str; + let query = null; + let search = null; + + // This takes the regexp from https://github.com/joyent/node/pull/7878 + // Which is /^(\/[^?#\s]*)(\?[^#\s]*)?$/ + // And unrolls it into a for loop + for (let i = 1; i < str.length; i++) { + switch (str.charCodeAt(i)) { + case 0x3f: /* ? */ + if (search === null) { + pathname = str.substring(0, i); + query = str.substring(i + 1); + search = str.substring(i); + } + break; + case 0x09: /* \t */ + case 0x0a: /* \n */ + case 0x0c: /* \f */ + case 0x0d: /* \r */ + case 0x20: /* */ + case 0x23: /* # */ + case 0xa0: + case 0xfeff: + return url.parse(str); + } + } + + let parsed = new url.Url(); + + parsed.path = str; + parsed.href = str; + parsed.pathname = pathname; + + if (search !== null) { + parsed.query = query; + parsed.search = search; + } + parsed.__raw = str; + + return parsed; +}; diff --git a/lib/request.js b/lib/request.js index 59bfb8d..2eb529e 100644 --- a/lib/request.js +++ b/lib/request.js @@ -8,7 +8,7 @@ const URL = require('url').URL; const net = require('net'); const stringify = require('url').format; -const parse = require('parseurl'); +const fastparse = require('./fastparse'); const qs = require('querystring'); const typeis = require('type-is'); const fresh = require('fresh'); @@ -139,7 +139,14 @@ module.exports = { */ get path() { - return parse(this.req).pathname; + return this.urlParsed.pathname; + }, + + get urlParsed() { + if (!this.req.__url || this.req.__url.__raw !== this.req.url) { + this.req.__url = fastparse(this.req.url); + } + return this.req.__url; }, /** @@ -150,7 +157,7 @@ module.exports = { */ set path(path) { - const url = parse(this.req); + const url = this.urlParsed; if (url.pathname === path) return; url.pathname = path; @@ -192,7 +199,7 @@ module.exports = { get querystring() { if (!this.req) return ''; - return parse(this.req).query || ''; + return this.urlParsed.query || ''; }, /** @@ -203,7 +210,7 @@ module.exports = { */ set querystring(str) { - const url = parse(this.req); + const url = this.urlParsed; if (url.search === `?${str}`) return; url.search = str; diff --git a/package.json b/package.json index c8cf2dc..c4ab5b3 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "koa-compose": "^4.1.0", "koa-is-json": "^1.0.0", "on-finished": "^2.3.0", - "parseurl": "^1.3.2", "type-is": "^1.6.16", "vary": "^1.1.2" }, diff --git a/test/parseurl/fastparse.js b/test/parseurl/fastparse.js new file mode 100644 index 0000000..87457c3 --- /dev/null +++ b/test/parseurl/fastparse.js @@ -0,0 +1,43 @@ +const assert = require('assert'); +const fastparse = require('../../lib/fastparse'); + +const URL_EMPTY_VALUE = null; + +describe('fastparse(url)', () => { + it('should parse the requrst URL', () => { + let url = fastparse('/foo/bar'); + assert.strictEqual(url.host, URL_EMPTY_VALUE); + assert.strictEqual(url.hostname, URL_EMPTY_VALUE); + assert.strictEqual(url.href, '/foo/bar'); + assert.strictEqual(url.pathname, '/foo/bar'); + assert.strictEqual(url.port, URL_EMPTY_VALUE); + assert.strictEqual(url.query, URL_EMPTY_VALUE); + assert.strictEqual(url.search, URL_EMPTY_VALUE); + }); + + it('should parse with query string', () => { + let url = fastparse('/foo/bar?fizz=buzz'); + assert.strictEqual(url.host, URL_EMPTY_VALUE); + assert.strictEqual(url.hostname, URL_EMPTY_VALUE); + assert.strictEqual(url.href, '/foo/bar?fizz=buzz'); + assert.strictEqual(url.pathname, '/foo/bar'); + assert.strictEqual(url.port, URL_EMPTY_VALUE); + assert.strictEqual(url.query, 'fizz=buzz'); + assert.strictEqual(url.search, '?fizz=buzz'); + }); + + it('should parse a full URL', () => { + let url = fastparse('http://localhost:8888/foo/bar'); + assert.strictEqual(url.host, 'localhost:8888'); + assert.strictEqual(url.hostname, 'localhost'); + assert.strictEqual(url.href, 'http://localhost:8888/foo/bar'); + assert.strictEqual(url.pathname, '/foo/bar'); + assert.strictEqual(url.port, '8888'); + assert.strictEqual(url.query, URL_EMPTY_VALUE); + assert.strictEqual(url.search, URL_EMPTY_VALUE); + }); + + it('should not choke on auth-looking URL', () => { + assert.strictEqual(fastparse('//todo@txt').pathname, '//todo@txt'); + }); +});