Skip to content

Commit 37d676c

Browse files
committedJul 10, 2019
first commit
0 parents  commit 37d676c

13 files changed

+346
-0
lines changed
 

‎.github/FUNDING.yml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# These are supported funding model platforms
2+
3+
github: song940
4+
patreon: song940
5+
open_collective: song940
6+
ko_fi: song940
7+
tidelift: npm/kanary
8+
custom: https://git.io/fjRcB

‎.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
*.log
3+
yarn.lock
4+
package-lock.json
5+
6+
node_modules/

‎.npmignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
*.log
3+
4+
node_modules/

‎.travis.yml

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
language: node_js
2+
node_js:
3+
- "stable"

‎LICENSE

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
ISC License (ISC)
2+
Copyright 2017
3+
4+
Permission to use, copy, modify, and/or distribute this software for any purpose
5+
with or without fee is hereby granted, provided that the above copyright notice
6+
and this permission notice appear in all copies.
7+
8+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
10+
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
11+
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
12+
DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
13+
ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

‎README.md

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
## routing2
2+
3+
> Parse HTTP Routing definition with no dependencies, just ~3kb!
4+
5+
[![routing2](https://img.shields.io/npm/v/routing2.svg)](https://npmjs.org/routing2)
6+
[![Build Status](https://travis-ci.org/song940/routing2.svg?branch=master)](https://travis-ci.org/song940/routing2)
7+
8+
### Define
9+
10+
<img src="./define.png" width="50%" >
11+
12+
### Installation
13+
14+
```bash
15+
$ npm install routing2
16+
```
17+
18+
### Example
19+
20+
```js
21+
const routing = require('routing2');
22+
23+
const routes = routing.parse(`
24+
get / => home#index
25+
get /:name => user#index, { "foo": "bar" }
26+
post /user => user#create
27+
`);
28+
29+
const request = {
30+
method: 'get',
31+
url: '/lsong?foo=bar'
32+
};
33+
34+
const route = routing.find(routes, request);
35+
36+
console.log(route);
37+
// { status: 200,
38+
// route:
39+
// { domain: undefined,
40+
// path: '/:name',
41+
// action: 'index',
42+
// controller: 'user',
43+
// options: { foo: "bar" },
44+
// method: 'GET' },
45+
// params: { name: 'lsong' },
46+
// query: { foo: 'bar' } }
47+
```
48+
49+
### Contributing
50+
- Fork this Repo first
51+
- Clone your Repo
52+
- Install dependencies by `$ npm install`
53+
- Checkout a feature branch
54+
- Feel free to add your features
55+
- Make sure your features are fully tested
56+
- Publish your local branch, Open a pull request
57+
- Enjoy hacking <3
58+
59+
### MIT
60+
61+
This work is licensed under the [MIT license](./LICENSE).
62+
63+
---

‎define.png

49.7 KB
Loading

‎example/index.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const routing = require('..');
2+
3+
const routes = routing.parse(`
4+
get / => home#index, { "foo": "bar" }
5+
get /:name => user#index
6+
post /user => user#create
7+
`);
8+
9+
const request = {
10+
method: 'get',
11+
url: '/lsong?foo=bar'
12+
};
13+
14+
const route = routing.find(routes, request);
15+
16+
console.log(route);
17+

‎index.js

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
const fs = require('fs');
2+
const url = require('url');
3+
4+
function removeQuotes(str){
5+
return str.trim().replace(/"|'/ig, '');
6+
};
7+
8+
const parseLine = line => {
9+
if (!line.trim() || /^(#|\/\/)/.test(line)) return;
10+
const m = /(\w+)\s+(.+)\s*=>\s*(.+)#(\w+)(\s*,(.+))?/.exec(line);
11+
if (!m) throw new SyntaxError('Unexpected token', line);
12+
let domain, path = removeQuotes(m[2].trim());
13+
const i = path.indexOf('/');
14+
if (i > 0) {
15+
domain = path.substr(0, i);
16+
path = path.substr(i);
17+
}
18+
return {
19+
domain,
20+
path,
21+
action: removeQuotes(m[4].trim()),
22+
controller: removeQuotes(m[3].trim()),
23+
options: m[6] && JSON.parse(m[6]),
24+
method: m[1].trim().toUpperCase(),
25+
}
26+
};
27+
28+
const parse = content =>
29+
content.split(/\r?\n/g)
30+
.map(parseLine)
31+
.filter(Boolean)
32+
.map(create);
33+
34+
const load = filename =>
35+
parse(fs.readFileSync(filename, 'utf8'));
36+
37+
const pathToRegexp = path => {
38+
if(path instanceof RegExp) return path;
39+
let arr = path.split('/'), pattern = '', keys = [];
40+
(arr[0] === '') && arr.shift();
41+
arr.forEach((p, i) => {
42+
switch(p[0]){
43+
case '*':
44+
pattern += '/(.+)';
45+
keys.push(p.substring(1) || `$${i}`);
46+
break;
47+
case ':':
48+
const o = p.substr(-1);
49+
const r = '([^/]+?)';
50+
const m = {
51+
'?': `(?:/${r})?`,
52+
'*': '(?:/)(.*)'
53+
};
54+
pattern += m[o] || `/${r}`;
55+
keys.push(p.substring(1, p.length - !!m[o]));
56+
break;
57+
default:
58+
pattern += `/${p}`;
59+
break;
60+
}
61+
});
62+
keys.length && (pattern += '(?:/)?');
63+
pattern = new RegExp(`^${pattern}\/?$`, 'i');
64+
pattern.keys = keys;
65+
pattern.parse = function(pathname) {
66+
if(this.test(pathname) === false) return null;
67+
return this.exec(pathname).slice(1).reduce((params, param, i) => {
68+
params[ this.keys[i] ] = param && decodeURIComponent(param);
69+
return params;
70+
}, {});
71+
};
72+
return pattern;
73+
};
74+
75+
const find = (routes, req) => {
76+
const [ domain ] = (req.host || '').split(':');// omit port
77+
const { pathname, query } = url.parse(req.url, true);
78+
const m = routes
79+
.filter(route =>
80+
(route.domain ? route.domain === domain : true) &&
81+
route.regexp.test(pathname))
82+
.sort((a, b) => {
83+
const { priority: aPriority = 0 } = a;
84+
const { priority: bPriority = 0 } = b;
85+
return bPriority - aPriority;
86+
});
87+
if (!m.length) return { status: 404 };
88+
const allowMethods = m.map(route => route.method.toUpperCase());
89+
const methodIndex = allowMethods.findIndex(x => x === '*' || x === req.method.toUpperCase());
90+
if (!~methodIndex && req.method === 'OPTIONS')
91+
return { status: 204, allowMethods, routes: m };
92+
if (!~methodIndex) return { status: 405, allowMethods, routes: m };
93+
const route = m[ methodIndex ];
94+
const params = route.regexp.parse(pathname);
95+
// will deprecated in future
96+
route.params = params;
97+
return { status: 200, route, params, query };
98+
};
99+
100+
const create = route => {
101+
if(typeof route === 'string')
102+
route = parseLine(route);
103+
route.method = (route.method || '*');
104+
route.regexp = pathToRegexp(route.path);
105+
return route;
106+
};
107+
108+
module.exports = {
109+
find,
110+
load,
111+
parse,
112+
create,
113+
parseLine,
114+
pathToRegexp
115+
};

‎package.json

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "routing2",
3+
"version": "0.0.15",
4+
"description": "Parse HTTP Routing definition with no dependencies, just ~3kb!",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "node test"
8+
},
9+
"keywords": [
10+
"route",
11+
"http"
12+
],
13+
"author": "lsong",
14+
"license": "MIT",
15+
"repository": {
16+
"type": "git",
17+
"url": "git+https://github.com/song940/routing2.git"
18+
},
19+
"bugs": {
20+
"url": "https://github.com/song940/routing2/issues"
21+
},
22+
"homepage": "https://github.com/song940/routing2#readme",
23+
"directories": {
24+
"example": "example"
25+
}
26+
}

‎test/index.js

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
const assert = require('assert');
2+
const test = require('./test');
3+
const { parse, pathToRegexp, find, load } = require('..');
4+
5+
var r, s;
6+
7+
test('normal path', () => {
8+
r = pathToRegexp('/normal/path');
9+
assert.ok(r.parse('/normal/path'));
10+
assert.equal(r.parse('/aaa'), null);
11+
});
12+
13+
test('optional parameter', () => {
14+
r = pathToRegexp('/v1/:name*');
15+
assert.deepEqual(r.parse('/v1/aaa/bbb'), { name: 'aaa/bbb'})
16+
});
17+
18+
test('named and optional parameter', () => {
19+
r = pathToRegexp('/:a/:b*');
20+
s = '/test/';
21+
assert.ok(r.test(s));
22+
assert.deepEqual(r.parse(s), { a: 'test', b: '' });
23+
});
24+
25+
const routes = parse(`
26+
get / => home#index
27+
get /:name => user#user
28+
`);
29+
30+
test('parse routing rules', () => {
31+
assert.equal(routes[0].path, '/');
32+
assert.equal(routes[0].action, 'index');
33+
assert.equal(routes[1].action, 'user');
34+
assert.equal(routes[1].controller, 'user');
35+
});
36+
37+
test('find match rule in routes', () => {
38+
r = find(routes, {
39+
host: 'google.com',
40+
method: 'GET',
41+
url: '/1?a=b'
42+
});
43+
assert.equal(r.status, 200);
44+
assert.equal(r.route.action, 'user');
45+
assert.equal(r.route.controller, 'user');
46+
assert.deepEqual(r.query, { a: 'b' });
47+
assert.deepEqual(r.params, { name: '1' });
48+
});
49+
50+
test('load routes from file', () => {
51+
const routes = load(__dirname + '/routes.txt');
52+
assert.ok(Array.isArray(routes));
53+
assert.equal(routes.length, 2);
54+
assert.equal(routes[0].controller, 'home');
55+
assert.equal(routes[0].action, 'index');
56+
assert.equal(routes[1].controller, 'user');
57+
assert.equal(routes[1].action, 'create');
58+
});

‎test/routes.txt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
3+
// home controller
4+
get / => home#index
5+
6+
# user
7+
post /user => user#create

‎test/test.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const { inspect } = require('util');
2+
/**
3+
* super tiny testing framework
4+
*
5+
* @author Liu song <hi@lsong.org>
6+
* @github https://github.com/song940
7+
*/
8+
const test = async (title, fn) => {
9+
try {
10+
await fn();
11+
console.log(color(` ✔ ${title}`, 32));
12+
} catch (err) {
13+
console.error(color(` ✘ ${title}`, 31));
14+
console.log();
15+
console.log(color(` ${err.name} ${err.message}`, 31));
16+
console.error(color(` expected: ${inspect(err.expected)}`, 32));
17+
console.error(color(` actual: ${inspect(err.actual)}`, 31));
18+
console.log();
19+
}
20+
};
21+
22+
function color(str, c) {
23+
return "\x1b[" + c + "m" + str + "\x1b[0m";
24+
};
25+
26+
module.exports = test;

0 commit comments

Comments
 (0)
Please sign in to comment.