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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/spritz.js +613 -615
package/spritz.js CHANGED
@@ -1,78 +1,76 @@
1
1
  "use strict";
2
2
 
3
3
  /*
4
- Spritz Web server framework
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
- http = require('http'),
12
- https = require('https'),
13
- cluster = require('cluster'),
14
- qs = require('querystring'),
15
- formidable = require('formidable');
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 = 0;
23
- exports.hooks = {
24
- 'setroute': [], // sync |
25
- 'arrive': [], // async | done
26
- 'readheaders': [], // async | done
27
- 'read': [], // async | done
28
- 'findroute': [], // async | done
29
-
30
- 'beforewritehead': [], // async | done
31
- // writehead -> // async | done
32
- 'beforewritedata': [], // async | done
33
- // writedata -> // async | done
34
- 'beforefinish': [], // async | done
35
- 'finish': [] // async | done
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
- var
44
- self = this,
45
- newServer
41
+ var
42
+ self = this,
43
+ newServer
46
44
 
47
- // Clone the current object
48
- newServer = self.cloneServer();
45
+ // Clone the current object
46
+ newServer = self.cloneServer();
49
47
 
50
- // Reset some data
51
- newServer.routes = {};
52
- newServer.rxRoutes = [];
53
- newServer.statusRoutes = {};
54
- newServer.reqSeq = 0;
48
+ // Reset some data
49
+ newServer.routes = {};
50
+ newServer.rxRoutes = [];
51
+ newServer.statusRoutes = {};
52
+ newServer.reqSeq = 0;
55
53
 
56
- // Cleanup hooks (copy them from globalHooks or initialize them)
57
- for ( var h in newServer.hooks )
58
- newServer.hooks[h] = exports.globalHooks[h] ? exports.globalHooks[h].slice(0) : [];
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
- // Delete newServer()
61
- delete newServer.newServer;
58
+ // Delete newServer()
59
+ delete newServer.newServer;
62
60
 
63
- // Return it
64
- return newServer;
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
- var
72
- self = this;
69
+ var
70
+ self = this;
73
71
 
74
- // Clone the current object and return it
75
- return _merge(self,{});
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
- var
84
- self = this,
85
- _mods = (modules instanceof Array) ? modules : [modules];
81
+ var
82
+ self = this,
83
+ _mods = (modules instanceof Array) ? modules : [modules];
86
84
 
87
- // Initialize all the modules
88
- _mods.forEach(function(mod){
89
- if ( typeof mod.init == "function" )
90
- mod.init.apply(self,[args||{}]);
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
- var
100
- self = this;
101
-
102
- if ( !name || !callback )
103
- return;
104
- if ( name.match(/^request(.+)$/i) )
105
- name = RegExp.$1;
106
- if ( name == "writehead" )
107
- name = 'beforewritedata';
108
- else if ( name == "writedata" )
109
- name = 'beforefinish';
110
-
111
- // Hook does not exit? Ciao!
112
- if ( !self.hooks[name.toLowerCase()] )
113
- return;
114
-
115
- // Register the callback
116
- self.hooks[name.toLowerCase()].push(callback);
117
-
118
- // Uppercase hooks are 'global' (to being set on new servers)
119
- if ( name.toUpperCase() == name ) {
120
- name = name.toLowerCase();
121
- if ( !self.globalHooks[name] )
122
- self.globalHooks[name] = [];
123
- // Register the callback
124
- self.globalHooks[name].push(callback);
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
- var
134
- sentBeforeHook = (args.length > 1 && args[1]._sent),
135
- sentDuringHook,
136
- _allHooks = (self.hooks[name] || []).slice(0),
137
- req = (args.length > 0) ? args[0] : null;
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
- // Does the request have a hook declaration ?
140
- if ( req && req._route && typeof req._route['#'+name] == "function" )
141
- _allHooks.push(args[0]._route['#'+name]);
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
- // No hooks? Ciao!
144
- if ( _allHooks.length == 0 ) {
145
- // Process the request normally
146
- return callback(null);
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
- // Add the 'self' instance
151
- args.unshift(self);
148
+ // Add the 'self' instance
149
+ args.unshift(self);
152
150
 
153
- // Call the hooks
154
- return series(_allHooks,args,function(err,done){
155
- if ( err ) {
156
- // _log_error("Error calling '"+name+"' hooks: ",err);
157
- return self.json(args[0],args[1],{error:err},500);
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
- // It's done, or the answer was sent during hook... finito!
161
- sentDuringHook = !sentBeforeHook && (args.length > 1 && args[1]._sent);
162
- if ( done || sentDuringHook )
163
- return;
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
- // Continue processing
166
- return callback(err);
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
- var
175
- sentBeforeHook = (args.length > 1 && args[1]._sent),
176
- sentDuringHook;
172
+ var
173
+ sentBeforeHook = (args.length > 1 && args[1]._sent),
174
+ sentDuringHook;
177
175
 
178
- // No callbacks, ciao!
179
- if ( !self.hooks[name] || self.hooks[name].length == 0 ) {
180
- // Process the request normally
181
- return callback(null);
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
- // Call the hooks
185
- for ( var x = 0 ; x < self.hooks[name].length ; x++ ) {
186
- var hook = self.hooks[name][x];
187
- var done = hook.apply(self,args);
188
- if ( typeof done == "boolean" && done )
189
- return true;
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
- return false;
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
- var
201
- self = this,
202
- args = Array.prototype.slice.call(arguments, 0),
203
- numProcs,
204
- workers = {};
205
-
206
- // Get and validate arguments
207
- if ( typeof opts == "function" ) {
208
- callback = opts;
209
- opts = null;
210
- }
211
- if ( !callback )
212
- callback = function(){};
213
- if ( !opts )
214
- opts = { port: 8080, address: "0.0.0.0" };
215
- self._opts = opts;
216
-
217
- // Defaults
218
- if ( !opts.mimes )
219
- opts.mimes = { 'html': 'text/html', 'htm': 'text/html', 'js': 'text/javascript', 'css': 'text/css', 'gif': 'image/gif', 'jpg': 'image/jpeg', 'png': 'image/png' };
220
- if ( !opts.processes )
221
- opts.processes = 1;
222
-
223
- _log_info("Starting...");
224
-
225
- // Cluster support
226
- numProcs = (opts.processes || 1);
227
- if ( numProcs > 1 ) {
228
- if ( cluster.isMaster ) {
229
- process.title = "Spritz MASTER";
230
- _log_info("Launching "+numProcs+" childs...");
231
- for ( var x = 0 ; x < numProcs ; x++ ) {
232
- var worker = cluster.fork();
233
- _workerSetup(workers,worker);
234
- }
235
-
236
- _log_info("Launched "+numProcs+" childs");
237
- cluster.on('exit',function(worker,code,signal){
238
- delete workers[worker.process.pid];
239
- self._log_error("Process #"+worker.process.pid+" died (signal "+signal+"). Launching other...");
240
- var worker = cluster.fork();
241
- _workerSetup(workers,worker);
242
- });
243
- }
244
- else {
245
- process.title = "Spritz child process";
246
- return _startServer(self,opts,callback);
247
- }
248
- }
249
- else {
250
- return _startServer(self,opts,callback);
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
- // Add worker to the list
259
- list[worker.process.pid] = worker;
256
+ // Add worker to the list
257
+ list[worker.process.pid] = worker;
260
258
 
261
- // Listen for messages
262
- worker.on('message', function(msg) {
263
- if ( typeof(msg) == "object" && msg.fn == "console.log" ) {
264
- msg.args.unshift("#"+worker.process.pid+":\t");
265
- console.log.apply(console,msg.args);
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
- var
275
- server,
276
- iface,
277
- _handleRequest = function(req,res){
278
- handleRequest(self,req,res);
279
- };
280
-
281
- self.handleRequest = _handleRequest;
282
-
283
- // Do we have a callback?
284
- if ( !callback )
285
- callback = function(){};
286
-
287
- // Decide which server module to use
288
- iface = (opts.proto == 'fastcgi') ? require('fastcgi-server') :
289
- (opts.proto == 'https') ? https :
290
- http;
291
-
292
- // Listen
293
- if ( opts.port == null )
294
- opts.port = (opts.proto == "https") ? 443 : 8080;
295
- if ( opts.port ) {
296
- server = iface.createServer(_handleRequest).listen(opts.port || 8080,opts.address || "0.0.0.0",callback);
297
- _log_info("Listening on "+(opts.address || "0.0.0.0")+":"+opts.port);
298
- }
299
- else if ( opts.address && opts.address.match(/\//) ) {
300
- server = self.createServer(_handleRequest).listen(opts.address,callback);
301
- _log_info("Listening on "+opts.address+" UNIX domain socket");
302
- }
303
- else {
304
- _log_warn("Don't know how to listen");
305
- }
306
- return server;
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
- var
314
- now = new Date();
315
-
316
- // Request just arrived, fire the hook
317
- return self._fireHook(self,'arrive',[req,res,{}],function(){
318
-
319
- // Request related values
320
- req._cType = req.headers['content-type'] ? req.headers['content-type'].toString().replace(/;.*/g,"") : "unknown/unknown";
321
- req.xRequestID = (self.reqSeq++) + "-" + process.pid.toString() + "-" + now.getYear()+now.getMonth()+now.getDay()+now.getHours()+now.getMinutes();
322
- req.xConnectDate = now;
323
- req.xRemoteAddr = req.connection.remoteAddress || ((req.client && req.client._peername) ? req.client._peername.address : "0.0.0.0");
324
- 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}$/) ) {
325
- req.xDirectRemoteAddr = req.xRemoteAddr;
326
- req.xRemoteAddr = req.headers['x-forwarded-for'];
327
- }
328
-
329
- // Response related values
330
- res._cookies = {};
331
- res.setCookie = function(n,v,o) {
332
- res._cookies[n] = { value: v, opts: o };
333
- };
334
-
335
- // Request arguments
336
- req.args = {};
337
- req.originalURL = req.url;
338
- if ( req.url.match(/^(.*?)\?(.*)$/) ) {
339
- req.url = RegExp.$1;
340
- req.urlNoArgs = RegExp.$1;
341
- req.args = qs.parse(RegExp.$2);
342
- }
343
-
344
- // POST data reader
345
- req.readPOSTData = function(cb){cb(null,{});};
346
- if ( req.method == "POST" ) {
347
- req.readPOSTData = function(cb){
348
- return readPOSTData(self,req,function(err){
349
- return cb(err,self.POSTdata);
350
- });
351
- };
352
- }
353
-
354
- // The logging flags
355
- req.xLoggingFlags = [];
356
-
357
- // Finished read request
358
- return self._fireHook(self,'readheaders',[req,res,{}],function(){
359
-
360
- // Route request
361
- return self._route(req,res);
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
- // POST data already read, don't do it again
372
- if ( req._readPOSTData )
373
- return callback(null,req);
374
- req._readPOSTData = true;
375
-
376
- // multipart/form-data or just a regular urlencoded form?
377
- if ( req._cType.match(/^multipart\/form\-data/) ) {
378
- try {
379
- var
380
- form = new formidable.IncomingForm();
381
-
382
- form.parse(req,function(err,args,files){
383
- if ( err )
384
- return callback(err,false);
385
-
386
- req.POSTargs = args;
387
- req.POSTfiles = files;
388
- return callback(null,req);
389
- });
390
- }
391
- catch(ex) {
392
- return callback(ex,null);
393
- }
394
- }
395
- else {
396
- req.setEncoding("utf-8");
397
- var buf = "";
398
- req.on('data',function(chunk){ buf += chunk; });
399
- req.on('end',function(){
400
- if ( req._cType == "application/json" ) {
401
- try { req.POSTjson = JSON.parse(buf); } catch(ex){ _log_error("Error parsing POST JSON: ",ex.toString(),"JSON: ",buf); }
402
- }
403
- else {
404
- req.POSTargs = qs.parse(buf);
405
- if ( req.POSTargs['json'] )
406
- try { req.POSTjson = JSON.parse(req.POSTargs['json']); } catch(ex){ _log_error("Error parsing POST JSON: ",ex.toString(),"JOSN: ",req.POSTargs['json']); }
407
- }
408
- return callback(null,req);
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
- var
419
- self = this,
420
- args = Array.prototype.slice.call(arguments, 0);
421
-
422
- // Get arguments
423
- r = args.shift();
424
- reqHandler = args.pop();
425
-
426
- // Is it a hook ? Set it using the hook()
427
- if ( typeof r == "string" && r.match(/^#(\w+)$/) )
428
- return self.hook(RegExp.$1,reqHandler);
429
-
430
-
431
- // Merge options with the defaults
432
- opts = _merge({
433
- // method: "GET",
434
- handler: reqHandler,
435
- expr: r
436
- },args.shift()||{});
437
-
438
- // Fire the setroute hook
439
- if ( self._fireSyncHook(self,'setroute',[opts]) ) {
440
- // Setting route was aborted
441
- return;
442
- }
443
-
444
- var routes = (r instanceof Array) ? r : [r];
445
-
446
- routes.forEach(function(r) {
447
- // Is it a RegExp ?
448
- if ( r instanceof RegExp ) {
449
- // Register the route on the RegExp route list
450
- self.rxRoutes.push([r,opts]);
451
- }
452
- else if ( typeof r == "string" ) {
453
- // Register the route on the string route list
454
- self.routes[(opts.method?opts.method.toUpperCase()+" ! ":"")+r] = opts;
455
- }
456
- else if ( typeof r == "number" ) {
457
- r = r.toString();
458
- if ( !self.statusRoutes[r] )
459
- self.statusRoutes[r] = [];
460
- // Register the route on the status route list
461
- self.statusRoutes[r].push(opts);
462
- }
463
- else
464
- throw new Error("Don't know what to do with route '"+r+"'");
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
- var
474
- self = this,
475
- routeOpts,
476
- matchedRoute;
477
-
478
- // String routes
479
- if ( self.routes[req.method+" ! "+req.url] != null ) {
480
- routeOpts = self.routes[req.method+" ! "+req.url];
481
- }
482
- else if ( self.routes[req.url] ) {
483
- routeOpts = self.routes[req.url];
484
- }
485
-
486
- // RegExp routes
487
- else {
488
- for ( var x = 0 ; x < self.rxRoutes.length ; x++ ) {
489
- if ( req.url.match(self.rxRoutes[x][0]) && (!self.rxRoutes[x][1].method || (self.rxRoutes[x][1].method.toUpperCase() == req.method)) ) {
490
- matchedRoute = self.rxRoutes[x][0];
491
- routeOpts = self.rxRoutes[x][1];
492
- break;
493
- }
494
- }
495
- }
496
-
497
- // Still no handler? 404...
498
- if ( !routeOpts ) {
499
- res.statusCode = 404;
500
- // Fire read hook
501
- return self._fireHook(self,'read',[req,res,{}],function(){
502
- return self._routeStatus(req,res,false);
503
- });
504
- }
505
-
506
- // Read POST data ?
507
- return _if ( !routeOpts.dontReadPOSTData,
508
- function(next){
509
- req.readPOSTData(next);
510
- },
511
- function(err){
512
- if ( err )
513
- _log_error("Error reading request POST data: ",err);
514
-
515
- // Fire read hook
516
- return self._fireHook(self,'read',[req,res,{}],function(){
517
-
518
- // Fire find route hook
519
- req._route = routeOpts;
520
- return self._fireHook(self,'findroute',[req,res,{route: routeOpts}],function(){
521
-
522
- // Set the RegExp object
523
- if ( matchedRoute )
524
- req.url.match(self.rxRoutes[x][0]);
525
-
526
- // Call the route handler
527
- return routeOpts.handler(req,res);
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,alreadyServed,headers) {
539
-
540
- var
541
- self = this,
542
- ans,
543
- routes;
544
-
545
- // Inside a status route handler ?
546
- if ( req.onStatusRouteH )
547
- return;
548
-
549
- // Already served ? Mark it on request, so future route handlers can take this in consideration
550
- req.served = alreadyServed;
551
-
552
- // Do we have a status handler ?
553
- routes = self.statusRoutes[res.statusCode.toString()];
554
- if ( routes ) {
555
- req.onStatusRouteH = true;
556
- return mapSeries(routes,self,
557
- function(r,next){
558
- return self._fireHook(self,'findroute',[req,res,{route:r}],next);
559
- },
560
- function(){
561
- var
562
- handlers = routes.map(function(r){return r.handler});
563
-
564
- // Call the handlers
565
- return series(handlers,[self,req,res],function(err,done){});
566
- }
567
- );
568
- }
569
-
570
- // Already served ? Ciao!
571
- if ( alreadyServed )
572
- return;
573
-
574
- // No.. default status handler
575
- ans = (res.statusCode == 404) ? { error: 'No route for this request type' } :
576
- (res.statusCode == 401) ? { warn: 'Authentication required' } :
577
- (res.statusCode >= 400) ? { error: 'Got error '+res.statusCode } :
578
- { info: 'Returning status '+res.statusCode };
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
- var
592
- headObj = {status: status, headers: headers};
589
+ var
590
+ headObj = {status: status, headers: headers};
593
591
 
594
- return self._fireHook(self,'beforewritehead',[req,res,headObj],function(){
595
- res.writeHead(headObj.status,headObj.headers);
596
- // Mark on the answer that we sent it
597
- res._sent = true;
598
- res.statusCode = headObj.status;
599
- return callback();
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
- var
609
- dataObj = { data: data, end: end };
606
+ var
607
+ dataObj = { data: data, end: end };
610
608
 
611
- return self._fireHook(self,'beforewritedata',[req,res,dataObj],function(){
609
+ return self._fireHook(self,'beforewritedata',[req,res,dataObj],function(){
612
610
 
613
- // Just writing...
614
- if ( !dataObj.end ) {
615
- res.write(dataObj.data);
616
- return callback();
617
- }
611
+ // Just writing...
612
+ if ( !dataObj.end ) {
613
+ res.write(dataObj.data);
614
+ return callback();
615
+ }
618
616
 
619
- // Write and end
620
- return self._fireHook(self,'beforefinish',[req,res,dataObj],function(){
621
- res.end(dataObj.data);
617
+ // Write and end
618
+ return self._fireHook(self,'beforefinish',[req,res,dataObj],function(){
619
+ res.end(dataObj.data);
622
620
 
623
- // Finish
624
- return self._fireHook(self,'finish',[req,res,{}],function(){
625
- return callback();
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
- var
637
- pr;
634
+ var
635
+ pr;
638
636
 
639
- return self._fireHook(self,'beforewritedata',[req,res,stream],function(){
637
+ return self._fireHook(self,'beforewritedata',[req,res,stream],function(){
640
638
 
641
- // Pipe the stream
642
- pr = stream.pipe(res);
643
- stream.on('end',function(){
644
- callback(null,true);
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
- var
657
- self = this,
658
- length = Buffer.byteLength(content,'utf8'),
659
- _headers = _merge({
660
- 'content-type': 'text/plain; charset=utf-8',
661
- 'content-length': length,
662
- 'date': new Date().toUTCString()
663
- },headers,true);
664
-
665
- // Set the status code
666
- res.statusCode = status || 200;
667
-
668
- // Send data
669
- return _writeHead(self,req,res,res.statusCode,_headers,function(){
670
- return _writeData(self,req,res,content,true,function(){
671
- // Log
672
- self._access_log(req,res,length);
673
-
674
- // Report status
675
- self._routeStatus(req,res,true);
676
-
677
- // Call the callback
678
- if ( callback )
679
- callback(null,true);
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
- var
689
- strfyArgs = [content];
686
+ var
687
+ strfyArgs = [content];
690
688
 
691
- if ( pretty && typeof pretty == "function" ) {
692
- callback = pretty;
693
- pretty = false;
694
- }
695
- if ( pretty )
696
- strfyArgs.push(null,4);
689
+ if ( pretty && typeof pretty == "function" ) {
690
+ callback = pretty;
691
+ pretty = false;
692
+ }
693
+ if ( pretty )
694
+ strfyArgs.push(null,4);
697
695
 
698
- // Build JSON content
699
- content = JSON.stringify.apply(null,strfyArgs);
696
+ // Build JSON content
697
+ content = JSON.stringify.apply(null,strfyArgs);
700
698
 
701
- // JSONP ?
702
- if ( req.args.callback )
703
- content = req.args.callback.toString() + "(" + content.replace(/[\u00a0\u2000-\u203f]/g,"") + ");";
699
+ // JSONP ?
700
+ if ( req.args.callback )
701
+ content = req.args.callback.toString() + "(" + content.replace(/[\u00a0\u2000-\u203f]/g,"") + ");";
704
702
 
705
- // Send the data
706
- this.text(req,res,content,status,_merge({"content-type":"application/json; charset=utf-8"},headers,true));
703
+ // Send the data
704
+ this.text(req,res,content,status,_merge({"content-type":"application/json; charset=utf-8"},headers,true));
707
705
 
708
- // Call the callback
709
- if ( callback )
710
- callback(null,true);
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
- throw new Exception("No templating module was loaded. Use spritz.use()");
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
- var
739
- _args = [type],
740
- _keys;
741
-
742
- // Convert arguments into array - old style
743
- _keys = args ? Object.keys(args) : [];
744
- _keys.forEach(function(num){
745
- _args.push(args[num]);
746
- });
747
-
748
- // Master, send directly to console
749
- if ( cluster.isMaster ) {
750
- _args.unshift("MASTER:\t");
751
- console.log.apply(console,_args);
752
- }
753
- // Children send via cluster messaging system
754
- else {
755
- // Send to the master process, so we avoid problems with many processes writing on the same file
756
- process.send({fn:'console.log',args: _args});
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 = _log;
760
- exports._log_info = _log_info;
761
- exports._log_warn = _log_warn;
762
- exports._log_error = _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
- var
767
- timeSpent = new Date().getTime() - req.xConnectDate.getTime(),
768
- flags = "";
764
+ var
765
+ timeSpent = new Date().getTime() - req.xConnectDate.getTime(),
766
+ flags = "";
769
767
 
770
- if ( req.xLoggingFlags && req.xLoggingFlags.length > 0 )
771
- flags = " "+req.xLoggingFlags.join('');
768
+ if ( req.xLoggingFlags && req.xLoggingFlags.length > 0 )
769
+ flags = " "+req.xLoggingFlags.join('');
772
770
 
773
- _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);
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
- var o = {};
779
- if ( a != null ) {
780
- for ( var p in a )
781
- o[lcProps?p.toLowerCase():p] = a[p];
782
- }
783
- if ( b != null ) {
784
- for ( var p in b )
785
- o[lcProps?p.toLowerCase():p] = b[p];
786
- }
787
- return o;
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
- return c ? a(b) : b();
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
- var
800
- _self = args.shift(),
801
- _fns = fns.slice(),
802
- _next = function(){
803
- if ( !_fns.length )
804
- return callback(null,false);
805
- _fns.shift().apply(_self,args);
806
- };
807
-
808
- // Add as last argument our function return handler
809
- args.push(function(err,stop,done){
810
- if ( err )
811
- return callback(err,false);
812
- return (stop || done) ? callback(null,done) : setImmediate(_next);
813
- });
814
-
815
- return _next();
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
- var
820
- _arr = arr.slice(),
821
- _res = [],
822
- _next = function(err,res){
823
- if ( !_arr.length )
824
- return fiCb(err,_res);
825
- itCb.apply(_self,[_arr.shift(),function(err,res){
826
- if ( err )
827
- return fiCb(err,_res);
828
- _res.push(res);
829
- setImmediate(_next);
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