Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance tweaks #901

Merged
merged 5 commits into from
May 30, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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