4 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
12 var utils = require('../utils')
13 , parse = require('url').parse
14 , _methods = require('./methods')
15 , Route = require('./route');
21 exports = module.exports = router;
27 exports.methods = _methods;
30 * Provides Sinatra-like routing capabilities.
32 * @param {Function} fn
37 function router(fn, app){
43 if (!fn) throw new Error('router provider requires a callback function');
45 // Generate method functions
46 _methods.forEach(function(method){
47 methods[method] = generateMethodFunction(method.toUpperCase());
50 // Alias del -> delete
51 methods.del = methods.delete;
53 // Apply callback to all methods
54 methods.all = function(){
56 _methods.forEach(function(name){
57 methods[name].apply(this, args);
62 // Register param callback
63 methods.param = function(name, fn){
67 fn.call(this, methods);
69 function generateMethodFunction(name) {
70 var localRoutes = routes[name] = routes[name] || [];
71 return function(path, fn){
76 if (arguments.length > 2) {
77 middleware = Array.prototype.slice.call(arguments, 1, arguments.length);
78 fn = middleware.pop();
79 middleware = utils.flatten(middleware);
82 if (!path) throw new Error(name + ' route requires a path');
83 if (!fn) throw new Error(name + ' route ' + path + ' requires a callback');
85 var options = { sensitive: app.enabled('case sensitive routes') };
86 var route = new Route(name, path, fn, options);
87 route.middleware = middleware;
88 localRoutes.push(route);
93 function router(req, res, next){
98 if (route = match(req, routes, i)) {
102 req.params = route.params;
104 // Param preconditions
105 (function param(err) {
108 , val = req.params[key]
111 if ('route' == err) {
112 pass(req._route_index + 1);
116 // Param has callback
119 if (1 == fn.length) {
120 req.params[key] = fn(val);
124 fn(req, res, param, val);
126 // Finished processing params
130 (function nextMiddleware(err){
131 var fn = route.middleware[i++];
132 if ('route' == err) {
133 pass(req._route_index + 1);
137 fn(req, res, nextMiddleware);
139 route.callback.call(self, req, res, function(err){
143 pass(req._route_index + 1);
156 } else if ('OPTIONS' == req.method) {
157 options(req, res, routes);
164 router.remove = function(path, method, ret){
168 // method specific remove
170 method = method.toUpperCase();
171 if (routes[method]) {
172 for (var i = 0; i < routes[method].length; ++i) {
173 route = routes[method][i];
174 if (path == route.path) {
176 routes[method].splice(i, 1);
184 _methods.forEach(function(method){
185 router.remove(path, method, ret);
192 router.lookup = function(path, method, ret){
195 // method specific lookup
197 method = method.toUpperCase();
198 if (routes[method]) {
199 routes[method].forEach(function(route, i){
200 if (path == route.path) {
208 _methods.forEach(function(method){
209 router.lookup(path, method, ret);
216 router.match = function(url, method, ret){
222 // method specific matches
224 method = method.toUpperCase();
225 req = { url: url, method: method };
226 while (route = match(req, routes, i)) {
227 i = req._route_index + 1;
233 _methods.forEach(function(method){
234 router.match(url, method, ret);
245 * Respond to OPTIONS.
247 * @param {ServerRequest} req
248 * @param {ServerResponse} req
249 * @param {Array} routes
253 function options(req, res, routes) {
254 var pathname = parse(req.url).pathname
255 , body = optionsFor(pathname, routes).join(',');
256 res.send(body, { Allow: body });
260 * Return OPTIONS array for the given `path`, matching `routes`.
262 * @param {String} path
263 * @param {Array} routes
268 function optionsFor(path, routes) {
269 return _methods.filter(function(method){
270 var arr = routes[method.toUpperCase()];
271 for (var i = 0, len = arr.length; i < len; ++i) {
272 if (arr[i].regexp.test(path)) return true;
274 }).map(function(method){
275 return method.toUpperCase();
280 * Attempt to match the given request to
281 * one of the routes. When successful
282 * a route function is returned.
284 * @param {ServerRequest} req
285 * @param {Object} routes
290 function match(req, routes, i) {
292 , method = req.method
295 // pass HEAD to GET routes
296 if ('HEAD' == method) method = 'GET';
298 // routes for this method
299 if (routes = routes[method]) {
300 var url = parse(req.url)
301 , pathname = url.pathname;
304 for (var len = routes.length; i < len; ++i) {
305 var route = routes[i]
306 , fn = route.callback
307 , path = route.regexp
311 if (captures = path.exec(pathname)) {
313 for (var j = 1, l = captures.length; j < l; ++j) {
315 val = 'string' == typeof captures[j]
316 ? decodeURIComponent(captures[j])
319 route.params[key] = val;
321 route.params.push(val);
324 req._route_index = i;