spritz 0.5.17 → 0.5.20
Sign up to get free protection for your applications and to get access to all the features.
- package/package.json +1 -1
- package/spritz.js +613 -615
package/spritz.js
CHANGED
@@ -1,78 +1,76 @@
|
|
1
1
|
"use strict";
|
2
2
|
|
3
3
|
/*
|
4
|
-
Spritz Web
|
5
|
-
Version: 0.5.2
|
6
|
-
Author: David Oliveira <d.oliveira@prozone.org>
|
4
|
+
Spritz Web framework
|
7
5
|
*/
|
8
6
|
|
9
7
|
|
10
8
|
var
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
9
|
+
http = require('http'),
|
10
|
+
https = require('https'),
|
11
|
+
cluster = require('cluster'),
|
12
|
+
qs = require('querystring'),
|
13
|
+
formidable = require('formidable');
|
16
14
|
|
17
15
|
|
18
16
|
// Our data (from the default/first server)
|
19
|
-
exports.routes
|
20
|
-
exports.rxRoutes
|
21
|
-
exports.statusRoutes
|
22
|
-
exports.reqSeq
|
23
|
-
exports.hooks
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
17
|
+
exports.routes = {};
|
18
|
+
exports.rxRoutes = [];
|
19
|
+
exports.statusRoutes = {};
|
20
|
+
exports.reqSeq = 0;
|
21
|
+
exports.hooks = {
|
22
|
+
'setroute': [], // sync |
|
23
|
+
'arrive': [], // async | done
|
24
|
+
'readheaders': [], // async | done
|
25
|
+
'read': [], // async | done
|
26
|
+
'findroute': [], // async | done
|
27
|
+
|
28
|
+
'beforewritehead': [], // async | done
|
29
|
+
// writehead -> // async | done
|
30
|
+
'beforewritedata': [], // async | done
|
31
|
+
// writedata -> // async | done
|
32
|
+
'beforefinish': [], // async | done
|
33
|
+
'finish': [] // async | done
|
36
34
|
};
|
37
|
-
exports.globalHooks
|
35
|
+
exports.globalHooks = {};
|
38
36
|
|
39
37
|
|
40
38
|
// Create a new server instance
|
41
39
|
exports.newServer = function(){
|
42
40
|
|
43
|
-
|
44
|
-
|
45
|
-
|
41
|
+
var
|
42
|
+
self = this,
|
43
|
+
newServer
|
46
44
|
|
47
|
-
|
48
|
-
|
45
|
+
// Clone the current object
|
46
|
+
newServer = self.cloneServer();
|
49
47
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
48
|
+
// Reset some data
|
49
|
+
newServer.routes = {};
|
50
|
+
newServer.rxRoutes = [];
|
51
|
+
newServer.statusRoutes = {};
|
52
|
+
newServer.reqSeq = 0;
|
55
53
|
|
56
|
-
|
57
|
-
|
58
|
-
|
54
|
+
// Cleanup hooks (copy them from globalHooks or initialize them)
|
55
|
+
for ( var h in newServer.hooks )
|
56
|
+
newServer.hooks[h] = exports.globalHooks[h] ? exports.globalHooks[h].slice(0) : [];
|
59
57
|
|
60
|
-
|
61
|
-
|
58
|
+
// Delete newServer()
|
59
|
+
delete newServer.newServer;
|
62
60
|
|
63
|
-
|
64
|
-
|
61
|
+
// Return it
|
62
|
+
return newServer;
|
65
63
|
|
66
64
|
};
|
67
65
|
|
68
66
|
// Clone a currently existing server instance
|
69
67
|
exports.cloneServer = function(){
|
70
68
|
|
71
|
-
|
72
|
-
|
69
|
+
var
|
70
|
+
self = this;
|
73
71
|
|
74
|
-
|
75
|
-
|
72
|
+
// Clone the current object and return it
|
73
|
+
return _merge(self,{});
|
76
74
|
|
77
75
|
};
|
78
76
|
|
@@ -80,15 +78,15 @@ exports.cloneServer = function(){
|
|
80
78
|
// Use a certain (or a list of) module(s)
|
81
79
|
exports.use = function(modules,args) {
|
82
80
|
|
83
|
-
|
84
|
-
|
85
|
-
|
81
|
+
var
|
82
|
+
self = this,
|
83
|
+
_mods = (modules instanceof Array) ? modules : [modules];
|
86
84
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
85
|
+
// Initialize all the modules
|
86
|
+
_mods.forEach(function(mod){
|
87
|
+
if ( typeof mod.init == "function" )
|
88
|
+
mod.init.apply(self,[args||{}]);
|
89
|
+
});
|
92
90
|
|
93
91
|
};
|
94
92
|
|
@@ -96,33 +94,33 @@ exports.use = function(modules,args) {
|
|
96
94
|
// Register a hook
|
97
95
|
exports.hook = function(name,callback){
|
98
96
|
|
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
|
-
|
97
|
+
var
|
98
|
+
self = this;
|
99
|
+
|
100
|
+
if ( !name || !callback )
|
101
|
+
return;
|
102
|
+
if ( name.match(/^request(.+)$/i) )
|
103
|
+
name = RegExp.$1;
|
104
|
+
if ( name == "writehead" )
|
105
|
+
name = 'beforewritedata';
|
106
|
+
else if ( name == "writedata" )
|
107
|
+
name = 'beforefinish';
|
108
|
+
|
109
|
+
// Hook does not exit? Ciao!
|
110
|
+
if ( !self.hooks[name.toLowerCase()] )
|
111
|
+
return;
|
112
|
+
|
113
|
+
// Register the callback
|
114
|
+
self.hooks[name.toLowerCase()].push(callback);
|
115
|
+
|
116
|
+
// Uppercase hooks are 'global' (to being set on new servers)
|
117
|
+
if ( name.toUpperCase() == name ) {
|
118
|
+
name = name.toLowerCase();
|
119
|
+
if ( !self.globalHooks[name] )
|
120
|
+
self.globalHooks[name] = [];
|
121
|
+
// Register the callback
|
122
|
+
self.globalHooks[name].push(callback);
|
123
|
+
}
|
126
124
|
|
127
125
|
};
|
128
126
|
|
@@ -130,66 +128,66 @@ exports.hook = function(name,callback){
|
|
130
128
|
// Fires a hook
|
131
129
|
exports._fireHook = function(self,name,args,callback) {
|
132
130
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
131
|
+
var
|
132
|
+
sentBeforeHook = (args.length > 1 && args[1]._sent),
|
133
|
+
sentDuringHook,
|
134
|
+
_allHooks = (self.hooks[name] || []).slice(0),
|
135
|
+
req = (args.length > 0) ? args[0] : null;
|
138
136
|
|
139
|
-
|
140
|
-
|
141
|
-
|
137
|
+
// Does the request have a hook declaration ?
|
138
|
+
if ( req && req._route && typeof req._route['#'+name] == "function" )
|
139
|
+
_allHooks.push(args[0]._route['#'+name]);
|
142
140
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
141
|
+
// No hooks? Ciao!
|
142
|
+
if ( _allHooks.length == 0 ) {
|
143
|
+
// Process the request normally
|
144
|
+
return callback(null);
|
145
|
+
}
|
148
146
|
|
149
147
|
|
150
|
-
|
151
|
-
|
148
|
+
// Add the 'self' instance
|
149
|
+
args.unshift(self);
|
152
150
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
//
|
157
|
-
|
158
|
-
|
151
|
+
// Call the hooks
|
152
|
+
return series(_allHooks,args,function(err,done){
|
153
|
+
if ( err ) {
|
154
|
+
// _log_error("Error calling '"+name+"' hooks: ",err);
|
155
|
+
return self.json(args[0],args[1],{error:err},500);
|
156
|
+
}
|
159
157
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
158
|
+
// It's done, or the answer was sent during hook... finito!
|
159
|
+
sentDuringHook = !sentBeforeHook && (args.length > 1 && args[1]._sent);
|
160
|
+
if ( done || sentDuringHook )
|
161
|
+
return;
|
164
162
|
|
165
|
-
|
166
|
-
|
167
|
-
|
163
|
+
// Continue processing
|
164
|
+
return callback(err);
|
165
|
+
});
|
168
166
|
|
169
167
|
};
|
170
168
|
|
171
169
|
// Fires a syncronous hook
|
172
170
|
exports._fireSyncHook = function(self,name,args,callback) {
|
173
171
|
|
174
|
-
|
175
|
-
|
176
|
-
|
172
|
+
var
|
173
|
+
sentBeforeHook = (args.length > 1 && args[1]._sent),
|
174
|
+
sentDuringHook;
|
177
175
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
176
|
+
// No callbacks, ciao!
|
177
|
+
if ( !self.hooks[name] || self.hooks[name].length == 0 ) {
|
178
|
+
// Process the request normally
|
179
|
+
return callback(null);
|
180
|
+
}
|
183
181
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
182
|
+
// Call the hooks
|
183
|
+
for ( var x = 0 ; x < self.hooks[name].length ; x++ ) {
|
184
|
+
var hook = self.hooks[name][x];
|
185
|
+
var done = hook.apply(self,args);
|
186
|
+
if ( typeof done == "boolean" && done )
|
187
|
+
return true;
|
188
|
+
}
|
191
189
|
|
192
|
-
|
190
|
+
return false;
|
193
191
|
|
194
192
|
};
|
195
193
|
|
@@ -197,217 +195,217 @@ exports._fireSyncHook = function(self,name,args,callback) {
|
|
197
195
|
// Start the server
|
198
196
|
exports.start = function(opts, callback){
|
199
197
|
|
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
|
-
|
198
|
+
var
|
199
|
+
self = this,
|
200
|
+
args = Array.prototype.slice.call(arguments, 0),
|
201
|
+
numProcs,
|
202
|
+
workers = {};
|
203
|
+
|
204
|
+
// Get and validate arguments
|
205
|
+
if ( typeof opts == "function" ) {
|
206
|
+
callback = opts;
|
207
|
+
opts = null;
|
208
|
+
}
|
209
|
+
if ( !callback )
|
210
|
+
callback = function(){};
|
211
|
+
if ( !opts )
|
212
|
+
opts = { port: 8080, address: "0.0.0.0" };
|
213
|
+
self._opts = opts;
|
214
|
+
|
215
|
+
// Defaults
|
216
|
+
if ( !opts.mimes )
|
217
|
+
opts.mimes = { 'html': 'text/html', 'htm': 'text/html', 'js': 'text/javascript', 'css': 'text/css', 'gif': 'image/gif', 'jpg': 'image/jpeg', 'png': 'image/png' };
|
218
|
+
if ( !opts.processes )
|
219
|
+
opts.processes = 1;
|
220
|
+
|
221
|
+
_log_info("Starting...");
|
222
|
+
|
223
|
+
// Cluster support
|
224
|
+
numProcs = (opts.processes || 1);
|
225
|
+
if ( numProcs > 1 ) {
|
226
|
+
if ( cluster.isMaster ) {
|
227
|
+
process.title = "Spritz MASTER";
|
228
|
+
_log_info("Launching "+numProcs+" childs...");
|
229
|
+
for ( var x = 0 ; x < numProcs ; x++ ) {
|
230
|
+
var worker = cluster.fork();
|
231
|
+
_workerSetup(workers,worker);
|
232
|
+
}
|
233
|
+
|
234
|
+
_log_info("Launched "+numProcs+" childs");
|
235
|
+
cluster.on('exit',function(worker,code,signal){
|
236
|
+
delete workers[worker.process.pid];
|
237
|
+
self._log_error("Process #"+worker.process.pid+" died (signal "+signal+"). Launching other...");
|
238
|
+
var worker = cluster.fork();
|
239
|
+
_workerSetup(workers,worker);
|
240
|
+
});
|
241
|
+
}
|
242
|
+
else {
|
243
|
+
process.title = "Spritz child process";
|
244
|
+
return _startServer(self,opts,callback);
|
245
|
+
}
|
246
|
+
}
|
247
|
+
else {
|
248
|
+
return _startServer(self,opts,callback);
|
249
|
+
}
|
252
250
|
|
253
251
|
};
|
254
252
|
|
255
253
|
// Setup a worker
|
256
254
|
var _workerSetup = function(list,worker) {
|
257
255
|
|
258
|
-
|
259
|
-
|
256
|
+
// Add worker to the list
|
257
|
+
list[worker.process.pid] = worker;
|
260
258
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
259
|
+
// Listen for messages
|
260
|
+
worker.on('message', function(msg) {
|
261
|
+
if ( typeof(msg) == "object" && msg.fn == "console.log" ) {
|
262
|
+
msg.args.unshift("#"+worker.process.pid+":\t");
|
263
|
+
console.log.apply(console,msg.args);
|
264
|
+
}
|
265
|
+
});
|
268
266
|
|
269
267
|
}
|
270
268
|
|
271
269
|
// Start the HTTP server
|
272
270
|
var _startServer = function(self, opts, callback){
|
273
271
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
272
|
+
var
|
273
|
+
server,
|
274
|
+
iface,
|
275
|
+
_handleRequest = function(req,res){
|
276
|
+
handleRequest(self,req,res);
|
277
|
+
};
|
278
|
+
|
279
|
+
self.handleRequest = _handleRequest;
|
280
|
+
|
281
|
+
// Do we have a callback?
|
282
|
+
if ( !callback )
|
283
|
+
callback = function(){};
|
284
|
+
|
285
|
+
// Decide which server module to use
|
286
|
+
iface = (opts.proto == 'fastcgi') ? require('fastcgi-server') :
|
287
|
+
(opts.proto == 'https') ? https :
|
288
|
+
http;
|
289
|
+
|
290
|
+
// Listen
|
291
|
+
if ( opts.port == null )
|
292
|
+
opts.port = (opts.proto == "https") ? 443 : 8080;
|
293
|
+
if ( opts.port ) {
|
294
|
+
server = iface.createServer(_handleRequest).listen(opts.port || 8080,opts.address || "0.0.0.0",callback);
|
295
|
+
_log_info("Listening on "+(opts.address || "0.0.0.0")+":"+opts.port);
|
296
|
+
}
|
297
|
+
else if ( opts.address && opts.address.match(/\//) ) {
|
298
|
+
server = self.createServer(_handleRequest).listen(opts.address,callback);
|
299
|
+
_log_info("Listening on "+opts.address+" UNIX domain socket");
|
300
|
+
}
|
301
|
+
else {
|
302
|
+
_log_warn("Don't know how to listen");
|
303
|
+
}
|
304
|
+
return server;
|
307
305
|
|
308
306
|
};
|
309
307
|
|
310
308
|
// Handle a request
|
311
309
|
var handleRequest = function(self,req,res) {
|
312
310
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
311
|
+
var
|
312
|
+
now = new Date();
|
313
|
+
|
314
|
+
// Request just arrived, fire the hook
|
315
|
+
return self._fireHook(self,'arrive',[req,res,{}],function(){
|
316
|
+
|
317
|
+
// Request related values
|
318
|
+
req._cType = req.headers['content-type'] ? req.headers['content-type'].toString().replace(/;.*/g,"") : "unknown/unknown";
|
319
|
+
req.xRequestID = (self.reqSeq++) + "-" + process.pid.toString() + "-" + now.getYear()+now.getMonth()+now.getDay()+now.getHours()+now.getMinutes();
|
320
|
+
req.xConnectDate = now;
|
321
|
+
req.xRemoteAddr = req.connection.remoteAddress || ((req.client && req.client._peername) ? req.client._peername.address : "0.0.0.0");
|
322
|
+
if ( req.xRemoteAddr == "127.0.0.1" && req.headers['x-forwarded-for'] && req.headers['x-forwarded-for'].match(/^(\d{1,3}\.){3}\d{1,3}$/) ) {
|
323
|
+
req.xDirectRemoteAddr = req.xRemoteAddr;
|
324
|
+
req.xRemoteAddr = req.headers['x-forwarded-for'];
|
325
|
+
}
|
326
|
+
|
327
|
+
// Response related values
|
328
|
+
res._cookies = {};
|
329
|
+
res.setCookie = function(n,v,o) {
|
330
|
+
res._cookies[n] = { value: v, opts: o };
|
331
|
+
};
|
332
|
+
|
333
|
+
// Request arguments
|
334
|
+
req.args = {};
|
335
|
+
req.originalURL = req.url;
|
336
|
+
if ( req.url.match(/^(.*?)\?(.*)$/) ) {
|
337
|
+
req.url = RegExp.$1;
|
338
|
+
req.urlNoArgs = RegExp.$1;
|
339
|
+
req.args = qs.parse(RegExp.$2);
|
340
|
+
}
|
341
|
+
|
342
|
+
// POST data reader
|
343
|
+
req.readPOSTData = function(cb){cb(null,{});};
|
344
|
+
if ( req.method == "POST" || req.method == "PUT" ) {
|
345
|
+
req.readPOSTData = function(cb){
|
346
|
+
return readPOSTData(self,req,function(err){
|
347
|
+
return cb(err,self.POSTdata);
|
348
|
+
});
|
349
|
+
};
|
350
|
+
}
|
351
|
+
|
352
|
+
// The logging flags
|
353
|
+
req.xLoggingFlags = [];
|
354
|
+
|
355
|
+
// Finished read request
|
356
|
+
return self._fireHook(self,'readheaders',[req,res,{}],function(){
|
357
|
+
|
358
|
+
// Route request
|
359
|
+
return self._route(req,res);
|
360
|
+
|
361
|
+
});
|
362
|
+
});
|
365
363
|
|
366
364
|
};
|
367
365
|
|
368
366
|
// Read data from POST and parse it
|
369
367
|
var readPOSTData = function(self,req,callback) {
|
370
368
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
369
|
+
// POST data already read, don't do it again
|
370
|
+
if ( req._readPOSTData )
|
371
|
+
return callback(null,req);
|
372
|
+
req._readPOSTData = true;
|
373
|
+
|
374
|
+
// multipart/form-data or just a regular urlencoded form?
|
375
|
+
if ( req._cType.match(/^multipart\/form\-data/) ) {
|
376
|
+
try {
|
377
|
+
var
|
378
|
+
form = new formidable.IncomingForm();
|
379
|
+
|
380
|
+
form.parse(req,function(err,args,files){
|
381
|
+
if ( err )
|
382
|
+
return callback(err,false);
|
383
|
+
|
384
|
+
req.POSTargs = args;
|
385
|
+
req.POSTfiles = files;
|
386
|
+
return callback(null,req);
|
387
|
+
});
|
388
|
+
}
|
389
|
+
catch(ex) {
|
390
|
+
return callback(ex,null);
|
391
|
+
}
|
392
|
+
}
|
393
|
+
else {
|
394
|
+
req.setEncoding("utf-8");
|
395
|
+
var buf = "";
|
396
|
+
req.on('data',function(chunk){ buf += chunk; });
|
397
|
+
req.on('end',function(){
|
398
|
+
if ( req._cType == "application/json" ) {
|
399
|
+
try { req.POSTjson = JSON.parse(buf); } catch(ex){ _log_error("Error parsing POST JSON: ",ex.toString(),"JSON: ",buf); }
|
400
|
+
}
|
401
|
+
else {
|
402
|
+
req.POSTargs = qs.parse(buf);
|
403
|
+
if ( req.POSTargs['json'] )
|
404
|
+
try { req.POSTjson = JSON.parse(req.POSTargs['json']); } catch(ex){ _log_error("Error parsing POST JSON: ",ex.toString(),"JOSN: ",req.POSTargs['json']); }
|
405
|
+
}
|
406
|
+
return callback(null,req);
|
407
|
+
});
|
408
|
+
}
|
411
409
|
|
412
410
|
};
|
413
411
|
|
@@ -415,54 +413,54 @@ var readPOSTData = function(self,req,callback) {
|
|
415
413
|
// Generic route handler
|
416
414
|
exports.on = function(r,opts,reqHandler){
|
417
415
|
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
//
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
416
|
+
var
|
417
|
+
self = this,
|
418
|
+
args = Array.prototype.slice.call(arguments, 0);
|
419
|
+
|
420
|
+
// Get arguments
|
421
|
+
r = args.shift();
|
422
|
+
reqHandler = args.pop();
|
423
|
+
|
424
|
+
// Is it a hook ? Set it using the hook()
|
425
|
+
if ( typeof r == "string" && r.match(/^#(\w+)$/) )
|
426
|
+
return self.hook(RegExp.$1,reqHandler);
|
427
|
+
|
428
|
+
|
429
|
+
// Merge options with the defaults
|
430
|
+
opts = _merge({
|
431
|
+
// method: "GET",
|
432
|
+
handler: reqHandler,
|
433
|
+
expr: r
|
434
|
+
},args.shift()||{});
|
435
|
+
|
436
|
+
// Fire the setroute hook
|
437
|
+
if ( self._fireSyncHook(self,'setroute',[opts]) ) {
|
438
|
+
// Setting route was aborted
|
439
|
+
return;
|
440
|
+
}
|
441
|
+
|
442
|
+
var routes = (r instanceof Array) ? r : [r];
|
443
|
+
|
444
|
+
routes.forEach(function(r) {
|
445
|
+
// Is it a RegExp ?
|
446
|
+
if ( r instanceof RegExp ) {
|
447
|
+
// Register the route on the RegExp route list
|
448
|
+
self.rxRoutes.push([r,opts]);
|
449
|
+
}
|
450
|
+
else if ( typeof r == "string" ) {
|
451
|
+
// Register the route on the string route list
|
452
|
+
self.routes[(opts.method?opts.method.toUpperCase()+" ! ":"")+r] = opts;
|
453
|
+
}
|
454
|
+
else if ( typeof r == "number" ) {
|
455
|
+
r = r.toString();
|
456
|
+
if ( !self.statusRoutes[r] )
|
457
|
+
self.statusRoutes[r] = [];
|
458
|
+
// Register the route on the status route list
|
459
|
+
self.statusRoutes[r].push(opts);
|
460
|
+
}
|
461
|
+
else
|
462
|
+
throw new Error("Don't know what to do with route '"+r+"'");
|
463
|
+
});
|
466
464
|
|
467
465
|
};
|
468
466
|
|
@@ -470,116 +468,113 @@ exports.on = function(r,opts,reqHandler){
|
|
470
468
|
// Route a request
|
471
469
|
var _route = function(req,res) {
|
472
470
|
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
471
|
+
var
|
472
|
+
self = this,
|
473
|
+
routeOpts,
|
474
|
+
matchedRoute;
|
475
|
+
|
476
|
+
// String routes
|
477
|
+
if ( self.routes[req.method+" ! "+req.url] != null ) {
|
478
|
+
routeOpts = self.routes[req.method+" ! "+req.url];
|
479
|
+
}
|
480
|
+
else if ( self.routes[req.url] ) {
|
481
|
+
routeOpts = self.routes[req.url];
|
482
|
+
}
|
483
|
+
|
484
|
+
// RegExp routes
|
485
|
+
else {
|
486
|
+
for ( var x = 0 ; x < self.rxRoutes.length ; x++ ) {
|
487
|
+
if ( req.url.match(self.rxRoutes[x][0]) && (!self.rxRoutes[x][1].method || (self.rxRoutes[x][1].method.toUpperCase() == req.method)) ) {
|
488
|
+
matchedRoute = self.rxRoutes[x][0];
|
489
|
+
routeOpts = self.rxRoutes[x][1];
|
490
|
+
break;
|
491
|
+
}
|
492
|
+
}
|
493
|
+
}
|
494
|
+
|
495
|
+
// Still no handler? 404...
|
496
|
+
if ( !routeOpts ) {
|
497
|
+
res.statusCode = 404;
|
498
|
+
// Fire read hook
|
499
|
+
return self._fireHook(self,'read',[req,res,{}],function(){
|
500
|
+
return self._routeStatus(req,res,false);
|
501
|
+
});
|
502
|
+
}
|
503
|
+
|
504
|
+
// Read POST data ?
|
505
|
+
return _if ( !routeOpts.dontReadPOSTData,
|
506
|
+
function(next){
|
507
|
+
req.readPOSTData(next);
|
508
|
+
},
|
509
|
+
function(err){
|
510
|
+
if ( err )
|
511
|
+
_log_error("Error reading request POST data: ",err);
|
512
|
+
|
513
|
+
// Fire read hook
|
514
|
+
return self._fireHook(self,'read',[req,res,{}],function(){
|
515
|
+
|
516
|
+
// Fire find route hook
|
517
|
+
req._route = routeOpts;
|
518
|
+
return self._fireHook(self,'findroute',[req,res,{route: routeOpts}],function(){
|
519
|
+
|
520
|
+
// Set the RegExp object
|
521
|
+
if ( matchedRoute )
|
522
|
+
req.url.match(self.rxRoutes[x][0]);
|
523
|
+
|
524
|
+
// Call the route handler
|
525
|
+
return routeOpts.handler(req,res);
|
526
|
+
});
|
527
|
+
});
|
528
|
+
}
|
529
|
+
);
|
532
530
|
|
533
531
|
};
|
534
532
|
exports._route = _route;
|
535
533
|
|
536
534
|
|
537
535
|
// Route a status occurrence
|
538
|
-
var _routeStatus = function(req,res,
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
// Something to answer? Answer..!
|
581
|
-
if ( ans && !alreadyServed )
|
582
|
-
return self.json(req,res,ans,res.statusCode,headers);
|
536
|
+
var _routeStatus = function(req,res,headers) {
|
537
|
+
|
538
|
+
var
|
539
|
+
self = this,
|
540
|
+
ans,
|
541
|
+
routes;
|
542
|
+
|
543
|
+
// Inside a status route handler ?
|
544
|
+
if ( req.onStatusRouteH )
|
545
|
+
return;
|
546
|
+
|
547
|
+
// Do we have a status handler ?
|
548
|
+
routes = self.statusRoutes[res.statusCode.toString()];
|
549
|
+
if ( routes ) {
|
550
|
+
req.onStatusRouteH = true;
|
551
|
+
return mapSeries(routes,self,
|
552
|
+
function(r,next){
|
553
|
+
return self._fireHook(self,'findroute',[req,res,{route:r}],next);
|
554
|
+
},
|
555
|
+
function(){
|
556
|
+
var
|
557
|
+
handlers = routes.map(function(r){return r.handler});
|
558
|
+
|
559
|
+
// Call the handlers
|
560
|
+
return series(handlers,[self,req,res],function(err,done){});
|
561
|
+
}
|
562
|
+
);
|
563
|
+
}
|
564
|
+
|
565
|
+
// Already served ? Ciao!
|
566
|
+
if ( req.served )
|
567
|
+
return;
|
568
|
+
|
569
|
+
// No.. default status handler
|
570
|
+
ans = (res.statusCode == 404) ? { error: 'No route for this request type' } :
|
571
|
+
(res.statusCode == 401) ? { warn: 'Authentication required' } :
|
572
|
+
(res.statusCode >= 400) ? { error: 'Got error '+res.statusCode } :
|
573
|
+
{ info: 'Returning status '+res.statusCode };
|
574
|
+
|
575
|
+
// Something to answer? Answer..!
|
576
|
+
if ( ans && !req.served )
|
577
|
+
return self.json(req,res,ans,res.statusCode,headers);
|
583
578
|
|
584
579
|
};
|
585
580
|
exports._routeStatus = _routeStatus;
|
@@ -587,17 +582,20 @@ exports._routeStatus = _routeStatus;
|
|
587
582
|
|
588
583
|
// Write the head of an HTTP response
|
589
584
|
var _writeHead = function(self,req,res,status,headers,callback){
|
585
|
+
if (req.served)
|
586
|
+
return;
|
587
|
+
req.served = true;
|
590
588
|
|
591
|
-
|
592
|
-
|
589
|
+
var
|
590
|
+
headObj = {status: status, headers: headers};
|
593
591
|
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
592
|
+
return self._fireHook(self,'beforewritehead',[req,res,headObj],function(){
|
593
|
+
res.writeHead(headObj.status,headObj.headers);
|
594
|
+
// Mark on the answer that we sent it
|
595
|
+
res._sent = true;
|
596
|
+
res.statusCode = headObj.status;
|
597
|
+
return callback();
|
598
|
+
});
|
601
599
|
|
602
600
|
};
|
603
601
|
exports._writeHead = _writeHead;
|
@@ -605,27 +603,27 @@ exports._writeHead = _writeHead;
|
|
605
603
|
// Write the head of an HTTP response
|
606
604
|
var _writeData = function(self,req,res,data,end,callback){
|
607
605
|
|
608
|
-
|
609
|
-
|
606
|
+
var
|
607
|
+
dataObj = { data: data, end: end };
|
610
608
|
|
611
|
-
|
609
|
+
return self._fireHook(self,'beforewritedata',[req,res,dataObj],function(){
|
612
610
|
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
611
|
+
// Just writing...
|
612
|
+
if ( !dataObj.end ) {
|
613
|
+
res.write(dataObj.data);
|
614
|
+
return callback();
|
615
|
+
}
|
618
616
|
|
619
|
-
|
620
|
-
|
621
|
-
|
617
|
+
// Write and end
|
618
|
+
return self._fireHook(self,'beforefinish',[req,res,dataObj],function(){
|
619
|
+
res.end(dataObj.data);
|
622
620
|
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
621
|
+
// Finish
|
622
|
+
return self._fireHook(self,'finish',[req,res,{}],function(){
|
623
|
+
return callback();
|
624
|
+
});
|
625
|
+
});
|
626
|
+
});
|
629
627
|
|
630
628
|
}
|
631
629
|
exports._writeData = _writeData;
|
@@ -633,18 +631,18 @@ exports._writeData = _writeData;
|
|
633
631
|
// Pipe a stream into an HTTP response
|
634
632
|
var _pipeStream = function(self,req,res,stream,callback){
|
635
633
|
|
636
|
-
|
637
|
-
|
634
|
+
var
|
635
|
+
pr;
|
638
636
|
|
639
|
-
|
637
|
+
return self._fireHook(self,'beforewritedata',[req,res,stream],function(){
|
640
638
|
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
639
|
+
// Pipe the stream
|
640
|
+
pr = stream.pipe(res);
|
641
|
+
stream.on('end',function(){
|
642
|
+
callback(null,true);
|
643
|
+
});
|
646
644
|
|
647
|
-
|
645
|
+
});
|
648
646
|
|
649
647
|
};
|
650
648
|
exports._pipeStream = _pipeStream;
|
@@ -653,61 +651,61 @@ exports._pipeStream = _pipeStream;
|
|
653
651
|
// Answer with a text string
|
654
652
|
exports.text = function(req,res,content,status,headers,callback) {
|
655
653
|
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
654
|
+
var
|
655
|
+
self = this,
|
656
|
+
length = Buffer.byteLength(content,'utf8'),
|
657
|
+
_headers = _merge({
|
658
|
+
'content-type': 'text/plain; charset=utf-8',
|
659
|
+
'content-length': length,
|
660
|
+
'date': new Date().toUTCString()
|
661
|
+
},headers,true);
|
662
|
+
|
663
|
+
// Set the status code
|
664
|
+
res.statusCode = status || 200;
|
665
|
+
|
666
|
+
// Send data
|
667
|
+
return _writeHead(self,req,res,res.statusCode,_headers,function(){
|
668
|
+
return _writeData(self,req,res,content,true,function(){
|
669
|
+
// Log
|
670
|
+
self._access_log(req,res,length);
|
671
|
+
|
672
|
+
// Report status
|
673
|
+
self._routeStatus(req,res,true);
|
674
|
+
|
675
|
+
// Call the callback
|
676
|
+
if ( callback )
|
677
|
+
callback(null,true);
|
678
|
+
});
|
679
|
+
});
|
682
680
|
|
683
681
|
};
|
684
682
|
|
685
683
|
// Answer with JSON
|
686
684
|
exports.json = function(req,res,content,status,headers,pretty,callback) {
|
687
685
|
|
688
|
-
|
689
|
-
|
686
|
+
var
|
687
|
+
strfyArgs = [content];
|
690
688
|
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
689
|
+
if ( pretty && typeof pretty == "function" ) {
|
690
|
+
callback = pretty;
|
691
|
+
pretty = false;
|
692
|
+
}
|
693
|
+
if ( pretty )
|
694
|
+
strfyArgs.push(null,4);
|
697
695
|
|
698
|
-
|
699
|
-
|
696
|
+
// Build JSON content
|
697
|
+
content = JSON.stringify.apply(null,strfyArgs);
|
700
698
|
|
701
|
-
|
702
|
-
|
703
|
-
|
699
|
+
// JSONP ?
|
700
|
+
if ( req.args.callback )
|
701
|
+
content = req.args.callback.toString() + "(" + content.replace(/[\u00a0\u2000-\u203f]/g,"") + ");";
|
704
702
|
|
705
|
-
|
706
|
-
|
703
|
+
// Send the data
|
704
|
+
this.text(req,res,content,status,_merge({"content-type":"application/json; charset=utf-8"},headers,true));
|
707
705
|
|
708
|
-
|
709
|
-
|
710
|
-
|
706
|
+
// Call the callback
|
707
|
+
if ( callback )
|
708
|
+
callback(null,true);
|
711
709
|
|
712
710
|
};
|
713
711
|
|
@@ -715,7 +713,7 @@ exports.json = function(req,res,content,status,headers,pretty,callback) {
|
|
715
713
|
// Template
|
716
714
|
exports.template = function(req,res,file,data,status,headers,callback){
|
717
715
|
|
718
|
-
|
716
|
+
throw new Exception("No templating module was loaded. Use spritz.use()");
|
719
717
|
|
720
718
|
};
|
721
719
|
|
@@ -735,100 +733,100 @@ var _log_error = function() {
|
|
735
733
|
return _log("ERROR: ",arguments);
|
736
734
|
}
|
737
735
|
var _log = function(type,args) {
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
736
|
+
var
|
737
|
+
_args = [type],
|
738
|
+
_keys;
|
739
|
+
|
740
|
+
// Convert arguments into array - old style
|
741
|
+
_keys = args ? Object.keys(args) : [];
|
742
|
+
_keys.forEach(function(num){
|
743
|
+
_args.push(args[num]);
|
744
|
+
});
|
745
|
+
|
746
|
+
// Master, send directly to console
|
747
|
+
if ( cluster.isMaster ) {
|
748
|
+
_args.unshift("MASTER:\t");
|
749
|
+
console.log.apply(console,_args);
|
750
|
+
}
|
751
|
+
// Children send via cluster messaging system
|
752
|
+
else {
|
753
|
+
// Send to the master process, so we avoid problems with many processes writing on the same file
|
754
|
+
process.send({fn:'console.log',args: _args});
|
755
|
+
}
|
758
756
|
};
|
759
|
-
exports._log
|
760
|
-
exports._log_info
|
761
|
-
exports._log_warn
|
762
|
-
exports._log_error
|
757
|
+
exports._log = _log;
|
758
|
+
exports._log_info = _log_info;
|
759
|
+
exports._log_warn = _log_warn;
|
760
|
+
exports._log_error = _log_error;
|
763
761
|
|
764
762
|
// Access log
|
765
763
|
exports._access_log = function(req,res,length) {
|
766
|
-
|
767
|
-
|
768
|
-
|
764
|
+
var
|
765
|
+
timeSpent = new Date().getTime() - req.xConnectDate.getTime(),
|
766
|
+
flags = "";
|
769
767
|
|
770
|
-
|
771
|
-
|
768
|
+
if ( req.xLoggingFlags && req.xLoggingFlags.length > 0 )
|
769
|
+
flags = " "+req.xLoggingFlags.join('');
|
772
770
|
|
773
|
-
|
771
|
+
_log(req.xRemoteAddr+(req.xDirectRemoteAddr?"/"+req.xDirectRemoteAddr:"")+" - "+req.xRequestID+" ["+req.xConnectDate.toString()+"] \""+req.method+" "+(req.originalURL || req.url)+" HTTP/"+req.httpVersionMajor+"."+req.httpVersionMajor+"\" "+res.statusCode+" "+(length||"-")+" "+(timeSpent / 1000).toString()+flags);
|
774
772
|
};
|
775
773
|
|
776
774
|
// Merge 2 objects
|
777
775
|
var _merge = function(a,b,lcProps){
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
776
|
+
var o = {};
|
777
|
+
if ( a != null ) {
|
778
|
+
for ( var p in a )
|
779
|
+
o[lcProps?p.toLowerCase():p] = a[p];
|
780
|
+
}
|
781
|
+
if ( b != null ) {
|
782
|
+
for ( var p in b )
|
783
|
+
o[lcProps?p.toLowerCase():p] = b[p];
|
784
|
+
}
|
785
|
+
return o;
|
788
786
|
};
|
789
787
|
exports._merge = _merge;
|
790
788
|
|
791
789
|
// Asyncronous if
|
792
790
|
var _if = function(c,a,b) {
|
793
|
-
|
791
|
+
return c ? a(b) : b();
|
794
792
|
};
|
795
793
|
|
796
794
|
|
797
795
|
// Call a list of callbacks in series (could eventually be replaced by async.series or async.mapSeries but we don't want to add more dependencies)
|
798
796
|
var series = function(fns,args,callback){
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
797
|
+
var
|
798
|
+
_self = args.shift(),
|
799
|
+
_fns = fns.slice(),
|
800
|
+
_next = function(){
|
801
|
+
if ( !_fns.length )
|
802
|
+
return callback(null,false);
|
803
|
+
_fns.shift().apply(_self,args);
|
804
|
+
};
|
805
|
+
|
806
|
+
// Add as last argument our function return handler
|
807
|
+
args.push(function(err,stop,done){
|
808
|
+
if ( err )
|
809
|
+
return callback(err,false);
|
810
|
+
return (stop || done) ? callback(null,done) : setImmediate(_next);
|
811
|
+
});
|
812
|
+
|
813
|
+
return _next();
|
816
814
|
};
|
817
815
|
|
818
816
|
var mapSeries = function(arr,_self,itCb,fiCb){
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
817
|
+
var
|
818
|
+
_arr = arr.slice(),
|
819
|
+
_res = [],
|
820
|
+
_next = function(err,res){
|
821
|
+
if ( !_arr.length )
|
822
|
+
return fiCb(err,_res);
|
823
|
+
itCb.apply(_self,[_arr.shift(),function(err,res){
|
824
|
+
if ( err )
|
825
|
+
return fiCb(err,_res);
|
826
|
+
_res.push(res);
|
827
|
+
setImmediate(_next);
|
828
|
+
}]);
|
829
|
+
};
|
832
830
|
return _next();
|
833
831
|
};
|
834
832
|
|