diff --git a/lib/processingChain.js b/lib/processingChain.js index f5bff87..5e8a106 100644 --- a/lib/processingChain.js +++ b/lib/processingChain.js @@ -9,6 +9,9 @@ function ProcessingChain( ) { } ProcessingChain.prototype.add = function( proc ) { + proc.names = proc.names ? + ( Array.isArray(proc.names) ? proc.names : Array(proc.names) ) + : []; if(proc.fn.length == 4) //it's an error handler this.errorHandlers.push(proc); else @@ -23,9 +26,17 @@ ProcessingChain.prototype.pop = function() { this.chain.pop(); } -ProcessingChain.prototype.runChain = function( req, res, finalFn, handler ) { +ProcessingChain.prototype.runChain = function( params ) { + params = params || {}; + var req = params.req; + var res = params.res; + var finalFn = params.finalFn; + var handler = params.handler; + var endPrep = (params.endPrep && {fn: params.endPrep}) || []; //last preprocessor + var currentItem = 0; - var totalItems = this.chain.length; + var chain = [].concat(this.chain, endPrep); + var totalItems = chain.length; var self = this; if(totalItems == 0) { if(typeof finalFn == 'function') finalFn(req, res); @@ -42,18 +53,23 @@ ProcessingChain.prototype.runChain = function( req, res, finalFn, handler ) { } var next = function(err) { - var chain = self.chain; + //chain is taken from the closure if ( err ) { //If there is an error, switch to the error handlers chain chain = self.errorHandlers; currentItem = -1; totalItems = self.errorHandlers.length; } if ( currentItem < totalItems - 1 ) { - for(var idx = currentItem + 1; idx < chain.length; idx++) { - if( (chain[idx].names && chain[idx].names.indexOf(handler.name) != -1) || !chain[idx].names) { - break + var idx = ++currentItem; + + if( handler && handler.name) { + for(; idx < chain.length; idx++) { + if( !chain[idx].names || ( ! chain[idx].names.length ) || ~chain[idx].names.indexOf(handler.name)) { + break + } } - } + } + currentItem = idx if(err) { chain[currentItem].fn(err, req, res, nextError) @@ -65,16 +81,16 @@ ProcessingChain.prototype.runChain = function( req, res, finalFn, handler ) { } } if(handler) { - var firstItem = self.findFirstValidItem(handler.name) + var firstItem = self.findFirstValidItem(handler.name, chain) firstItem.fn(req, res, next) } else { - this.chain[0].fn(req, res, next ) + chain[0].fn(req, res, next ) } }; -ProcessingChain.prototype.findFirstValidItem = function(name) { - if(!name) return this.chain[0] - return _.find(this.chain, function(item) { +ProcessingChain.prototype.findFirstValidItem = function(name, chain) { + if(!name) return chain[0] + return _.find(chain, function(item) { if(item.names && Array.isArray(item.names) && item.names.length > 0) { return item.names.indexOf(name) != -1 } else { diff --git a/lib/vatican.js b/lib/vatican.js index eaa6a3a..ea459ae 100644 --- a/lib/vatican.js +++ b/lib/vatican.js @@ -5,7 +5,8 @@ var http = require("http"), handlerParser = require("./handlerParser"), processingChain = require("./processingChain"), mongoose = require("mongoose"), - _ = require("lodash"); + _ = require("lodash"), + path = require("path"); module.exports = Vatican; @@ -37,22 +38,20 @@ function Vatican(options) { this.parseHandlers(); this.paths = []; this.server = null; - this.totalPreprocessors = 0; this.preprocessors = new processingChain(); this.postprocessors = new processingChain(); } Vatican.prototype.preprocess = function(fn, endpointNames) { - this.preprocessors.add({fn: fn, names: endpointNames ? endpointNames : []}) - this.totalPreprocessors = this.preprocessors.getTotal(); + this.preprocessors.add({fn: fn, names: endpointNames}) } Vatican.prototype.postprocess = function(fn, endpointNames) { - this.postprocessors.add({fn: fn, names: endpointNames ? endpointNames : []}); + this.postprocessors.add({fn: fn, names: endpointNames}); } Vatican.prototype.parseHandlers = function(cb) { - var dir = this.options.handlers; + var dir = path.isAbsolute(this.options.handlers) ? this.options.handlers : process.cwd() + "/" + this.options.handlers; var self = this; handlerParser.parse(dir, function(err, path) { if(typeof cb == 'function' && err) return cb(err) @@ -148,18 +147,20 @@ Vatican.prototype.requestHandler = function (req, res) { } else { try { var request = this.createRequest(req); - var hdlr = this.loadHandler(process.cwd() + "/" + methodFound.handlerPath); + var hdlr = this.loadHandler(methodFound.handlerPath); res = vaticanResp.improveResponse(res, request, this.options, this.postprocessors); //Parse the request to grab the parameters this.parseRequest(request, methodFound.url, req, function(newRequest) { - //Run the pre-process chain and finally, call the handler - if(self.preprocessors.getTotal() > self.totalPreprocessors) { - self.preprocessors.pop(); - } var hdlrInstance = new hdlr(self.getCorrectModel(methodFound)) + hdlrInstance.models = self.dbmodels //Let the handler access all other models in case they're neeeded - self.preprocessors.add({fn: hdlrInstance[methodFound.action].bind(hdlrInstance)}) - self.preprocessors.runChain(newRequest, res, null, methodFound); + + self.preprocessors.runChain({ + req: newRequest, + res: res, + handler: methodFound, + endPrep: hdlrInstance[methodFound.action].bind(hdlrInstance), + }); }); } catch (ex) { logger.error("Error instantiating handler: " + ex.message); @@ -171,7 +172,7 @@ Vatican.prototype.requestHandler = function (req, res) { Vatican.prototype.getCorrectModel = function(handler) { var modelName = handler.handlerName.replace("Hdlr", '') - return this.dbmodels[modelName] + return this.dbmodels && this.dbmodels[modelName] } /** @@ -181,6 +182,7 @@ Vatican.prototype.start = function(cb) { try { this.server = http.createServer(this.requestHandler.bind(this)); this.server.listen(this.options.port); + console.log(this.server); logger.info("Server started on port: " + this.options.port); if(typeof cb == 'function') { cb(); @@ -191,6 +193,22 @@ Vatican.prototype.start = function(cb) { } }; +/** + Close the server +*/ +Vatican.prototype.close = function(cb) { + try { + this.server.close(); + logger.info("Server closed"); + if(typeof cb == 'function') { + cb(); + } + } catch (ex) { + logger.error("Error closing server: " + ex.message); + return false; + } +} + Vatican.prototype.dbStart = function(opts, cb) { if(typeof opts === 'function') { cb = opts diff --git a/lib/vaticanResponse.js b/lib/vaticanResponse.js index 472d5fa..77291eb 100644 --- a/lib/vaticanResponse.js +++ b/lib/vaticanResponse.js @@ -17,26 +17,30 @@ VaticanResponse.prototype.send = function(txt) { var headers = {}; var self = this; this.body = txt; - this.ppChain.runChain(this.request, this, function(req, resp) { - //Check for CORS config - if(self.options.cors !== false) { - headers = _getCORSHeaders(self.options.cors); - } + this.ppChain.runChain({ + req: this.request, + res: this, + finalFn: function(req, resp) { + //Check for CORS config + if(self.options.cors !== false) { + headers = _getCORSHeaders(self.options.cors); + } - //Adds the rest of the headers - for(var i in resp.headers) { - headers = _.assign(headers, resp.headers[i]); - } + //Adds the rest of the headers + for(var i in resp.headers) { + headers = _.assign(headers, resp.headers[i]); + } - //Write the headers - resp.response.writeHead(resp.statusCode, headers); - if( typeof resp.body == 'object') { - resp.body = JSON.stringify(resp.body); - } + //Write the headers + resp.response.writeHead(resp.statusCode, headers); + if( typeof resp.body == 'object') { + resp.body = JSON.stringify(resp.body); + } - //Write out the response text - resp.response.write(resp.body); - resp.response.end(); + //Write out the response text + resp.response.write(resp.body); + resp.response.end(); + }, }) }; @@ -95,4 +99,4 @@ function _getCORSHeaders(corsOpts) { module.exports = { improveResponse: _improveResponse, writeNotFound: _writeNotFound -}; \ No newline at end of file +}; diff --git a/package.json b/package.json index ee8c11f..ed53d47 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "devDependencies": { "should": "4.0.4", "mocha": "1.21.4", + "supertest": "0.13.0", "istanbul": "0.3.0" } } diff --git a/test/fixtures/vaticanConfig/handlers/people.js b/test/fixtures/vaticanConfig/handlers/people.js new file mode 100644 index 0000000..9871bcf --- /dev/null +++ b/test/fixtures/vaticanConfig/handlers/people.js @@ -0,0 +1,6 @@ +module.exports = People; +function People() {} +@endpoint (url: /people method: get) +People.prototype.getPeople = function(req, res) { + res.send('ok'); +} diff --git a/test/fixtures/vaticanHttpMethods/handlers/people.js b/test/fixtures/vaticanHttpMethods/handlers/people.js new file mode 100644 index 0000000..747cfe2 --- /dev/null +++ b/test/fixtures/vaticanHttpMethods/handlers/people.js @@ -0,0 +1,22 @@ +module.exports = People; +var peeps = [ 'user0', 'user1']; +function People() {} +@endpoint (url: /people method: get) +People.prototype.getPeople = function(req, res) { + res.send('get,' + peeps.join(',')); +} + +@endpoint (url: /people method: post) +People.prototype.postPeople = function(req, res) { + res.send('post,' + peeps.join(',')); +} + +@endpoint (url: /people method: put) +People.prototype.putPeople = function(req, res) { + res.send('put,' + peeps.join(',')); +} + +@endpoint (url: /people method: delete) +People.prototype.deletePeople = function(req, res) { + res.send('delete,' + peeps.join(',')); +} diff --git a/test/processingChain.js b/test/processingChain.js index fb96807..da529c0 100644 --- a/test/processingChain.js +++ b/test/processingChain.js @@ -47,19 +47,20 @@ describe('Processing Chain methods', function() { pc = new ProcessingChain() pc.add({fn: 1}) pc.add({fn: 2}) - pc.findFirstValidItem().fn.should.equal(1) + pc.findFirstValidItem(undefined, pc.chain).fn.should.equal(1) }) it("should correctly find the first valid process when there is an endpoint name set", function() { pc = new ProcessingChain() pc.add({fn: 1, names: ['first']}) pc.add({fn: 2, names: ['second']}) - pc.findFirstValidItem('second').fn.should.equal(2) + pc.findFirstValidItem('first', pc.chain).fn.should.equal(1) + pc.findFirstValidItem('second', pc.chain).fn.should.equal(2) }) }) describe("@runChain", function() { - it("should run the chain correctly", function(done) { + it("should run the chain correctly without handler name", function(done) { var result = "" pc = new ProcessingChain() pc.add({fn: function(req, res, n) { @@ -67,17 +68,65 @@ describe('Processing Chain methods', function() { n() }}) pc.add({fn: function(req, res, n) { - result+= "2" + result+= "2" + n() + }, + names: ['ok'], + }) + pc.add({fn: function(req, res, n) { + result+= "3" + n() + }}) + pc.runChain({ + req: {}, + res: {}, + finalFn: function() { + result.should.equal("123") + done() + }, + }) + }) + + it("should run the chain correctly with handler name", function(done) { + var result = "" + pc = new ProcessingChain() + pc.add({fn: function(req, res, n) { + result+= "1" n() }}) + pc.add({fn: function(req, res, n) { + result+= "2" + n() + }, + names: ['ok'], + }) pc.add({fn: function(req, res, n) { result+= "3" n() }}) - pc.runChain({}, {}, function() { - result.should.equal("123") - done() - }, null) + + pc.add({fn: function(req, res, n) { + result+= "4" + n() + }, + names: ['not ok', 'nott1 ok'] + }); + + pc.add({fn: function(req, res, n) { + result+= "5" + n() + }, + names: ['not ok', 'ok'] + }); + pc.runChain({ + req: {}, + res: {}, + finalFn: function() { + result.should.equal("1235") + done() + }, + handler: {name: 'ok'} + }) }) it("should switch to the error chain if there is a problem", function(done) { @@ -104,10 +153,14 @@ describe('Processing Chain methods', function() { result += 'e2' n() }}) - pc.runChain({}, {}, function() { - result.should.equal("12errore2") - done() - }, null) + pc.runChain({ + req: {}, + res: {}, + finalFn: function() { + result.should.equal("12errore2") + done() + } + }) }) it("should run correctly if there are named endpoints involved", function(done) { @@ -131,10 +184,15 @@ describe('Processing Chain methods', function() { n() }, names: ["endpoint2", "endpoint1"]}) - pc.runChain({}, {}, function() { - result.should.equal("134") - done() - }, {name: 'endpoint2'}) + pc.runChain({ + req: {}, + res: {}, + finalFn: function() { + result.should.equal("134") + done() + }, + handler: {name: 'endpoint2'} + }) }) }) }) diff --git a/test/vaticanConfig.js b/test/vaticanConfig.js new file mode 100644 index 0000000..489ff26 --- /dev/null +++ b/test/vaticanConfig.js @@ -0,0 +1,43 @@ +var should = require('should'), + vatican = require('../'), + request = require('supertest'); + +describe('vatican configuration', function() { + it('handlers must support a relative path', function( done ) { + var app = new vatican({ + 'handlers': 'fixtures/vaticanConfig/handlers', + 'port': 24000, + }); + + app.start(); + + request('http://localhost:24000') + .get('/people') + .expect(200) + .expect('ok') + .end(function(err) { + ( err == undefined ).should.be.true; + app.close(); + done(); + }); + }); + + it('handlers must support a absolute path', function( done ) { + var app = new vatican({ + 'handlers': __dirname + '/fixtures/vaticanConfig/handlers', + 'port': 24000, + }); + + app.start(); + + request('http://localhost:24000') + .get('/people') + .expect(200) + .expect('ok') + .end(function(err) { + ( err == undefined ).should.be.true; + app.close(); + done(); + }); + }); +}); diff --git a/test/vaticanHttpMethods.js b/test/vaticanHttpMethods.js new file mode 100644 index 0000000..c2d46c2 --- /dev/null +++ b/test/vaticanHttpMethods.js @@ -0,0 +1,32 @@ +var request = require('supertest'), + should = require('should'), + vatican = require('../'), + http = require('http'); + +describe('vatican http methods', function() { + var app = new vatican({ + 'handlers': __dirname + '/fixtures/vaticanHttpMethods/handlers', + 'port': 24000, + }); + + before(function() { + app.start(); + }); + + after(function() { + app.close(); + }); + + [ + 'get', + 'put', + 'post', + 'delete' + ].forEach(function(method) { + it(method + " method without params", function( done ) { + request('http://localhost:24000')[method]('/people') + .expect(200) //statusCode + .expect(method + ',user0,user1', done); //body + }); + }); +});