-
Notifications
You must be signed in to change notification settings - Fork 8
/
choreographer.js
97 lines (85 loc) · 3.21 KB
/
choreographer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/**
* The Choreographer
* Your server is my stage -- dirt simple URL routing for Node.js.
*
* by Han
*
* http://github.com/laughinghan/choreographer
*
*/
var parse = require('url').parse;
//creates router
exports.router = function() {
//router, to be passed to `require('http').createServer()`
var router = function(req, res) {
if (!(req.parsedUrl && 'pathname' in req.parsedUrl))
req.parsedUrl = parse(req.url);
if (req.method in routes) {
var path = req.parsedUrl.pathname, routesForMethod = routes[req.method],
len = routesForMethod.length;
for (var i = 0; i < len; i += 1) {
//say '/foo/bar/baz' matches '/foo/*/*'
var route = routesForMethod[i], matches = route.exec(path);
if (matches) { //then matches would be ['/foo/bar/baz','bar','baz']
//so turn arguments from [req,res] into [req,res,'bar','baz']
__Array_push.apply(arguments, matches.slice(1));
return route.callback.apply(this, arguments);
}
}
}
//route not found: no route has matched and hence returned yet
notFoundHandler.apply(this, arguments);
};
//dictionary of arrays of routes
var routes = {};
//routing API
['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'TRACE', 'OPTIONS', 'CONNECT']
.forEach(function(method) {
routes[method] = [];
//e.g. router.get('/foo/*',function(req,res,bar){});
router[method.toLowerCase()] = function(route, ignoreCase, callback) {
if (arguments.length === 2) {
callback = ignoreCase;
ignoreCase = router.ignoreCase;
}
if (route.constructor.name === 'RegExp') //instanceof fails between modules
route = new RegExp(route); //if route is already a RegExp, just clone it
else //else stringify and interpret as regex where * matches URI segments
route = new RegExp('^' //and everything else matches literally
+ String(route)
.replace(specialChars, '\\$&')
.replace(/\*\*/g, '(.*)')
.replace(/\*(?!\))/g, '([^/]*)') //negative lookahead so as not to
//replace the '*' in '(.*)'
+ '$',
ignoreCase ? 'i' : ''
);
route.callback = callback;
routes[method].push(route);
return this;
};
});
//special characters that need to be escaped when passed to `RegExp()`, lest
//they be interpreted as pattern-matching:
var specialChars = /[|.+?{}()\[\]^$]/g;
//creating `get` routes automatically creates `head` routes:
routes.GET.push = function(route) { //as called by `router.get()`
__Array_push.call(this, route);
routes.HEAD.push(route);
};
//404 is a route too
router.notFound = function(handler) {
notFoundHandler = handler;
return this;
};
//handles requests where no matching route is found
var notFoundHandler = defaultNotFound;
return router;
};
var __Array_push = [].push; //Array.prototype.push, used by `router()`
function defaultNotFound(req, res) {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('<html><head><title>Error 404: Not Found</title></head><body>\n' +
'<h1>Error 404: Not Found</h1>\n' +
'<p>Cannot ' + req.method + ' ' + req.url + '</body></html>\n');
}