Coverage

100%
104
104
0

urlrouter.js

100%
63
63
0
LineHitsSource
1/*!
2 * urlrouter.js
3 * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>
4 * MIT Licensed
5 */
6
71"use strict";
8
9/**
10 * Module dependencies.
11 */
12
131var urlparse = require('url').parse;
141var utils = require('./utils');
15
161var METHODS = ['get', 'post', 'put', 'delete', 'head', 'options'];
17
18/**
19 * Default page not found handler.
20 *
21 * @param {HttpRequest} req
22 * @param {HttpResponse} res
23 */
241function pageNotFound(req, res) {
2512 res.statusCode = 404;
2612 res.end(req.method !== 'HEAD' && req.method + ' ' + req.url + ' Not Found ');
27}
28
29/**
30 * Default error handler.
31 *
32 * @param {Error} err
33 * @param {HttpRequest} req
34 * @param {HttpResponse} res
35 */
361function errorHandler(err, req, res) {
371 res.statusCode = 500;
381 res.end(err.stack);
39}
40
41/**
42 * Create a url router.
43 *
44 * @param {Function(app)} fn
45 * @param {Object} [options]
46 * - {String} paramsName, req[paramsName] for url router match `params`.
47 * - {Function(req, res)} pageNotFound, page not found handler.
48 * @return {Function(req, res[, next])}
49 * @public
50 */
511function router(fn, options) {
522 var routes = [];
532 var methods = {};
542 options = options || {};
552 options.paramsName = options.paramsName || 'params';
562 options.pageNotFound = options.pageNotFound || pageNotFound;
572 options.errorHandler = options.errorHandler || errorHandler;
58
592 function createMethod(name) {
6014 var localRoutes = routes[name.toUpperCase()] = [];
61 // fn(url[, middleware[s]], handle)
6214 return function (urlpattern, handle) {
6317 var middleware = null;
64
65 // slice middleware
6617 if (arguments.length > 2) {
675 middleware = Array.prototype.slice.call(arguments, 1, arguments.length);
685 handle = middleware.pop();
695 middleware = utils.flatten(middleware);
70 }
71
7217 localRoutes.push([utils.createRouter(urlpattern), handle, middleware]);
73 };
74 }
75
762 METHODS.forEach(function (method) {
7712 methods[method] = createMethod(method);
78 });
792 methods.all = createMethod('all');
80
812 fn(methods);
82
832 return function lookup(req, res, next) {
8461 var method = req.method.toUpperCase();
8561 var localRoutes = routes[method] || [];
8661 var allRoutes = routes.ALL;
8761 if (allRoutes) {
8861 localRoutes = localRoutes.concat(allRoutes);
89 }
90
9161 if (localRoutes.length > 0) {
9261 var pathname = urlparse(req.url).pathname;
9361 for (var i = 0, l = localRoutes.length; i < l; i++) {
94235 var route = localRoutes[i];
95235 var urlroute = route[0];
96235 var handle = route[1];
97235 var middleware = route[2];
98235 var match = urlroute.match(pathname);
99235 if (!match) {
100187 continue;
101 }
102
10348 req[options.paramsName] = match;
104 // if middleware not exists or empty, return directly
10548 if (!middleware || !middleware.length) {
10638 return handle(req, res, next);
107 }
108 // route middleware
10910 var k = 0;
11010 var routeMiddleware = function (err) {
11120 var mw = middleware[k++];
11220 if (err) {
1134 var errHandler = next || options.errorHandler;
1144 return errHandler(err, req, res);
11516 } else if (mw) {
11612 return mw(req, res, routeMiddleware);
117 } else {
1184 return handle(req, res, next);
119 }
120 };
12110 return routeMiddleware();
122 }
123 }
124 // not found
12513 next ? next() : options.pageNotFound(req, res);
126 };
127}
128
1291router.METHODS = METHODS;
1301router.pageNotFound = pageNotFound;
1311module.exports = router;

utils.js

100%
41
41
0
LineHitsSource
1/*!
2 * urlrouter - lib/utils.js
3 * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>
4 * MIT Licensed
5 */
6
71"use strict";
8
9/**
10 * Module dependencies.
11 */
12
13/**
14 * URL Router
15 * @param {String} url, routing url.
16 * e.g.: /user/:id, /user/:id([0-9]+), /user/:id.:format?
17 */
181function Router(url) {
1931 this.keys = null;
2031 if (url instanceof RegExp) {
212 this.rex = url;
222 this.source = this.rex.source;
232 return;
24 }
25
2629 var keys = [];
2729 this.source = url;
2829 url = url.replace(/\//g, '\\/') // '/' => '\/'
29 .replace(/\./g, '\\.?') // '.' => '\.?'
30 .replace(/\*/g, '.+'); // '*' => '.+'
31
32 // ':id' => ([^\/]+),
33 // ':id?' => ([^\/]*),
34 // ':id([0-9]+)' => ([0-9]+)+,
35 // ':id([0-9]+)?' => ([0-9]+)*
3629 url = url.replace(/:(\w+)(?:\(([^\)]+)\))?(\?)?/g, function (all, name, rex, atLeastOne) {
3714 keys.push(name);
3814 if (!rex) {
3911 rex = '[^\\/]' + (atLeastOne === '?' ? '*' : '+');
40 }
4114 return '(' + rex + ')';
42 });
43 // /user/:id => /user, /user/123
4429 url = url.replace(/\\\/\(\[\^\\\/\]\*\)/g, '(?:\\/(\\w*))?');
4529 this.keys = keys;
4629 this.rex = new RegExp('^' + url + '\\/?$');
47}
48
49/**
50 * Try to match given pathname, if match, return the match `params`.
51 *
52 * @param {String} pathname
53 * @return {Object|null} match `params` or null.
54 */
551Router.prototype.match = function (pathname) {
56302 var m = this.rex.exec(pathname);
57 // console.log(this.rex, pathname, this.keys, m, this.source)
58302 var match = null;
59302 if (m) {
6082 if (!this.keys) {
6116 return m.slice(1);
62 }
6366 match = {};
6466 var keys = this.keys;
6566 for (var i = 0, l = keys.length; i < l; i++) {
6636 var value = m[i + 1];
6736 if (value) {
6828 match[keys[i]] = value;
69 }
70 }
71 }
72286 return match;
73};
74
75/**
76 * Create a `Router` instance.
77 *
78 * @param {String|RegExp} urlpattern
79 * @return {Router}
80 */
811exports.createRouter = function (urlpattern) {
8231 return new Router(urlpattern);
83};
84
85/**
86 * Flatten the given `arr`.
87 *
88 * @param {Array} arr
89 * @return {Array}
90 * @api private
91 */
92
931exports.flatten = function (arr, ret) {
947 ret = ret || [];
957 var len = arr.length;
967 for (var i = 0; i < len; ++i) {
978 if (Array.isArray(arr[i])) {
982 exports.flatten(arr[i], ret);
99 } else {
1006 ret.push(arr[i]);
101 }
102 }
1037 return ret;
104};