spritz 0.5.17 → 0.5.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|