Browse files

fixed #1 support `express` like routing

  • Loading branch information...
1 parent e565937 commit 4c6fb244d28c32a0fa5a2edf5e44968457aa5118 @fengmk2 fengmk2 committed Jun 20, 2012
Showing with 239 additions and 11 deletions.
  1. +1 −0 .gitignore
  2. +13 −0 README.md
  3. +4 −0 benchmark/simple.js
  4. +3 −0 example/connect-hello.js
  5. +8 −0 example/http-hello.js
  6. +8 −11 lib/urlrouter.js
  7. +69 −0 lib/utils.js
  8. +11 −0 test/urlrouter.js
  9. +122 −0 test/utils.js
View
1 .gitignore
@@ -1,3 +1,4 @@
node_modules
+siege.log
lib-cov
coverage.html
View
13 README.md
@@ -6,6 +6,8 @@
[connect](https://github.com/senchalabs/connect) missing router middleware.
+Support [express](http://expressjs.com) format [routing](http://expressjs.com/guide.html#routing).
+
Support `connect` @1.8.x and @2.2.0+ .
## Test connect version
@@ -36,6 +38,9 @@ connect(urlrouter(function (app) {
app.get('/', function (req, res, next) {
res.end('hello urlrouter');
});
+ app.get('/user/:id([0-9]+)', function (req, res, next) {
+ res.end('hello user ' + req.params.id);
+ });
})).listen(3000);
```
@@ -50,6 +55,14 @@ var router = urlrouter(function (app) {
res.end('GET home page' + req.url + ' , headers: ' + JSON.stringify(req.headers));
});
+ app.get('/user/:id', function (req, res) {
+ res.end('user: ' + req.params.id);
+ });
+
+ app.get(/^\/users?(?:\/(\d+)(?:\.\.(\d+))?)?/, function (req, res) {
+ res.end(req.url + ' : ' + req.params);
+ });
+
app.get('/foo', function (req, res) {
res.end('GET ' + req.url + ' , headers: ' + JSON.stringify(req.headers));
});
View
4 benchmark/simple.js
@@ -14,6 +14,10 @@ http.createServer(urlrouter(function (app) {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('Hello World\n');
});
+ app.get('/user/:id', function (request, response) {
+ response.writeHead(200, {'Content-Type': 'text/plain'});
+ response.end('Hello World\n');
+ });
})).listen(8125);
connect(function (request, response) {
View
3 example/connect-hello.js
@@ -5,4 +5,7 @@ connect(urlrouter(function (app) {
app.get('/', function (req, res, next) {
res.end('hello urlrouter');
});
+ app.get('/user/:id([0-9]+)', function (req, res, next) {
+ res.end('hello user ' + req.params.id);
+ });
})).listen(3000);
View
8 example/http-hello.js
@@ -6,6 +6,14 @@ var router = urlrouter(function (app) {
res.end('GET home page' + req.url + ' , headers: ' + JSON.stringify(req.headers));
});
+ app.get('/user/:id', function (req, res) {
+ res.end('user: ' + req.params.id);
+ });
+
+ app.get(/^\/users?(?:\/(\d+)(?:\.\.(\d+))?)?/, function (req, res) {
+ res.end(req.url + ' : ' + req.params);
+ });
+
app.get('/foo', function (req, res) {
res.end('GET ' + req.url + ' , headers: ' + JSON.stringify(req.headers));
});
View
19 lib/urlrouter.js
@@ -9,6 +9,7 @@
*/
var urlparse = require('url').parse;
+var utils = require('./utils');
var METHODS = ['get', 'post', 'put', 'delete', 'head', 'options'];
@@ -17,14 +18,16 @@ function notFound(req, res) {
res.end(req.method !== 'HEAD' && req.method + ' ' + req.url + ' Not Found ');
}
-function router(fn) {
+function router(fn, options) {
var routes = [];
var methods = {};
+ options = options || {};
+ options.paramsName = options.paramsName || 'params';
function createMethod(name) {
var localRoutes = routes[name.toUpperCase()] = [];
return function (urlroute, fn) {
- localRoutes.push([urlroute, fn]);
+ localRoutes.push([utils.createRouter(urlroute), fn]);
};
}
@@ -43,15 +46,9 @@ function router(fn) {
var route = localRoutes[i];
var urlroute = route[0];
var fn = route[1];
- if (urlroute instanceof RegExp) {
- var m = urlroute.exec(pathname);
- if (m) {
- req.params = m.slice(1);
- return fn(req, res, next);
- }
- continue;
- }
- if (pathname === urlroute) {
+ var match = urlroute.match(pathname);
+ if (match) {
+ req[options.paramsName] = match;
return fn(req, res, next);
}
}
View
69 lib/utils.js
@@ -0,0 +1,69 @@
+/*!
+ * urlrouter - lib/utils.js
+ * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+/**
+ * URL Router
+ * @param {String} url, routing url.
+ * e.g.: /user/:id, /user/:id([0-9]+), /user/:id.:format?
+ */
+function Router(url) {
+ this.keys = null;
+ if (url instanceof RegExp) {
+ this.rex = url;
+ this.source = this.rex.source;
+ return;
+ }
+
+ var keys = [];
+ this.source = url;
+ url = url.replace(/\//g, '\\/') // '/' => '\/'
+ .replace(/\./g, '\\.?') // '.' => '\.?'
+ .replace(/\*/g, '.+'); // '*' => '.+'
+
+ // ':id' => ([^\/]+),
+ // ':id?' => ([^\/]*),
+ // ':id([0-9]+)' => ([0-9]+)+,
+ // ':id([0-9]+)?' => ([0-9]+)*
+ url = url.replace(/:(\w+)(?:\(([^\)]+)\))?(\?)?/g, function (all, name, rex, atLeastOne) {
+ keys.push(name);
+ if (!rex) {
+ rex = '[^\\/]' + (atLeastOne === '?' ? '*' : '+');
+ }
+ return '(' + rex + ')';
+ });
+ // /user/:id => /user, /user/123
+ url = url.replace(/\\\/\(\[\^\\\/\]\*\)/g, '(?:\\/(\\w*))?');
+ this.keys = keys;
+ this.rex = new RegExp('^' + url + '\\/?$');
+}
+
+Router.prototype.match = function (pathname) {
+ var m = this.rex.exec(pathname);
+ // console.log(this.rex, pathname, this.keys, m, this.source)
+ var match = null;
+ if (m) {
+ if (!this.keys) {
+ return m.slice(1);
+ }
+ match = {};
+ var keys = this.keys;
+ for (var i = 0, l = keys.length; i < l; i++) {
+ var value = m[i + 1];
+ if (value) {
+ match[keys[i]] = value;
+ }
+ }
+ }
+ return match;
+};
+
+exports.createRouter = function (router) {
+ return new Router(router);
+};
View
11 test/urlrouter.js
@@ -21,6 +21,9 @@ var router = urlrouter(function (app) {
app.get(/^\/users?(?:\/(\d+)(?:\.\.(\d+))?)?/, function (req, res) {
res.end(JSON.stringify(req.params));
});
+ app.get('/topic/:id', function (req, res) {
+ res.end('topic ' + req.params.id);
+ });
app.get('/foo', function (req, res) {
res.end(req.method + ' ' + req.url);
});
@@ -125,6 +128,14 @@ var router = urlrouter(function (app) {
done();
});
});
+
+ it('should /topic/9999 200', function (done) {
+ app.request().get('/topic/9999').end(function (res) {
+ res.should.status(200);
+ res.body.toString().should.equal('topic 9999');
+ done();
+ });
+ });
});
describe('get()', function () {
View
122 test/utils.js
@@ -0,0 +1,122 @@
+/*!
+ * urlrouter - test/utils.js
+ * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var utils = process.env.URLROUTER_COV ? require('../lib-cov/utils') : require('../lib/utils');
+var should = require('should');
+
+describe('utils.js', function () {
+
+ describe('createRouter()', function () {
+
+ it('should match regexp', function () {
+ var router = utils.createRouter(/^\/users?(?:\/(\d+)(?:\.\.(\d+))?)?/);
+ var cases = [
+ ['/user', [undefined, undefined]],
+ ['/users', [undefined, undefined]],
+ ['/users/1..100', ['1', '100']],
+ ['/user/123', ['123', undefined]]
+ ];
+ cases.forEach(function (item) {
+ var url = item[0];
+ var m = router.match(url);
+ should.ok(m);
+ m.should.eql(item[1]);
+ });
+ });
+
+ it('should match all string', function () {
+
+ // http://expressjs.com/guide.html#routing
+ var cases = [
+ // [urlrouter, [matchs], [dont matchs]]
+ ["/user/:id",
+ [
+ ["/user/12", {id: '12'}],
+ ["/user/mk2", {id: 'mk2'}],
+ ["/user/mk2.123@$qwe,.xml-_hdhd", {id: 'mk2.123@$qwe,.xml-_hdhd'}],
+ ["/user/中文", {id: '中文'}],
+ ['/user/%E4%B8%AD%E5%8D%88', {id: '%E4%B8%AD%E5%8D%88'}]
+ ],
+ ["/user", "/", "/use/12", "/user12"]
+ ],
+ ["/users/:name?",
+ [
+ ["/users/5", {name: '5'}], ["/users", {}], ["/users/", {}]
+ ], ["/user", "/", "/user/12", "/users12"]],
+ ["/index/:i([0-9])",
+ [
+ ['/index/1', {i: '1'}, '/index/0', {i: '0'}]
+ ],
+ ['/index/', '/index', '/index/a', '/index/123', '/index/12', '/index/:i']
+ ],
+ ["/user/:name/status/:id([0-9]+)",
+ [
+ ["/user/123/status/456", {name: '123', id: '456'}],
+ ["/user/mk2/status/783972", {name: 'mk2', id: '783972'}],
+ ["/user/mk-2005_bac/status/783972", {name: 'mk-2005_bac', id: '783972'}]
+ ], ["/user/foo", "/user", "/user/", "/user/mk2/status/foo"]],
+ ["/files/*", ["/files/jquery.js", "/files/javascripts/jquery.js"], ['/files/', '/files']],
+ ["/files/*.*", ["/files/jquery.js", "/files/javascripts/jquery.js"], ['/files/', '/files']],
+ ["/user/:id/:operation?",
+ [["/user/1", {id: '1'}], ["/user/1/edit", {id: '1', operation: 'edit'}]]
+ ],
+ ["/products.:format",
+ [["/products.json", {format: 'json'}], ["/products.xml", {format: 'xml'}]],
+ [
+ "/products", "/products/"
+ // "/products."
+ ]
+ ],
+ ["/products.:format(json|xml)",
+ [["/products.json", {format: 'json'}], ["/products.xml", {format: 'xml'}]],
+ [
+ "/products", "/products/",
+ "/products.txt", "/products.html", "/products.js", "/products.xml2", "/products.rss"
+ ]
+ ],
+ ["/products.:format?",
+ ["/products.json", "/products.xml", "/products"]
+ ],
+ ["/user/:id.:format?", ["/user/12", "/user/12.json"]],
+ ["/users", ["/users", "/users/"]],
+ ["/users/", ["/users/"], ["/users", "/user"]]
+ ];
+
+ cases.forEach(function (item) {
+ var router = utils.createRouter(item[0]);
+ var matchs = item[1];
+ var unmatchs = item[2] || [];
+ matchs.forEach(function (match) {
+ var url;
+ var params = null;
+ if (Array.isArray(match)) {
+ url = match[0];
+ params = match[1];
+ } else {
+ url = match;
+ }
+ var m = router.match(url);
+ // console.log('match', m, url, router.rex, item[0]);
+ should.ok(m);
+ if (params) {
+ m.should.eql(params);
+ }
+ });
+ unmatchs.forEach(function (unmatch) {
+ var m = router.match(unmatch);
+ // console.log(unmatch, m, router.rex, item[0]);
+ should.not.exist(m);
+ });
+ });
+
+ });
+
+ });
+});

0 comments on commit 4c6fb24

Please sign in to comment.