Skip to content

Commit

Permalink
Merge pull request hapijs#901 from spumko/perf
Browse files Browse the repository at this point in the history
Performance tweaks
  • Loading branch information
geek committed May 30, 2013
2 parents 3403953 + 6605fc3 commit 606b6ce
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 246 deletions.
19 changes: 10 additions & 9 deletions lib/payload.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,18 @@ internals.validGzip = new Buffer(['0x1f', '0x8b']);

exports.read = function (request, next) {

if (request.method === 'get' ||
request.method === 'head') {

return next();
}

// Levels are: 'stream', 'raw', 'parse', 'try'

var level = request.route.payload.mode || (request.route.validate.payload || request.method === 'post' || request.method === 'put' || request.method === 'patch' ? 'parse' : 'stream');
if (level === 'stream') {
return next();
var level = request.route.payload.mode; // Will be null when not explicitly set and route method is '*'
if (!level) {
if (request.method === 'get' || request.method === 'head') {
return next();
}

level = request.route.payload.mode || (request.route.validate.payload || ['post', 'put', 'patch'].indexOf(request.method) !== -1 ? 'parse' : 'stream');
if (level === 'stream') {
return next();
}
}

// Check content size
Expand Down
105 changes: 40 additions & 65 deletions lib/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ var Url = require('url');
var Async = require('async');
var Boom = require('boom');
var Utils = require('./utils');
var Payload = require('./payload');
var State = require('./state');
var Auth = require('./auth');
var Validation = require('./validation');
var Response = require('./response');
var Cached = require('./response/cached');
var Closed = require('./response/closed');
Expand All @@ -33,22 +29,34 @@ exports = module.exports = internals.Request = function (server, req, res, optio
// Public members

this.server = server;
this._setUrl(req.url); // Sets: this.url, this.path, this.query
this._setMethod(req.method); // Sets: this.method

this.url = null;
this.query = null;
this.path = null;
this.method = null;

this.setUrl = this._setUrl; // Decoration removed after 'onRequest'
this.setMethod = this._setMethod;

this.setUrl(req.url); // Sets: this.url, this.path, this.query
this.setMethod(req.method); // Sets: this.method

this.id = now + '-' + process.pid + '-' + Math.floor(Math.random() * 0x10000);

this.app = {}; // Place for application-specific state without conflicts with hapi, should not be used by plugins
this.plugins = {}; // Place for plugins to store state without conflicts with hapi, should be namespaced using plugin name
this.route = {};
this.app = {}; // Place for application-specific state without conflicts with hapi, should not be used by plugins
this.plugins = {}; // Place for plugins to store state without conflicts with hapi, should be namespaced using plugin name

this._route = this.server._router.notfound; // Used prior to routing (only settings are used, not the handler)
this.route = this._route.settings;

this.auth = {
isAuthenticated: false,
credentials: null, // Special keys: 'app', 'user', 'scope', 'tos'
artifacts: null // Scheme-specific artifacts
// session: { set(), clear() }
credentials: null, // Special keys: 'app', 'user', 'scope', 'tos'
artifacts: null, // Scheme-specific artifacts
session: null // Used by cookie auth { set(), clear() }
};

this.session = null; // Special key reserved for plugins implementing session support
this.session = null; // Special key reserved for plugins implementing session support

this.pre = {};
this.info = {
Expand All @@ -69,17 +77,14 @@ exports = module.exports = internals.Request = function (server, req, res, optio

// Defined elsewhere:

// this.query
// this.params
// this.rawPayload
// this.payload
// this.state

// this.setUrl()
// this.setMethod()
this.params = null;
this.rawPayload = null;
this.payload = null;
this.state = null;
this.jsonp = null;

// this.reply(): { hold(), send(), close(), raw(), payload(), stream(), redirect(), view() }
// this.response()
this.reply = null; // this.reply(): { hold(), send(), close(), raw(), payload(), stream(), redirect(), view() }
this.response = null; // this.response()

// Semi-public members

Expand Down Expand Up @@ -239,11 +244,6 @@ internals.Request.prototype._execute = function () {

var self = this;

// Decorate request

this.setUrl = this._setUrl;
this.setMethod = this._setMethod;

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

this.server._ext.invoke(this, 'onRequest', function (err) {
Expand All @@ -254,15 +254,11 @@ internals.Request.prototype._execute = function () {
delete self.setMethod;

if (err) {
self._route = self.server._router.notfound; // Only settings are used, not the handler
self.route = self._route.settings;
self._reply(err);
return;
}

if (self.path[0] !== '/') {
self._route = self.server._router.notfound; // Only settings are used, not the handler
self.route = self._route.settings;
self._reply(Boom.badRequest('Invalid path'));
return;
}
Expand All @@ -272,6 +268,8 @@ internals.Request.prototype._execute = function () {
self._route = self.server._router.route(self);
self.route = self._route.settings;

// Setup timer

var serverTimeout = self.server.settings.timeout.server;
if (serverTimeout) {
serverTimeout -= (Date.now() - self._timestamp); // Calculate the timeout from when the request was constructed
Expand All @@ -287,26 +285,7 @@ internals.Request.prototype._execute = function () {
self._serverTimeoutId = setTimeout(timeoutReply, serverTimeout);
}

var funcs = [
// 'onRequest' above
State.parseCookies,
'onPreAuth',
Auth.authenticate, // Authenticates the raw.req object
Payload.read,
Auth.authenticatePayload,
'onPostAuth',
Validation.path,
internals.queryExtensions,
Validation.query,
Validation.payload,
'onPreHandler',
internals.handler, // Must not call next() with an Error
'onPostHandler', // An error from here on will override any result set in handler()
Validation.response
// 'onPreResponse' in _reply // Always called
];

Async.forEachSeries(funcs, function (func, next) {
Async.forEachSeries(self._route.cycle, function (func, next) {

if (self._isReplied) {
self.log(['hapi', 'server', 'timeout']);
Expand Down Expand Up @@ -415,20 +394,16 @@ internals.Request.prototype._reply = function (exit) {
};


internals.queryExtensions = function (request, next) {

// JSONP
internals.Request.parseJSONP = function (request, next) {

if (request.route.jsonp) {
var jsonp = request.query[request.route.jsonp];
if (jsonp) {
if (!jsonp.match(/^[\w\$\[\]\.]+$/)) {
return next(Boom.badRequest('Invalid JSONP parameter value'));
}

request.jsonp = jsonp;
delete request.query[request.route.jsonp];
var jsonp = request.query[request.route.jsonp];
if (jsonp) {
if (!jsonp.match(/^[\w\$\[\]\.]+$/)) {
return next(Boom.badRequest('Invalid JSONP parameter value'));
}

request.jsonp = jsonp;
delete request.query[request.route.jsonp];
}

return next();
Expand Down Expand Up @@ -474,7 +449,7 @@ internals.Request.bindPre = function (pre) {
};


internals.handler = function (request, next) {
internals.Request.handler = function (request, next) {

var check = function () {

Expand Down
98 changes: 95 additions & 3 deletions lib/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ var Schema = require('./schema');
var Utils = require('./utils');
var Views = require('./views');
var Request = require('./request');
var State = require('./state');
var Auth = require('./auth');
var Payload = require('./payload');
var Validation = require('./validation');


// Declare internals
Expand Down Expand Up @@ -49,16 +53,22 @@ exports = module.exports = internals.Route = function (options, server, env) {
this.settings.plugins = this.settings.plugins || {}; // Route-specific plugins settings, namespaced using plugin name
this.settings.app = this.settings.app || {}; // Route-specific application settings

// Payload configuration ('stream', 'raw', 'parse')
// Default is 'parse' for POST and PUT otherwise 'stream'
// Payload parsing

this.settings.validate = this.settings.validate || {};

if (!this.settings.payload ||
typeof this.settings.payload !== 'object') {

this.settings.payload = { mode: this.settings.payload };
}

this.settings.validate = this.settings.validate || {};
if (!this.settings.payload.mode &&
this.settings.method !== '*') {

this.settings.payload.mode = (this.settings.validate.payload || ['post', 'put', 'patch'].indexOf(this.settings.method) !== -1 ? 'parse' : 'stream');
}

Utils.assert(!this.settings.validate.payload || !this.settings.payload.mode || this.settings.payload.mode === 'parse', 'Route payload must be set to \'parse\' when payload validation enabled');
Utils.assert(!this.settings.jsonp || typeof this.settings.jsonp === 'string', 'Bad route JSONP parameter name');

Expand Down Expand Up @@ -177,6 +187,88 @@ exports = module.exports = internals.Route = function (options, server, env) {
else if (this.settings.handler === 'notFound') {
this.settings.handler = internals.notFound();
}

// Route lifecycle

this.cycle = this.lifecycle();
};


internals.Route.prototype.lifecycle = function () {

var self = this;

var cycle = [];

// Lifecycle flags

var parsePayload = (!this.settings.payload.mode || (this.method !== 'get' && this.method !== 'head' && this.settings.payload.mode !== 'stream'));
var authenticate = (this.settings.auth !== false); // Anything other than 'false' can still require authentication

var validate = function (type) {

// null, undefined, true - anything allowed
// false, {} - nothing allowed
// {...} - ... allowed

return self.settings.validate[type] !== null &&
self.settings.validate[type] !== undefined &&
self.settings.validate[type] !== true;
};

// 'onRequest'

if (this.server.settings.state.cookies.parse) {
cycle.push(State.parseCookies);
}

cycle.push('onPreAuth');

if (authenticate) {
cycle.push(Auth.authenticate);
}

if (parsePayload) {
cycle.push(Payload.read);
if (authenticate) {
cycle.push(Auth.authenticatePayload);
}
}

cycle.push('onPostAuth');

if (validate('path')) {
cycle.push(Validation.path);
}

if (this.settings.jsonp) {
cycle.push(Request.parseJSONP);
}

if (validate('query')) {
cycle.push(Validation.query);
}

if (parsePayload &&
validate('payload')) {

cycle.push(Validation.payload);
}

cycle.push('onPreHandler');
cycle.push(Request.handler); // Must not call next() with an Error
cycle.push('onPostHandler'); // An error from here on will override any result set in handler()

if (validate('response') &&
this.settings.validate.response.sample !== 0 &&
this.settings.validate.response.sample !== false) {

cycle.push(Validation.response);
}

// 'onPreResponse'

return cycle;
};


Expand Down
4 changes: 0 additions & 4 deletions lib/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ exports.parseCookies = function (request, next) {

var prepare = function () {

if (!request.server.settings.state.cookies.parse) {
return next();
}

request.state = {};

var req = request.raw.req;
Expand Down
Loading

0 comments on commit 606b6ce

Please sign in to comment.