-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrouter.js
More file actions
146 lines (140 loc) · 4.53 KB
/
router.js
File metadata and controls
146 lines (140 loc) · 4.53 KB
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/**
* Router
*
* A simple router that redirects URLS to objects, and serves static files.
* The idea is to attach objects to paths, such as an object user to the path 'user'
* After that, the router will forward requests starting with user to methods for this object.
* Samples:
* GET /user -> user.index(args)
* GET /user/index -> user.index(args)
* GET /user/create -> user.create(args)
* GET /user/create/jack -> user.create('jack', args)
*
* You can also define a method called 'anyMethod', which will fetch all requests for non-existing methods.
* GET /user/jibberish -> user.anyMethod('jibberish', args)
*
* Args will contain an anonymous object constructed out of the querystring.
* The object will receive request and response as a property.
*
* The class also serves static files and these take priority over routes.
* You shouldn't serve large files from this class, as the file serving operates in read-whole-file-mode.
* Routes should be added in order of priority, i.e. '/' should probably be last, as it matches everything.
*
* TODO: chunked file serving
* TODO: blockable paths to prevent source code serving
*/
module.exports = function(debug) {
this.routes = [];
this.debug = debug;
return this;
};
module.exports.prototype =
{
/**
* Adds a new route by attaching an object to a path.
*/
addRoute : function(path, object) {
require('util').log(require('util').inspect(this));
this.routes.push({ 'path' : path, 'object' : object });
},
/**
* Internal function for static file serving
*/
realPath : function(err, resolvedPath) {
if (err) {
if (this.debug) {
require('util').log('Path not resolved');
}
return this.findRoute();
}
if (resolvedPath.length < this.filePath.length || resolvedPath.substring(0, this.filePath.length) != this.filePath) {
if (this.debug) {
require('util').log('Path not in current directory: ' + resolvedPath + ' ' + this.filePath);
}
return this.findRoute();
}
require('fs').readFile(this.filePath, this.fileRead.bind(this));
},
/**
* Internal function for static file serving
*/
fileRead : function(err, data) {
if (err) {
if (this.debug) {
require('util').log('Error reading file');
}
return this.findRoute();
}
else {
if (this.debug) {
require('util').log('Sending file');
}
}
this.response.writeHead(200, { 'Content-Type' : require('mime').lookup(this.path) });
this.response.end(data);
},
/**
* The main request handler. You can pass this as a callback to http.createServer
*/
route : function(request, response) {
this.request = request;
this.response = response;
this.handled = false;
if (this.debug) {
require('util').log('Request received for ' + request.url);
}
if (this.request.url.indexOf('?') == -1) {
this.path = this.request.url;
this.variables = {};
}
else {
this.path = request.url.substring(0, request.url.indexOf('?') - 1);
this.variables = querystring.parse(request.url.substring(request.url.indexOf('?') + 1));
}
this.filePath = process.cwd() + this.path;
require('fs').realpath(this.filePath, this.realPath.bind(this));
},
/**
* Internal function that tries to match the request path in the internal routing table.
*/
findRoute : function() {
this.routes.forEach(function(route) {
if (this.path.substring(0, route.path.length) == route.path) {
var func = this.path.substring(route.path.length);
var fullFunc;
var subpath = "";
if (func[0] == '/') {
func = func.substring(1);
}
fullFunc = func;
if (func.indexOf('/') != -1) {
subpath = func.substring(func.indexOf('/') + 1);
func = func.substring(0, func.indexOf('/') - 1);
}
if (!func.length && typeof(route.object.index) == 'function') {
route.object.request = this.request;
route.object.response = this.response;
route.object.index.call(route.object, this.variables);
this.handled = true;
}
else if (typeof(route.object[func]) == 'function') {
route.object.request = this.request;
route.object.response = this.response;
route.object[func].apply(route.object, subpath.split('/').push(this.variables));
this.handled = true;
}
else if (typeof(route.object['anyMethod']) == 'function') {
route.object.request = this.request;
route.object.response = this.response;
route.object.anyMethod.apply(route.object, fullFunc.split('/').push(this.variables));
this.handled = true;
}
}
}, this);
if (!this.handled) {
if (this.debug) {
require('util').log('No route found for ' + this.path);
}
}
}
}