Skip to content

Commit

Permalink
Route level and plugin level extensions. Closes #2566
Browse files Browse the repository at this point in the history
  • Loading branch information
hueniverse committed Oct 5, 2015
1 parent 6f8374f commit ae2c280
Show file tree
Hide file tree
Showing 13 changed files with 509 additions and 277 deletions.
10 changes: 10 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,11 @@ Registers an extension function in one of the available extension points where:
- `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.
- `sandbox` - if set to `'plugin'` when adding a [request extension points](#request-lifecycle)
the extension is only added to routes defined by the current plugin. Not allowed when
configuring route-level extensions, or when adding server extensions. Defaults to
`'connection'` which applies to any route added to the connection the extension is added
to.

```js
var Hapi = require('hapi');
Expand Down Expand Up @@ -2150,6 +2155,11 @@ following options:
response is sent. If set to `'merge'`, appends the configured values to the manually set
headers. Defaults to `true`.

- `ext` - defined a route-level [request extension points](#request-lifecycle) by setting
the option to an object with a key for each of the desired extension points (`'onRequest'`
is not allowed), and the value is the same as the [`server.ext(events)`](#serverextevents)
`event` argument.

- <a name="route.config.files"></a>`files` - defines the behavior for accessing files:
- `relativeTo` - determines the folder relative paths are resolved against.

Expand Down
2 changes: 1 addition & 1 deletion lib/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ internals.Auth.prototype.strategy = function (name, scheme /*, mode, options */)

internals.Auth.prototype.default = function (options) {

options = Schema.assert('auth', options, 'default strategy');
options = Schema.apply('auth', options, 'default strategy');
Hoek.assert(!this.settings.default, 'Cannot set default strategy more than once');

var settings = Hoek.clone(options); // options can be reused
Expand Down
47 changes: 16 additions & 31 deletions lib/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ var Call = require('call');
var Hoek = require('hoek');
var Shot = require('shot');
var Statehood = require('statehood');
var Topo = require('topo');
var Auth = require('./auth');
var Ext = require('./ext');
var Route = require('./route');


Expand Down Expand Up @@ -65,20 +65,19 @@ exports = module.exports = internals.Connection = function (server, options) {
this.registrations = {}; // Tracks plugin for dependency validation { name -> { version } }

this._extensions = {
onRequest: null, // New request, before handing over to the router (allows changes to the request method, url, etc.)
onPreAuth: null, // After cookie parse and before authentication (skipped if state error)
onPostAuth: null, // After authentication (and payload processing) and before validation (skipped if auth or payload error)
onPreHandler: null, // After validation and body parsing, before route handler (skipped if auth or validation error)
onPostHandler: null, // After route handler returns, before sending response (skipped if onPreHandler not called)
onPreResponse: null // Before response is sent (always called)
onRequest: new Ext(this.server),
onPreAuth: new Ext(this.server),
onPostAuth: new Ext(this.server),
onPreHandler: new Ext(this.server),
onPostHandler: new Ext(this.server),
onPreResponse: new Ext(this.server)
};

this._requestCounter = { value: internals.counter.min, min: internals.counter.min, max: internals.counter.max };
this._load = server._heavy.policy(this.settings.load);
this.states = new Statehood.Definitions(this.settings.state);
this.auth = new Auth(this);
this._router = new Call.Router(this.settings.router);
this._routes = []; // An array of all route objects
this._defaultRoutes();

this.plugins = {}; // Registered plugin APIs by plugin name
Expand Down Expand Up @@ -326,20 +325,11 @@ internals.Connection.prototype.match = function (method, path, host) {
};


internals.Connection.prototype._ext = function (event, nodes, options) {
internals.Connection.prototype._ext = function (event) {

Hoek.assert(this._extensions[event] !== undefined, 'Unknown event type', event);

this._extensions[event] = this._extensions[event] || new Topo();
this._extensions[event].add(nodes, options);

if (event === 'onRequest') {
return;
}

for (var i = 0, il = this._routes.length; i < il; ++i) {
this._routes[i].rebuild(event, nodes, options);
}
var type = event.type;
Hoek.assert(this._extensions[type], 'Unknown event type', type);
this._extensions[type].add(event);
};


Expand All @@ -365,9 +355,9 @@ internals.Connection.prototype._route = function (configs, realm) {
};


internals.Connection.prototype._addRoute = function (config, realm) {
internals.Connection.prototype._addRoute = function (config, plugin) {

var route = new Route(config, this, realm); // Do no use config beyond this point, use route members
var route = new Route(config, this, plugin); // Do no use config beyond this point, use route members
var vhosts = [].concat(route.settings.vhost || '*');

for (var i = 0, il = vhosts.length; i < il; ++i) {
Expand All @@ -376,8 +366,6 @@ internals.Connection.prototype._addRoute = function (config, realm) {
route.fingerprint = record.fingerprint;
route.params = record.params;
}

this._routes.push(route);
};


Expand All @@ -393,10 +381,9 @@ internals.Connection.prototype._defaultRoutes = function () {
return reply(Boom.notFound());
}
}
}, this, this.server.realm);
}, this, this.server);

this._router.special('notFound', notFound);
this._routes.push(notFound);

var badRequest = new Route({
method: 'badRequest',
Expand All @@ -408,10 +395,9 @@ internals.Connection.prototype._defaultRoutes = function () {
return reply(Boom.badRequest());
}
}
}, this, this.server.realm);
}, this, this.server);

this._router.special('badRequest', badRequest);
this._routes.push(badRequest);

if (this.settings.routes.cors) {
var optionsRoute = new Route({
Expand All @@ -425,9 +411,8 @@ internals.Connection.prototype._defaultRoutes = function () {
return reply();
}
}
}, this, this.server.realm);
}, this, this.server);

this._router.special('options', optionsRoute);
this._routes.push(optionsRoute);
}
};
68 changes: 68 additions & 0 deletions lib/ext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Load modules

var Topo = require('topo');


// Declare internals

var internals = {};


exports = module.exports = internals.Ext = function (server) {

this._topo = new Topo();
this._server = server;
this._routes = [];

this.nodes = null;
};


internals.Ext.prototype.add = function (event) {

var methods = [].concat(event.method);
var options = event.options;

for (var i = 0, il = methods.length; i < il; ++i) {
var settings = {
before: options.before,
after: options.after,
group: event.plugin.realm.plugin,
sort: this._server._extensionsSeq++
};

var node = {
func: methods[i], // Connection: function (request, next), Server: function (server, next)
bind: options.bind,
plugin: event.plugin
};

this._topo.add(node, settings);
}

this.nodes = this._topo.nodes;

// Notify routes

for (i = 0, il = this._routes.length; i < il; ++i) {
this._routes[i].rebuild(event);
}
};


internals.Ext.prototype.merge = function (others) {

var merge = [];
for (var i = 0, il = others.length; i < il; ++i) {
merge.push(others[i]._topo);
}

this._topo.merge(merge);
this.nodes = (this._topo.nodes.length ? this._topo.nodes : null);
};


internals.Ext.prototype.subscribe = function (route) {

this._routes.push(route);
};
4 changes: 2 additions & 2 deletions lib/methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ internals.Methods.prototype.add = function (name, method, options, realm) {
var items = [].concat(name);
for (var i = 0, il = items.length; i < il; ++i) {
var item = items[i];
item = Schema.assert('methodObject', item);
item = Schema.apply('methodObject', item);
this._add(item.name, item.method, item.options, realm);
}
};
Expand All @@ -45,7 +45,7 @@ internals.Methods.prototype._add = function (name, method, options, realm) {
Hoek.assert(name.match(exports.methodNameRx), 'Invalid name:', name);
Hoek.assert(!Hoek.reach(this.methods, name, { functions: false }), 'Server method function name already exists:', name);

options = Schema.assert('method', options || {}, name);
options = Schema.apply('method', options || {}, name);

var settings = Hoek.cloneWithShallow(options, ['bind']);
settings.generateKey = settings.generateKey || internals.generateKey;
Expand Down
58 changes: 24 additions & 34 deletions lib/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ var Catbox = require('catbox');
var Hoek = require('hoek');
var Items = require('items');
var Kilt = require('kilt');
var Topo = require('topo');
var Connection = require('./connection');
var Ext = require('./ext');
var Package = require('../package.json');
var Schema = require('./schema');

Expand Down Expand Up @@ -37,11 +37,11 @@ exports = module.exports = internals.Plugin = function (server, connections, env

this.realm = typeof env !== 'string' ? env : {
_extensions: {
onPreAuth: null,
onPostAuth: null,
onPreHandler: null,
onPostHandler: null,
onPreResponse: null
onPreAuth: new Ext(this.root),
onPostAuth: new Ext(this.root),
onPreHandler: new Ext(this.root),
onPostHandler: new Ext(this.root),
onPreResponse: new Ext(this.root)
},
modifiers: {
route: {}
Expand Down Expand Up @@ -180,7 +180,7 @@ internals.Plugin.prototype.register = function (plugins /*, [options], callback
options.routes.vhost = this.realm.modifiers.route.vhost || options.routes.vhost;
}

options = Schema.assert('register', options);
options = Schema.apply('register', options);

/*
var register = function (server, options, next) { return next(); };
Expand Down Expand Up @@ -226,7 +226,7 @@ internals.Plugin.prototype.register = function (plugins /*, [options], callback
plugin.register = plugin.register.register;
}

plugin = Schema.assert('plugin', plugin);
plugin = Schema.apply('plugin', plugin);

var attributes = plugin.register.attributes;
var registration = {
Expand Down Expand Up @@ -345,7 +345,7 @@ internals.Plugin.prototype.bind = function (context) {

internals.Plugin.prototype.cache = function (options, _segment) {

options = Schema.assert('cachePolicy', options);
options = Schema.apply('cachePolicy', options);

var segment = options.segment || _segment || (this.realm.plugin ? '!' + this.realm.plugin : '');
Hoek.assert(segment, 'Missing cache segment name');
Expand Down Expand Up @@ -431,7 +431,8 @@ internals.Plugin.prototype.ext = function (events) { // (event, method, o
events = { type: arguments[0], method: arguments[1], options: arguments[2] };
}

events = [].concat(events);
events = Schema.apply('exts', events);

for (var i = 0, il = events.length; i < il; ++i) {
this._ext(events[i]);
}
Expand All @@ -440,40 +441,29 @@ internals.Plugin.prototype.ext = function (events) { // (event, method, o

internals.Plugin.prototype._ext = function (event) {

event = Hoek.shallow(event);
event.plugin = this;
var type = event.type;
var methods = [].concat(event.method);
var options = event.options || {};

var settings = {
before: options.before,
after: options.after,
group: this.realm.plugin
};
if (!this.root._extensions[type]) {

var nodes = [];
for (var i = 0, il = methods.length; i < il; ++i) {
var node = {
func: methods[i], // Connection: function (request, next), Server: function (server, next)
realm: this.realm,
bind: options.bind,
plugin: this
};
// Realm route extensions

nodes.push(node);
}
if (event.options.sandbox === 'plugin') {
Hoek.assert(this.realm._extensions[type], 'Unknown event type', type);
return this.realm._extensions[type].add(event);
}

// Connection extensions
// Connection route extensions

if (this.root._extensions[type] === undefined) {
return this._apply('ext', Connection.prototype._ext, [type, nodes, settings]);
return this._apply('ext', Connection.prototype._ext, [event]);
}

// Server extensions

Hoek.assert(!event.options.sandbox, 'Cannot specify sandbox option for server extension');
Hoek.assert(type !== 'onPreStart' || this.root._state === 'stopped', 'Cannot add onPreStart (after) extension after the server was initialized');

this.root._extensions[type] = this.root._extensions[type] || new Topo();
this.root._extensions[type].add(nodes, settings);
this.root._extensions[type].add(event);
};


Expand Down Expand Up @@ -555,7 +545,7 @@ internals.Plugin.prototype.route = function (options) {
Hoek.assert(this.connections, 'Cannot add route from a connectionless plugin');
Hoek.assert(this.connections.length, 'Cannot add a route without any connections');

this._apply('route', Connection.prototype._route, [options, this.realm]);
this._apply('route', Connection.prototype._route, [options, this]);
};


Expand Down
8 changes: 4 additions & 4 deletions lib/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ internals.Request.prototype._execute = function () {

// Execute onRequest extensions (can change request method and url)

if (!this.connection._extensions.onRequest) {
if (!this.connection._extensions.onRequest.nodes) {
return this._lifecycle();
}

Expand Down Expand Up @@ -403,8 +403,8 @@ internals.Request.prototype._invoke = function (event, callback) {

Items.serial(event.nodes, function (ext, next) {

var reply = self.server._replier.interface(self, ext.realm, next);
var bind = (ext.bind || ext.realm.settings.bind);
var reply = self.server._replier.interface(self, ext.plugin.realm, next);
var bind = (ext.bind || ext.plugin.realm.settings.bind);

ext.func.call(bind, self, reply);
}, exit);
Expand Down Expand Up @@ -456,7 +456,7 @@ internals.Request.prototype._reply = function (exit) {
});
};

if (!this._route._extensions.onPreResponse) {
if (!this._route._extensions.onPreResponse.nodes) {
return transmit();
}

Expand Down
Loading

0 comments on commit ae2c280

Please sign in to comment.