From ed81bdb6b8fc9f9fbe22fe75fa686d47ea4537f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Mon, 13 Jan 2014 15:24:52 +0100 Subject: [PATCH 1/4] Implement app.installMiddleware The implementation is mostly a copy of loopback-workspace/templates/app.js --- lib/application.js | 125 +++++++++++++++++++++++++++++++++++++++++++++ test/app.test.js | 96 ++++++++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+) diff --git a/lib/application.js b/lib/application.js index ddcdfdbd..426d1e82 100644 --- a/lib/application.js +++ b/lib/application.js @@ -439,6 +439,131 @@ function tryReadConfig(cwd, fileName) { } } +/** + * Install all express middleware required by LoopBack. + * + * It is possible to inject your own middleware by listening on one of the + * following events: + * + * - `middleware:preprocessors` is emitted after all other + * request-preprocessing middleware was installed, but before any + * request-handling middleware is configured. + * + * Usage: + * ```js + * app.on('middleware:preprocessors', function() { + * app.use(loopback.limit('5.5mb')) + * }); + * ``` + * - `middleware:handlers` is emitted when it's time to add your custom + * request-handling middleware. Note that you should not install any + * express routes at this point (express routes are discussed later). + * + * Usage: + * ```js + * app.on('middleware:handlers', function() { + * app.use('/admin', adminExpressApp); + * app.use('/custom', function(req, res, next) { + * res.send(200, { url: req.url }); + * }); + * }); + * ``` + * - `middleware:error-loggers` is emitted at the end, before the loopback + * error handling middleware is installed. This is the point where you + * can install your own middleware to log errors. + * + * Notes: + * - The middleware function must take four parameters, otherwise it won't + * be called by express. + * + * - It should also call `next(err)` to let the loopback error handler convert + * the error to an HTTP error response. + * + * Usage: + * ```js + * var bunyan = require('bunyan'); + * var log = bunyan.createLogger({name: "myapp"}); + * app.on('middleware:error-loggers', function() { + * app.use(function(err, req, res, next) { + * log.error(err); + * next(err); + * }); + * }); + * ``` + * + * Express routes should be added after `installMiddleware` was called. + * This way the express router middleware is injected at the right place in the + * middleware chain. If you add an express route before calling this function, + * bad things will happen: Express will automatically add the router + * middleware and since we haven't added request-preprocessing middleware like + * cookie & body parser yet, your route handlers will receive raw unprocessed + * requests. + * + * This is the correct order in which to call `app` methods: + * ```js + * app.boot(__dirname); // optional + * + * app.installMiddleware(); + * + * // [register your express routes here] + * + * app.listen(); + * ``` + */ +app.installMiddleware = function() { + var loopback = require('../'); + + /* + * Request pre-processing + */ + this.use(loopback.favicon()); + // TODO(bajtos) refactor to app.get('loggerFormat') + var loggerFormat = this.get('env') === 'development' ? 'dev' : 'default'; + this.use(loopback.logger(loggerFormat)); + this.use(loopback.cookieParser(this.get('cookieSecret'))); + this.use(loopback.token({ model: this.models.accessToken })); + this.use(loopback.bodyParser()); + this.use(loopback.methodOverride()); + + // Allow the app to install custom preprocessing middleware + this.emit('middleware:preprocessors'); + + /* + * Request handling + */ + + // LoopBack REST transport + this.use(this.get('restApiRoot') || '/api', loopback.rest()); + + // Allow the app to install custom request handling middleware + this.emit('middleware:handlers'); + + // Let express routes handle requests that were not handled + // by any of the middleware registered above. + // This way LoopBack REST and API Explorer take precedence over + // express routes. + this.use(this.router); + + // The static file server should come after all other routes + // Every request that goes through the static middleware hits + // the file system to check if a file exists. + this.use(loopback.static(path.join(__dirname, 'public'))); + + // Requests that get this far won't be handled + // by any middleware. Convert them into a 404 error + // that will be handled later down the chain. + this.use(loopback.urlNotFound()); + + /* + * Error handling + */ + + // Allow the app to install custom error logging middleware + this.emit('middleware:error-handlers'); + + // The ultimate error handler. + this.use(loopback.errorHandler()); +}; /** * Listen for connections and update the configured port. diff --git a/test/app.test.js b/test/app.test.js index be1d4013..4087d33a 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -197,6 +197,102 @@ describe('app', function() { }); }); + describe('installMiddleware()', function() { + var app; + beforeEach(function() { app = loopback(); }); + + it('installs loopback.token', function(done) { + app.models.accessToken = loopback.AccessToken; + + app.installMiddleware(); + + app.get('/', function(req, res) { + res.send({ accessTokenId: req.accessToken && req.accessToken.id }); + }); + + app.models.accessToken.create({}, function(err, token) { + if (err) done(err); + request(app).get('/') + .set('Authorization', token.id) + .expect(200, { accessTokenId: token.id }) + .end(done); + }); + }); + + it('emits "middleware:preprocessors" before handlers are installed', + function(done) { + app.on('middleware:preprocessors', function() { + this.use(function(req, res, next) { + req.preprocessed = true; + next(); + }); + }); + + app.installMiddleware(); + + app.get('/', function(req, res) { + res.send({ preprocessed: req.preprocessed }); + }); + + request(app).get('/') + .expect(200, { preprocessed: true}) + .end(done); + } + ); + + it('emits "middleware:handlers before installing express router', + function(done) { + app.on('middleware:handlers', function() { + this.use(function(req, res, next) { + res.send({ handler: 'middleware' }); + }); + }); + + app.installMiddleware(); + + app.get('/', function(req, res) { + res.send({ handler: 'router' }); + }); + + request(app).get('/') + .expect(200, { handler: 'middleware' }) + .end(done); + } + ); + + it('emits "middleware:error-handlers" after all request handlers', + function(done) { + var logs = []; + app.on('middleware:error-handlers', function() { + app.use(function(err, req, res, next) { + logs.push(req.url); + next(err); + }); + }); + + app.installMiddleware(); + + request(app).get('/not-found') + .expect(404) + .end(function(err, res) { + if (err) done(err); + expect(logs).to.eql(['/not-found']); + done(); + }); + } + ); + + it('installs REST transport', function(done) { + app.model(loopback.Application); + app.set('restApiRoot', '/api'); + app.installMiddleware(); + + request(app).get('/api/applications') + .expect(200, []) + .end(done); + }); + }); + describe('listen()', function() { it('starts http server', function(done) { var app = loopback(); From 809411c103060c9f3b51bc36f9cb27f596d941ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Mon, 13 Jan 2014 20:05:22 +0100 Subject: [PATCH 2/4] Speed up tests accessing User.password Decrease User.settings.saltWorkFactor in order to speed up unit-tests. --- test/support.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/support.js b/test/support.js index 2262e294..394bc6d4 100644 --- a/test/support.js +++ b/test/support.js @@ -11,6 +11,11 @@ app = null; TaskEmitter = require('strong-task-emitter'); request = require('supertest'); + +// Speed up the password hashing algorithm +// for tests using the built-in User model +loopback.User.settings.saltWorkFactor = 4; + beforeEach(function () { app = loopback(); From 8573d01c162d08340275344a3d24d5188b9dbe07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 14 Jan 2014 08:07:47 +0100 Subject: [PATCH 3/4] Replace `on` with `once` in middleware examples Fix the jsdox for `app.installMiddleware` to use `app.once` for listening on 'middleware:*' events. The previous doc version using `on` was a sort of preliminary pessimisation, becase the event handlers would stay in the heap for the whole life-time of the app, even though they won't be called more than once. --- lib/application.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/application.js b/lib/application.js index 426d1e82..c7a19365 100644 --- a/lib/application.js +++ b/lib/application.js @@ -451,7 +451,7 @@ function tryReadConfig(cwd, fileName) { * * Usage: * ```js - * app.on('middleware:preprocessors', function() { + * app.once('middleware:preprocessors', function() { * app.use(loopback.limit('5.5mb')) * }); * ``` @@ -461,7 +461,7 @@ function tryReadConfig(cwd, fileName) { * * Usage: * ```js - * app.on('middleware:handlers', function() { + * app.once('middleware:handlers', function() { * app.use('/admin', adminExpressApp); * app.use('/custom', function(req, res, next) { * res.send(200, { url: req.url }); @@ -483,7 +483,7 @@ function tryReadConfig(cwd, fileName) { * ```js * var bunyan = require('bunyan'); * var log = bunyan.createLogger({name: "myapp"}); - * app.on('middleware:error-loggers', function() { + * app.once('middleware:error-loggers', function() { * app.use(function(err, req, res, next) { * log.error(err); * next(err); From 7fbb70d520f419a877fd8ceb0e7fd24711fbe614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 14 Jan 2014 08:29:56 +0100 Subject: [PATCH 4/4] v1.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 88eb97c9..2db043ea 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "Platform", "mBaaS" ], - "version": "1.4.2", + "version": "1.5.0", "scripts": { "test": "mocha -R spec" },