index.js
6.43 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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
var events = require('events'),
qs = require('querystring'),
util = require('util'),
BaseRouter = require('../router').Router,
responses = require('./responses');
//
// ### Expose all HTTP methods and responses
//
exports.methods = require('./methods');
Object.keys(responses).forEach(function (name) {
exports[name] = responses[name];
});
//
// ### function Router (routes)
// #### @routes {Object} **Optional** Routing table for this instance.
// Constuctor function for the HTTP Router object responsible for building
// and dispatching from a given routing table.
//
var Router = exports.Router = function (routes) {
//
// ### Extend the `Router` prototype with all of the RFC methods.
//
this.params = {};
this.routes = {};
this.methods = ['on', 'after', 'before'];
this.scope = [];
this._methods = {};
this.recurse = 'forward';
this._attach = [];
this.extend(exports.methods.concat(['before', 'after']));
this.configure();
this.mount(routes || {});
};
//
// Inherit from `BaseRouter`.
//
util.inherits(Router, BaseRouter);
//
// ### function configure (options)
// #### @options {Object} **Optional** Options to configure this instance with
// Configures this instance with the specified `options`.
//
Router.prototype.configure = function (options) {
options = options || {};
// useful when using connect's bodyParser
this.stream = options.stream || false;
return BaseRouter.prototype.configure.call(this, options);
};
//
// ### function on (method, path, route)
// #### @method {string} **Optional** Method to use
// #### @path {string} Path to set this route on.
// #### @route {Array|function} Handler for the specified method and path.
// Adds a new `route` to this instance for the specified `method`
// and `path`.
//
Router.prototype.on = function (method, path) {
var args = Array.prototype.slice.call(arguments, 2),
route = args.pop(),
options = args.pop(),
accept;
if (options) {
if (options.stream) {
route.stream = true;
}
if (options.accept) {
this._hasAccepts = true;
accept = options.accept;
route.accept = (Array.isArray(accept) ? accept : [accept]).map(function (a) {
return typeof a === 'string' ? RegExp(a) : a;
});
}
}
if (typeof path !== 'string' && !path.source) {
path = '';
}
BaseRouter.prototype.on.call(this, method, path, route);
};
//
// ### function attach (func)
// ### @func {function} Function to execute on `this` before applying to router function
// Ask the router to attach objects or manipulate `this` object on which the
// function passed to the http router will get applied
Router.prototype.attach = function (func) {
this._attach.push(func);
};
//
// ### function dispatch (method, path)
// #### @req {http.ServerRequest} Incoming request to dispatch.
// #### @res {http.ServerResponse} Outgoing response to dispatch.
// #### @callback {function} **Optional** Continuation to respond to for async scenarios.
// Finds a set of functions on the traversal towards
// `method` and `path` in the core routing table then
// invokes them based on settings in this instance.
//
Router.prototype.dispatch = function (req, res, callback) {
//
// Dispatch `HEAD` requests to `GET`
//
var method = req.method === 'HEAD' ? 'get' : req.method.toLowerCase(),
thisArg = { req: req, res: res },
self = this,
contentType,
runlist,
stream,
error,
fns,
url;
//
// Trap bad URLs from `decodeUri`
//
try { url = decodeURI(req.url.split('?', 1)[0]); }
catch (ex) { url = null }
if (url && this._hasAccepts) {
contentType = req.headers['content-type'];
fns = this.traverse(method, url, this.routes, '', function (route) {
return !route.accept || route.accept.some(function (a) {
return a.test(contentType);
});
});
}
else if (url) {
fns = this.traverse(method, url, this.routes, '');
}
if (this._attach) {
for (var i in this._attach) {
this._attach[i].call(thisArg);
}
}
if (!fns || fns.length === 0) {
error = new exports.NotFound('Could not find path: ' + req.url);
if (typeof this.notfound === 'function') {
this.notfound.call(thisArg, callback);
}
else if (callback) {
callback.call(thisArg, error, req, res);
}
return false;
}
if (this.recurse === 'forward') {
fns = fns.reverse();
}
runlist = this.runlist(fns);
stream = this.stream || runlist.some(function (fn) { return fn.stream === true; });
function parseAndInvoke() {
error = self.parse(req);
if (error) {
if (callback) {
callback.call(thisArg, error, req, res);
}
return false;
}
self.invoke(runlist, thisArg, callback);
}
if (!stream) {
//
// If there is no streaming required on any of the functions on the
// way to `path`, then attempt to parse the fully buffered request stream
// once it has emitted the `end` event.
//
if (req.readable) {
//
// If the `http.ServerRequest` is still readable, then await
// the end event and then continue
//
req.once('end', parseAndInvoke);
// Streams2 requires us to start the stream if we're not explicitly
// reading from it.
req.resume();
}
else {
//
// Otherwise, just parse the body now.
//
parseAndInvoke();
}
}
else {
this.invoke(runlist, thisArg, callback);
}
return true;
};
//
// ### @parsers {Object}
// Lookup table of parsers to use when attempting to
// parse incoming responses.
//
Router.prototype.parsers = {
'application/x-www-form-urlencoded': qs.parse,
'application/json': JSON.parse
};
//
// ### function parse (req)
// #### @req {http.ServerResponse|BufferedStream} Incoming HTTP request to parse
// Attempts to parse `req.body` using the value found at `req.headers['content-type']`.
//
Router.prototype.parse = function (req) {
function mime(req) {
var str = req.headers['content-type'] || '';
return str.split(';')[0];
}
var parser = this.parsers[mime(req)],
body;
if (parser) {
req.body = req.body || '';
if (req.chunks) {
req.chunks.forEach(function (chunk) {
req.body += chunk;
});
}
if ('string' === typeof req.body) {
try {
req.body = req.body && req.body.length
? parser(req.body)
: {};
}
catch (err) {
return new exports.BadRequest('Malformed data');
}
}
}
};