]> git.cworth.org Git - obsolete/notmuch-web/blob - node_modules/express/node_modules/connect/lib/middleware/router.js
Install the "express" node module via npm
[obsolete/notmuch-web] / node_modules / express / node_modules / connect / lib / middleware / router.js
1
2 /*!
3  * Connect - router
4  * Copyright(c) 2010 Sencha Inc.
5  * Copyright(c) 2011 TJ Holowaychuk
6  * MIT Licensed
7  */
8
9 /**
10  * Module dependencies.
11  */
12
13 var utils = require('../utils')
14   , parse = require('url').parse;
15
16 /**
17  * Expose router.
18  */
19
20 exports = module.exports = router;
21
22 /**
23  * Supported HTTP / WebDAV methods.
24  */
25
26 var _methods = exports.methods = [
27     'get'
28   , 'post'
29   , 'put'
30   , 'delete'
31   , 'connect'
32   , 'options'
33   , 'trace'
34   , 'copy'
35   , 'lock'
36   , 'mkcol'
37   , 'move'
38   , 'propfind'
39   , 'proppatch'
40   , 'unlock'
41   , 'report'
42   , 'mkactivity'
43   , 'checkout'
44   , 'merge'
45 ];
46
47 /**
48  * Provides Sinatra and Express-like routing capabilities.
49  *
50  * Examples:
51  *
52  *     connect.router(function(app){
53  *       app.get('/user/:id', function(req, res, next){
54  *         // populates req.params.id
55  *       });
56  *       app.put('/user/:id', function(req, res, next){
57  *         // populates req.params.id
58  *       });
59  *     })
60  *
61  * @param {Function} fn
62  * @return {Function}
63  * @api public
64  */
65
66 function router(fn){
67   var self = this
68     , methods = {}
69     , routes = {}
70     , params = {};
71
72   if (!fn) throw new Error('router provider requires a callback function');
73
74   // Generate method functions
75   _methods.forEach(function(method){
76     methods[method] = generateMethodFunction(method.toUpperCase());
77   });
78
79   // Alias del -> delete
80   methods.del = methods.delete;
81
82   // Apply callback to all methods
83   methods.all = function(){
84     var args = arguments;
85     _methods.forEach(function(name){
86       methods[name].apply(this, args);
87     });
88     return self;
89   };
90
91   // Register param callback
92   methods.param = function(name, fn){
93     params[name] = fn;
94   };
95       
96   fn.call(this, methods);
97
98   function generateMethodFunction(name) {
99     var localRoutes = routes[name] = routes[name] || [];
100     return function(path, fn){
101       var keys = []
102         , middleware = [];
103
104       // slice middleware
105       if (arguments.length > 2) {
106         middleware = Array.prototype.slice.call(arguments, 1, arguments.length);
107         fn = middleware.pop();
108         middleware = utils.flatten(middleware);
109       }
110
111       fn.middleware = middleware;
112
113       if (!path) throw new Error(name + ' route requires a path');
114       if (!fn) throw new Error(name + ' route ' + path + ' requires a callback');
115       var regexp = path instanceof RegExp
116         ? path
117         : normalizePath(path, keys);
118       localRoutes.push({
119           fn: fn
120         , path: regexp
121         , keys: keys
122         , orig: path
123         , method: name
124       });
125       return self;
126     };
127   }
128
129   function router(req, res, next){
130     var route
131       , self = this;
132
133     (function pass(i){
134       if (route = match(req, routes, i)) {
135         var i = 0
136           , keys = route.keys;
137
138         req.params = route.params;
139
140         // Param preconditions
141         (function param(err) {
142           try {
143             var key = keys[i++]
144               , val = req.params[key]
145               , fn = params[key];
146
147             if ('route' == err) {
148               pass(req._route_index + 1);
149             // Error
150             } else if (err) {
151               next(err);
152             // Param has callback
153             } else if (fn) {
154               // Return style
155               if (1 == fn.length) {
156                 req.params[key] = fn(val);
157                 param();
158               // Middleware style
159               } else {
160                 fn(req, res, param, val);
161               }
162             // Finished processing params
163             } else if (!key) {
164               // route middleware
165               i = 0;
166               (function nextMiddleware(err){
167                 var fn = route.middleware[i++];
168                 if ('route' == err) {
169                   pass(req._route_index + 1);
170                 } else if (err) {
171                   next(err);
172                 } else if (fn) {
173                   fn(req, res, nextMiddleware);
174                 } else {
175                   route.call(self, req, res, function(err){
176                     if (err) {
177                       next(err);
178                     } else {
179                       pass(req._route_index + 1);
180                     }
181                   });
182                 }
183               })();
184             // More params
185             } else {
186               param();
187             }
188           } catch (err) {
189             next(err);
190           }
191         })();
192       } else if ('OPTIONS' == req.method) {
193         options(req, res, routes);
194       } else {
195         next();
196       }
197     })();
198   };
199
200   router.remove = function(path, method){
201     var fns = router.lookup(path, method);
202     fns.forEach(function(fn){
203       routes[fn.method].splice(fn.index, 1);
204     });
205   };
206
207   router.lookup = function(path, method, ret){
208     ret = ret || [];
209
210     // method specific lookup
211     if (method) {
212       method = method.toUpperCase();
213       if (routes[method]) {
214         routes[method].forEach(function(route, i){
215           if (path == route.orig) {
216             var fn = route.fn;
217             fn.regexp = route.path;
218             fn.keys = route.keys;
219             fn.path = route.orig;
220             fn.method = route.method;
221             fn.index = i;
222             ret.push(fn);
223           }
224         });
225       }
226     // global lookup
227     } else {
228       _methods.forEach(function(method){
229         router.lookup(path, method, ret);
230       });
231     }
232
233     return ret;
234   };
235
236   router.match = function(url, method, ret){
237     var ret = ret || []
238       , i = 0
239       , fn
240       , req;
241
242     // method specific matches
243     if (method) {
244       method = method.toUpperCase();
245       req = { url: url, method: method };
246       while (fn = match(req, routes, i)) {
247         i = req._route_index + 1;
248         ret.push(fn);
249       } 
250     // global matches
251     } else {
252       _methods.forEach(function(method){
253         router.match(url, method, ret);
254       });
255     }
256
257     return ret;
258   };
259
260   return router;
261 }
262
263 /**
264  * Respond to OPTIONS.
265  *
266  * @param {ServerRequest} req
267  * @param {ServerResponse} req
268  * @param {Array} routes
269  * @api private
270  */
271
272 function options(req, res, routes) {
273   var pathname = parse(req.url).pathname
274     , body = optionsFor(pathname, routes).join(',');
275   res.writeHead(200, {
276       'Content-Length': body.length
277     , 'Allow': body
278   });
279   res.end(body);
280 }
281
282 /**
283  * Return OPTIONS array for the given `path`, matching `routes`.
284  *
285  * @param {String} path
286  * @param {Array} routes
287  * @return {Array}
288  * @api private
289  */
290
291 function optionsFor(path, routes) {
292   return _methods.filter(function(method){
293     var arr = routes[method.toUpperCase()];
294     for (var i = 0, len = arr.length; i < len; ++i) {
295       if (arr[i].path.test(path)) return true;
296     }
297   }).map(function(method){
298     return method.toUpperCase();
299   });
300 }
301
302 /**
303  * Normalize the given path string,
304  * returning a regular expression.
305  *
306  * An empty array should be passed,
307  * which will contain the placeholder
308  * key names. For example "/user/:id" will
309  * then contain ["id"].
310  *
311  * @param  {String} path
312  * @param  {Array} keys
313  * @return {RegExp}
314  * @api private
315  */
316
317 function normalizePath(path, keys) {
318   path = path
319     .concat('/?')
320     .replace(/\/\(/g, '(?:/')
321     .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
322       keys.push(key);
323       slash = slash || '';
324       return ''
325         + (optional ? '' : slash)
326         + '(?:'
327         + (optional ? slash : '')
328         + (format || '') + (capture || '([^/]+?)') + ')'
329         + (optional || '');
330     })
331     .replace(/([\/.])/g, '\\$1')
332     .replace(/\*/g, '(.+)');
333   return new RegExp('^' + path + '$', 'i');
334 }
335
336 /**
337  * Attempt to match the given request to
338  * one of the routes. When successful
339  * a route function is returned.
340  *
341  * @param  {ServerRequest} req
342  * @param  {Object} routes
343  * @return {Function}
344  * @api private
345  */
346
347 function match(req, routes, i) {
348   var captures
349     , method = req.method
350     , i = i || 0;
351   if ('HEAD' == method) method = 'GET';
352   if (routes = routes[method]) {
353     var url = parse(req.url)
354       , pathname = url.pathname;
355     for (var len = routes.length; i < len; ++i) {
356       var route = routes[i]
357         , fn = route.fn
358         , path = route.path
359         , keys = fn.keys = route.keys;
360       if (captures = path.exec(pathname)) {
361         fn.method = method;
362         fn.params = [];
363         for (var j = 1, len = captures.length; j < len; ++j) {
364           var key = keys[j-1],
365             val = typeof captures[j] === 'string'
366               ? decodeURIComponent(captures[j])
367               : captures[j];
368           if (key) {
369             fn.params[key] = val;
370           } else {
371             fn.params.push(val);
372           }
373         }
374         req._route_index = i;
375         return fn;
376       }
377     }
378   }
379 }