diff --git a/API.md b/API.md index a0d8dd37b..2e260cea3 100755 --- a/API.md +++ b/API.md @@ -28,6 +28,7 @@ - [`server.dependency(dependencies, [after])`](#serverdependencydependencies-after) - [`server.expose(key, value)`](#serverexposekey-value) - [`server.expose(obj)`](#serverexposeobj) + - [`server.ext(events)`](#serverextevents) - [`server.ext(event, method, [options])`](#serverextevent-method-options) - [`server.handler(name, method)`](#serverhandlername-method) - [`server.initialize(callback)`](#serverinitializecallback) @@ -973,38 +974,68 @@ exports.register = function (server, options, next) { }; ``` -### `server.ext(event, method, [options])` +### `server.ext(events)` Registers an extension function in one of the available extension points where: -- `event` - the event name. -- `method` - a function or an array of functions to be executed at a specified point during request - processing. The required extension function signature is: - - server extension points: `function(server, next)` where: - - `server` - the server object. - - `next` - the continuation method with signature `function(err)`. - - `this` - the object provided via `options.bind` or the current active context set with - [`server.bind()`](#serverbindcontext). - - request extension points: `function(request, reply)` where: - - `request` - the [request object](#request-object). - - `reply` - the [reply interface](#reply-interface) which is used to return control back to the - framework. To continue normal execution of the [request lifecycle](#request-lifecycle), - `reply.continue()` must be called. To abort processing and return a response to the client, - call `reply(value)` where value is an error or any other valid response. - - `this` - the object provided via `options.bind` or the current active context set with - [`server.bind()`](#serverbindcontext). -- `options` - an optional object with the following: - - `before` - a string or array of strings of plugin names this method must execute before (on - the same event). Otherwise, extension methods are executed in the order added. - - `after` - a string or array of strings of plugin names this method must execute after (on the - same event). Otherwise, extension methods are executed in the order added. - - `bind` - a context object passed back to the provided method (via `this`) when called. - -The available extension points include the [request extension points](#request-lifecycle) as well -as the following server extension points: -- `'onPreStart'` - called before the connection listeners are started. -- `'onPostStart'` - called after the connection listeners are started. -- `'onPreStop'` - called before the connection listeners are stopped. -- `'onPostStop'` - called after the connection listeners are stopped. +- `events` - an object or array of objects with the following: + - `type` - the extension point event name. The available extension points include the + [request extension points](#request-lifecycle) as well as the following server extension points: + - `'onPreStart'` - called before the connection listeners are started. + - `'onPostStart'` - called after the connection listeners are started. + - `'onPreStop'` - called before the connection listeners are stopped. + - `'onPostStop'` - called after the connection listeners are stopped. + - `method` - a function or an array of functions to be executed at a specified point during request + processing. The required extension function signature is: + - server extension points: `function(server, next)` where: + - `server` - the server object. + - `next` - the continuation method with signature `function(err)`. + - `this` - the object provided via `options.bind` or the current active context set with + [`server.bind()`](#serverbindcontext). + - request extension points: `function(request, reply)` where: + - `request` - the [request object](#request-object). + - `reply` - the [reply interface](#reply-interface) which is used to return control back to the + framework. To continue normal execution of the [request lifecycle](#request-lifecycle), + `reply.continue()` must be called. To abort processing and return a response to the client, + call `reply(value)` where value is an error or any other valid response. + - `this` - the object provided via `options.bind` or the current active context set with + [`server.bind()`](#serverbindcontext). + - `options` - an optional object with the following: + - `before` - a string or array of strings of plugin names this method must execute before (on + the same event). Otherwise, extension methods are executed in the order added. + - `after` - a string or array of strings of plugin names this method must execute after (on the + same event). Otherwise, extension methods are executed in the order added. + - `bind` - a context object passed back to the provided method (via `this`) when called. + +```js +var Hapi = require('hapi'); +var server = new Hapi.Server(); +server.connection({ port: 80 }); + +server.ext({ + type: 'onRequest', + method: function (request, reply) { + + // Change all requests to '/test' + request.setUrl('/test'); + return reply.continue(); + } +}); + +var handler = function (request, reply) { + + return reply({ status: 'ok' }); +}; + +server.route({ method: 'GET', path: '/test', handler: handler }); +server.start(function (err) { }); + +// All requests will get routed to '/test' +``` + +### `server.ext(event, method, [options])` + +Registers a single extension event using the same properties as used in +[`server.ext(events)`](#serverextevents), but passed as arguments. ```js var Hapi = require('hapi'); diff --git a/lib/plugin.js b/lib/plugin.js index b63fca426..11d1046e0 100755 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -36,6 +36,13 @@ exports = module.exports = internals.Plugin = function (server, connections, env this.version = Package.version; this.realm = typeof env !== 'string' ? env : { + _extensions: { + onPreAuth: null, + onPostAuth: null, + onPreHandler: null, + onPostHandler: null, + onPreResponse: null + }, modifiers: { route: {} }, @@ -418,11 +425,24 @@ internals.Plugin.prototype.expose = function (key, value) { }; -internals.Plugin.prototype.ext = function (event, func, options) { +internals.Plugin.prototype.ext = function (events) { // (event, method, options) -OR- (events) - var self = this; + if (typeof events === 'string') { + events = { type: arguments[0], method: arguments[1], options: arguments[2] }; + } + + events = [].concat(events); + for (var i = 0, il = events.length; i < il; ++i) { + this._ext(events[i]); + } +}; - options = options || {}; + +internals.Plugin.prototype._ext = function (event) { + + var type = event.type; + var methods = [].concat(event.method); + var options = event.options || {}; var settings = { before: options.before, @@ -431,30 +451,29 @@ internals.Plugin.prototype.ext = function (event, func, options) { }; var nodes = []; - ([].concat(func)).forEach(function (fn, i) { - + for (var i = 0, il = methods.length; i < il; ++i) { var node = { - func: fn, // Connection: function (request, next), Server: function (server, next) - realm: self.realm, + func: methods[i], // Connection: function (request, next), Server: function (server, next) + realm: this.realm, bind: options.bind, - plugin: self + plugin: this }; nodes.push(node); - }); + } // Connection extensions - if (this.root._extensions[event] === undefined) { - return this._apply('ext', Connection.prototype._ext, [event, nodes, settings]); + if (this.root._extensions[type] === undefined) { + return this._apply('ext', Connection.prototype._ext, [type, nodes, settings]); } // Server extensions - Hoek.assert(event !== 'onPreStart' || this.root._state === 'stopped', 'Cannot add onPreStart (after) extension after the server was initialized'); + Hoek.assert(type !== 'onPreStart' || this.root._state === 'stopped', 'Cannot add onPreStart (after) extension after the server was initialized'); - this.root._extensions[event] = this.root._extensions[event] || new Topo(); - this.root._extensions[event].add(nodes, settings); + this.root._extensions[type] = this.root._extensions[type] || new Topo(); + this.root._extensions[type].add(nodes, settings); }; diff --git a/lib/protect.js b/lib/protect.js index 03bfcf0f7..348b58a5d 100755 --- a/lib/protect.js +++ b/lib/protect.js @@ -48,10 +48,6 @@ internals.Protect.prototype.run = function (next, enter) { // enter var self = this; - if (!this.domain) { - return enter(finish); - } - var finish = function (arg0, arg1, arg2) { self._error = null; @@ -60,6 +56,10 @@ internals.Protect.prototype.run = function (next, enter) { // enter finish = Hoek.once(finish); + if (!this.domain) { + return enter(finish); + } + this._error = function (err) { return finish(Boom.badImplementation('Uncaught error', err)); diff --git a/lib/route.js b/lib/route.js index f3c8955a4..706b09978 100755 --- a/lib/route.js +++ b/lib/route.js @@ -61,12 +61,13 @@ exports = module.exports = internals.Route = function (options, connection, real this.server = connection.server; this.path = options.path; this.method = method; + this.realm = realm; this.public = { method: this.method, path: this.path, vhost: this.vhost, - realm: realm, + realm: this.realm, settings: this.settings }; @@ -241,11 +242,11 @@ exports = module.exports = internals.Route = function (options, connection, real // Route lifecycle this._extensions = { - onPreAuth: Hoek.clone(this.connection._extensions.onPreAuth), - onPostAuth: Hoek.clone(this.connection._extensions.onPostAuth), - onPreHandler: Hoek.clone(this.connection._extensions.onPreHandler), - onPostHandler: Hoek.clone(this.connection._extensions.onPostHandler), - onPreResponse: Hoek.clone(this.connection._extensions.onPreResponse) + onPreAuth: this._combineExtensions('onPreAuth'), + onPostAuth: this._combineExtensions('onPostAuth'), + onPreHandler: this._combineExtensions('onPreHandler'), + onPostHandler: this._combineExtensions('onPostHandler'), + onPreResponse: this._combineExtensions('onPreResponse') }; this._cycle = null; @@ -253,6 +254,19 @@ exports = module.exports = internals.Route = function (options, connection, real }; +internals.Route.prototype._combineExtensions = function (event) { + + var sources = [ + this.connection._extensions[event], + this.realm._extensions[event] + ]; + + var ext = new Topo(); + ext.merge(sources); + return (ext.nodes.length ? ext : null); +}; + + internals.compileRule = function (rule) { // null, undefined, true - anything allowed diff --git a/test/plugin.js b/test/plugin.js index f1c17a066..d2ebdff50 100755 --- a/test/plugin.js +++ b/test/plugin.js @@ -3062,6 +3062,61 @@ describe('Plugin', function () { }); }); }); + + it('extends server actions (single call)', function (done) { + + var server = new Hapi.Server(); + server.connection(); + + var result = ''; + server.ext([ + { + type: 'onPreStart', + method: function (srv, next) { + + result += '1'; + return next(); + } + }, + { + type: 'onPostStart', + method: function (srv, next) { + + result += '2'; + return next(); + } + }, + { + type: 'onPreStop', + method: function (srv, next) { + + result += '3'; + return next(); + } + }, + { + type: 'onPreStop', + method: function (srv, next) { + + result += '4'; + return next(); + } + } + ]); + + server.start(function (err) { + + expect(err).to.not.exist(); + expect(result).to.equal('12'); + + server.stop(function (err) { + + expect(err).to.not.exist(); + expect(result).to.equal('1234'); + done(); + }); + }); + }); }); describe('handler()', function () {