Comparing version 17.0.2 to 17.1.0
@@ -274,8 +274,15 @@ 'use strict'; | ||
internals.handler = Joi.alternatives([ | ||
Joi.func(), | ||
Joi.object().length(1) | ||
]); | ||
internals.route = Joi.object({ | ||
method: Joi.string().regex(/^[a-zA-Z0-9!#\$%&'\*\+\-\.^_`\|~]+$/).required(), | ||
path: Joi.string().required(), | ||
rules: Joi.object(), | ||
vhost: internals.vhost, | ||
// Validated in routeConfig | ||
// Validated in route construction | ||
@@ -301,10 +308,5 @@ handler: Joi.any(), | ||
internals.routeConfig = internals.routeBase.keys({ | ||
description: Joi.string(), | ||
id: Joi.string(), | ||
isInternal: Joi.boolean(), | ||
pre: Joi.array().items(internals.pre.concat(Joi.array().items(internals.pre).min(1))), | ||
handler: [ | ||
Joi.func(), | ||
Joi.object().length(1) | ||
], | ||
description: Joi.string(), | ||
notes: [ | ||
@@ -314,2 +316,3 @@ Joi.string(), | ||
], | ||
pre: Joi.array().items(internals.pre.concat(Joi.array().items(internals.pre).min(1))), | ||
tags: [ | ||
@@ -389,1 +392,10 @@ Joi.string(), | ||
.unknown(); | ||
internals.rules = Joi.object({ | ||
validate: Joi.object({ | ||
schema: Joi.alternatives(Joi.object(), Joi.array()).required(), | ||
options: Joi.object() | ||
.default({ allowUnknown: true }) | ||
}) | ||
}); |
@@ -65,6 +65,5 @@ 'use strict'; | ||
this.compression = new Compression(); | ||
this.decorations = { handler: [], request: [], server: [], toolkit: [] }; | ||
this.decorations = { handler: [], request: [], server: [], toolkit: [] }; // Public decoration names | ||
this.dependencies = []; // Plugin dependencies | ||
this.events = new Podium(internals.events); | ||
this.handlers = {}; // Registered handlers | ||
this.heavy = new Heavy(this.settings.load); | ||
@@ -75,3 +74,2 @@ this.instances = new Set(); | ||
this.registrations = {}; // Tracks plugin for dependency validation { name -> { version } } | ||
this.requestor = new Request(); | ||
this.onConnection = null; // Used to remove event listener on stop | ||
@@ -84,3 +82,2 @@ this.plugins = {}; // Exposed plugin properties by name | ||
this.phase = 'stopped'; // 'stopped', 'initializing', 'initialized', 'starting', 'started', 'stopping', 'invalid' | ||
this.serverDecorations = {}; | ||
this.sockets = new Set(); // Track open sockets for graceful shutdown | ||
@@ -111,2 +108,3 @@ this.started = false; | ||
this._debug(); | ||
this._decorations = { handler: {}, request: {}, server: {}, toolkit: {}, requestApply: null }; | ||
this._initializeCache(); | ||
@@ -445,3 +443,3 @@ | ||
const request = this.requestor.request(this.root, req, res, options); | ||
const request = Request.generate(this.root, req, res, options); | ||
@@ -448,0 +446,0 @@ // Check load |
@@ -93,3 +93,3 @@ 'use strict'; | ||
const type = Object.keys(handler)[0]; | ||
const serverHandler = core.handlers[type]; | ||
const serverHandler = core._decorations.handler[type]; | ||
@@ -111,3 +111,3 @@ Hoek.assert(serverHandler, 'Unknown handler:', type); | ||
const type = Object.keys(handler)[0]; | ||
const serverHandler = route._core.handlers[type]; | ||
const serverHandler = route._core._decorations.handler[type]; | ||
@@ -114,0 +114,0 @@ Hoek.assert(serverHandler, 'Unknown handler:', type); |
@@ -20,44 +20,9 @@ 'use strict'; | ||
const internals = { | ||
properties: ['server', 'url', 'query', 'path', 'method', 'mime', 'setUrl', 'setMethod', 'headers', 'id', 'app', 'plugins', 'route', 'auth', 'pre', 'preResponses', 'info', 'orig', 'params', 'paramsArray', 'payload', 'state', 'jsonp', 'response', 'raw', 'domain', 'log', 'logs', 'generateResponse'], | ||
events: Podium.validate(['finish', { name: 'peek', spread: true }, 'disconnect']) | ||
events: Podium.validate(['finish', { name: 'peek', spread: true }, 'disconnect']), | ||
reserved: ['server', 'url', 'query', 'path', 'method', 'mime', 'setUrl', 'setMethod', 'headers', 'id', 'app', 'plugins', 'route', 'auth', 'pre', 'preResponses', 'info', 'orig', 'params', 'paramsArray', 'payload', 'state', 'jsonp', 'response', 'raw', 'domain', 'log', 'logs', 'generateResponse'] | ||
}; | ||
exports = module.exports = internals.Generator = class { | ||
exports = module.exports = internals.Request = class { | ||
constructor() { | ||
this._decorations = null; | ||
} | ||
request(server, req, res, options) { | ||
const request = new internals.Request(server, req, res, options); | ||
// Decorate | ||
if (this._decorations) { | ||
const properties = Object.keys(this._decorations); | ||
for (let i = 0; i < properties.length; ++i) { | ||
const property = properties[i]; | ||
const assignment = this._decorations[property]; | ||
request[property] = (assignment.apply ? assignment.method(request) : assignment.method); | ||
} | ||
} | ||
return request; | ||
} | ||
decorate(property, method, options) { | ||
Hoek.assert(internals.properties.indexOf(property) === -1, 'Cannot override built-in request interface decoration:', property); | ||
this._decorations = this._decorations || {}; | ||
this._decorations[property] = { method, apply: options.apply }; | ||
} | ||
}; | ||
internals.Request = class { | ||
constructor(server, req, res, options) { | ||
@@ -116,2 +81,20 @@ | ||
static generate(server, req, res, options) { | ||
const request = new internals.Request(server, req, res, options); | ||
// Decorate | ||
if (server._core._decorations.requestApply) { | ||
const properties = Object.keys(server._core._decorations.requestApply); | ||
for (let i = 0; i < properties.length; ++i) { | ||
const property = properties[i]; | ||
const assignment = server._core._decorations.requestApply[property]; | ||
request[property] = assignment(request); | ||
} | ||
} | ||
return request; | ||
} | ||
get events() { | ||
@@ -497,2 +480,5 @@ | ||
internals.Request.reserved = internals.reserved; | ||
internals.info = function (core, req) { | ||
@@ -499,0 +485,0 @@ |
120
lib/route.js
@@ -9,2 +9,3 @@ 'use strict'; | ||
const Hoek = require('hoek'); | ||
const Joi = require('joi'); | ||
const Subtext = require('subtext'); | ||
@@ -35,18 +36,18 @@ | ||
// Apply plugin environment (before schema validation) | ||
// Routing information | ||
if (realm.modifiers.route.vhost || | ||
realm.modifiers.route.prefix) { | ||
const display = `${route.method} ${route.path}`; | ||
Config.apply('route', route, display); | ||
route = Hoek.cloneWithShallow(route, ['options']); // options is left unchanged | ||
route.path = (realm.modifiers.route.prefix ? realm.modifiers.route.prefix + (route.path !== '/' ? route.path : '') : route.path); | ||
route.vhost = realm.modifiers.route.vhost || route.vhost; | ||
} | ||
const method = route.method.toLowerCase(); | ||
Hoek.assert(method !== 'head', 'Method name not allowed:', display); | ||
// Setup and validate route configuration | ||
const path = (realm.modifiers.route.prefix ? realm.modifiers.route.prefix + (route.path !== '/' ? route.path : '') : route.path); | ||
Hoek.assert(path === '/' || path[path.length - 1] !== '/' || !core.settings.router.stripTrailingSlash, 'Path cannot end with a trailing slash when configured to strip:', display); | ||
Hoek.assert(route.path, 'Route missing path'); | ||
const routeDisplay = route.method + ' ' + route.path; | ||
const vhost = (realm.modifiers.route.vhost || route.vhost); | ||
let config = route.options || route.config; | ||
// Prepare configuration | ||
let config = route.options || route.config || {}; | ||
if (typeof config === 'function') { | ||
@@ -56,32 +57,40 @@ config = config.call(realm.settings.bind, server); | ||
Hoek.assert(route.handler || (config && config.handler), 'Missing or undefined handler:', routeDisplay); | ||
Hoek.assert(!!route.handler ^ !!(config && config.handler), 'Handler must only appear once:', routeDisplay); // XOR | ||
Hoek.assert(route.path === '/' || route.path[route.path.length - 1] !== '/' || !core.settings.router.stripTrailingSlash, 'Path cannot end with a trailing slash when configured to strip:', routeDisplay); | ||
config = Config.enable(config); // Shallow clone | ||
config = Config.enable(config); | ||
route = Config.apply('route', route, routeDisplay); | ||
// Rules | ||
const handler = route.handler || config.handler; | ||
const method = route.method.toLowerCase(); | ||
Hoek.assert(method !== 'head', 'Method name not allowed:', routeDisplay); | ||
Hoek.assert(!route.rules || !config.rules, 'Route rules can only appear once:', display); // XOR | ||
const rules = (route.rules || config.rules); | ||
const rulesConfig = internals.rules(rules, { method, path, vhost }, server); | ||
delete config.rules; | ||
// Apply settings in order: {server} <- {handler} <- {realm} <- {route} | ||
// Handler | ||
Hoek.assert(route.handler || config.handler, 'Missing or undefined handler:', display); | ||
Hoek.assert(!!route.handler ^ !!config.handler, 'Handler must only appear once:', display); // XOR | ||
const handler = Config.apply('handler', route.handler || config.handler); | ||
delete config.handler; | ||
const handlerDefaults = Handler.defaults(method, handler, core); | ||
let base = Hoek.applyToDefaultsWithShallow(core.settings.routes, handlerDefaults, ['bind']); | ||
base = Hoek.applyToDefaultsWithShallow(base, realm.settings, ['bind']); | ||
this.settings = Hoek.applyToDefaultsWithShallow(base, config, ['bind', 'validate.headers', 'validate.payload', 'validate.params', 'validate.query']); | ||
this.settings.handler = handler; | ||
this.settings = Config.apply('routeConfig', this.settings, routeDisplay); | ||
// Apply settings in order: server <- handler <- realm <- route | ||
const settings = internals.config([core.settings.routes, handlerDefaults, realm.settings, rulesConfig, config]); | ||
this.settings = Config.apply('routeConfig', settings, display); | ||
// Validate timeouts | ||
const socketTimeout = (this.settings.timeout.socket === undefined ? 2 * 60 * 1000 : this.settings.timeout.socket); | ||
Hoek.assert(!this.settings.timeout.server || !socketTimeout || this.settings.timeout.server < socketTimeout, 'Server timeout must be shorter than socket timeout:', routeDisplay); | ||
Hoek.assert(!this.settings.payload.timeout || !socketTimeout || this.settings.payload.timeout < socketTimeout, 'Payload timeout must be shorter than socket timeout:', routeDisplay); | ||
Hoek.assert(!this.settings.timeout.server || !socketTimeout || this.settings.timeout.server < socketTimeout, 'Server timeout must be shorter than socket timeout:', display); | ||
Hoek.assert(!this.settings.payload.timeout || !socketTimeout || this.settings.payload.timeout < socketTimeout, 'Payload timeout must be shorter than socket timeout:', display); | ||
// Route members | ||
this._core = core; | ||
this.path = route.path; | ||
this.path = path; | ||
this.method = method; | ||
this.realm = realm; | ||
this.settings.vhost = route.vhost; | ||
this.settings.vhost = vhost; | ||
this.settings.plugins = this.settings.plugins || {}; // Route-specific plugins settings, namespaced using plugin name | ||
@@ -100,3 +109,3 @@ this.settings.app = this.settings.app || {}; // Route-specific application settings | ||
path: this.path, | ||
vhost: this.vhost, | ||
vhost, | ||
realm, | ||
@@ -117,4 +126,4 @@ settings: this.settings, | ||
Hoek.assert(!config.payload, 'Cannot set payload settings on HEAD or GET request:', routeDisplay); | ||
Hoek.assert(!config.validate || !config.validate.payload, 'Cannot validate HEAD or GET requests:', routeDisplay); | ||
Hoek.assert(!config.payload, 'Cannot set payload settings on HEAD or GET request:', display); | ||
Hoek.assert(!config.validate || !config.validate.payload, 'Cannot validate HEAD or GET requests:', display); | ||
@@ -124,3 +133,3 @@ validation.payload = null; | ||
Hoek.assert(!validation.params || this.params.length, 'Cannot set path parameters validations without path parameters:', routeDisplay); | ||
Hoek.assert(!validation.params || this.params.length, 'Cannot set path parameters validations without path parameters:', display); | ||
@@ -164,8 +173,8 @@ ['headers', 'params', 'query', 'payload'].forEach((type) => { | ||
Hoek.assert(!this.settings.validate.payload || this.settings.payload.parse, 'Route payload must be set to \'parse\' when payload validation enabled:', routeDisplay); | ||
Hoek.assert(!this.settings.jsonp || typeof this.settings.jsonp === 'string', 'Bad route JSONP parameter name:', routeDisplay); | ||
Hoek.assert(!this.settings.validate.payload || this.settings.payload.parse, 'Route payload must be set to \'parse\' when payload validation enabled:', display); | ||
Hoek.assert(!this.settings.jsonp || typeof this.settings.jsonp === 'string', 'Bad route JSONP parameter name:', display); | ||
// Authentication configuration | ||
this.settings.auth = (this._special ? false : this._core.auth._setupRoute(this.settings.auth, route.path)); | ||
this.settings.auth = (this._special ? false : this._core.auth._setupRoute(this.settings.auth, path)); | ||
@@ -192,3 +201,3 @@ // Cache | ||
this.settings.handler = Handler.configure(this.settings.handler, this); | ||
this.settings.handler = Handler.configure(handler, this); | ||
this._prerequisites = Handler.prerequisitesConfig(this.settings.pre); | ||
@@ -454,1 +463,38 @@ | ||
}; | ||
internals.config = function (chain) { | ||
if (!chain.length) { | ||
return {}; | ||
} | ||
let config = chain[0]; | ||
for (let i = 1; i < chain.length; ++i) { | ||
config = Hoek.applyToDefaultsWithShallow(config, chain[i], ['bind', 'validate.headers', 'validate.payload', 'validate.params', 'validate.query']); | ||
} | ||
return config; | ||
}; | ||
internals.rules = function (rules, info, server) { | ||
const configs = []; | ||
let realm = server.realm; | ||
while (realm) { | ||
if (realm._rules) { | ||
const source = (!realm._rules.settings.validate ? rules : Joi.attempt(rules, realm._rules.settings.validate.schema, realm._rules.settings.validate.options)); | ||
const config = realm._rules.processor(source, info); | ||
if (config) { | ||
configs.unshift(config); | ||
} | ||
} | ||
realm = realm.parent; | ||
} | ||
return internals.config(configs); | ||
}; |
@@ -6,2 +6,3 @@ 'use strict'; | ||
const Hoek = require('hoek'); | ||
const Joi = require('joi'); | ||
const Shot = require('shot'); | ||
@@ -14,2 +15,3 @@ | ||
const Package = require('../package.json'); | ||
const Request = require('./request'); | ||
const Route = require('./route'); | ||
@@ -72,2 +74,3 @@ | ||
plugins: {}, | ||
_rules: null, | ||
settings: { | ||
@@ -83,6 +86,5 @@ bind: undefined, | ||
const methods = Object.keys(core.serverDecorations); | ||
for (let i = 0; i < methods.length; ++i) { | ||
const method = methods[i]; | ||
this[method] = core.serverDecorations[method]; | ||
for (let i = 0; i < core.decorations.server.length; ++i) { | ||
const method = core.decorations.server[i]; | ||
this[method] = core._decorations.server[method]; | ||
} | ||
@@ -115,4 +117,15 @@ | ||
Hoek.assert(property[0] !== '_', 'Property name cannot begin with an underscore:', property); | ||
Hoek.assert(this._core.decorations[type].indexOf(property) === -1, `${type[0].toUpperCase() + type.slice(1)} decoration already defined: ${property}`); | ||
const existing = this._core._decorations[type][property]; | ||
if (options.extend) { | ||
Hoek.assert(type !== 'handler', 'Cannot extent handler decoration:', property); | ||
Hoek.assert(existing, `Cannot extend missing ${type} decoration: ${property}`); | ||
Hoek.assert(typeof method === 'function', `Extended ${type} decoration method must be a function: ${property}`); | ||
method = method(existing); | ||
} | ||
else { | ||
Hoek.assert(existing === undefined, `${type[0].toUpperCase() + type.slice(1)} decoration already defined: ${property}`); | ||
} | ||
if (type === 'handler') { | ||
@@ -124,4 +137,3 @@ | ||
Hoek.assert(!method.defaults || typeof method.defaults === 'object' || typeof method.defaults === 'function', 'Handler defaults property must be an object or function'); | ||
this._core.handlers[property] = method; | ||
Hoek.assert(!options.extend, 'Cannot extend handler decoration:', property); | ||
} | ||
@@ -132,3 +144,11 @@ else if (type === 'request') { | ||
this._core.requestor.decorate(property, method, options); | ||
Hoek.assert(Request.reserved.indexOf(property) === -1, 'Cannot override built-in request interface decoration:', property); | ||
if (options.apply) { | ||
this._core._decorations.requestApply = this._core._decorations.requestApply || {}; | ||
this._core._decorations.requestApply[property] = method; | ||
} | ||
else { | ||
Request.prototype[property] = method; | ||
} | ||
} | ||
@@ -139,3 +159,3 @@ else if (type === 'toolkit') { | ||
this._core.toolkit.decorate(property, method); | ||
Hoek.assert(this._core.toolkit.reserved.indexOf(property) === -1, 'Cannot override built-in toolkit decoration:', property); | ||
} | ||
@@ -146,5 +166,4 @@ else { | ||
Hoek.assert(this[property] === undefined && this._core[property] === undefined, 'Cannot override the built-in server interface method:', property); | ||
Hoek.assert(Object.getOwnPropertyNames(internals.Server.prototype).indexOf(property) === -1, 'Cannot override the built-in server interface method:', property); | ||
this._core.serverDecorations[property] = method; | ||
this._core.instances.forEach((server) => { | ||
@@ -156,2 +175,3 @@ | ||
this._core._decorations[type][property] = method; | ||
this._core.decorations[type].push(property); | ||
@@ -348,3 +368,3 @@ } | ||
const items = [].concat(plugins); | ||
for (let i = 0; i < items.length; ++i){ | ||
for (let i = 0; i < items.length; ++i) { | ||
let item = items[i]; | ||
@@ -460,2 +480,15 @@ | ||
rules(processor, options = {}) { | ||
Hoek.assert(!this.realm._rules, 'Server realm rules already defined'); | ||
const settings = Config.apply('rules', options); | ||
if (settings.validate) { | ||
const schema = settings.validate.schema; | ||
settings.validate.schema = Joi.compile(schema); | ||
} | ||
this.realm._rules = { processor, settings }; | ||
} | ||
state(name, options) { | ||
@@ -462,0 +495,0 @@ |
@@ -14,3 +14,5 @@ 'use strict'; | ||
const internals = {}; | ||
const internals = { | ||
reserved: ['abandon', 'authenticated', 'close', 'context', 'continue', 'entity', 'redirect', 'realm', 'request', 'response', 'state', 'unauthenticated', 'unstate'] | ||
}; | ||
@@ -22,17 +24,8 @@ | ||
this._decorations = null; | ||
this.abandon = Symbol('abandon'); | ||
this.close = Symbol('close'); | ||
this.continue = Symbol('continue'); | ||
this.reserved = internals.reserved; | ||
} | ||
decorate(property, method) { | ||
Hoek.assert(['abandon', 'authenticated', 'close', 'context', 'continue', 'entity', 'redirect', 'realm', 'request', 'response', 'state', 'unauthenticated', 'unstate'].indexOf(property) === -1, 'Cannot override built-in toolkit decoration:', property); | ||
this._decorations = this._decorations || {}; | ||
this._decorations[property] = method; | ||
} | ||
async execute(method, request, options) { | ||
@@ -61,3 +54,3 @@ | ||
if (response === undefined) { | ||
response = Boom.badImplementation('Method did not return a value, a promise, or throw an error'); | ||
response = Boom.badImplementation(`${method.name} method did not return a value, a promise, or throw an error`); | ||
} | ||
@@ -135,3 +128,2 @@ | ||
this.abandon = manager.abandon; | ||
@@ -149,8 +141,5 @@ this.close = manager.close; | ||
if (manager._decorations) { | ||
const methods = Object.keys(manager._decorations); | ||
for (let i = 0; i < methods.length; ++i) { | ||
const method = methods[i]; | ||
this[method] = manager._decorations[method]; | ||
} | ||
for (let i = 0; i < request._core.decorations.toolkit.length; ++i) { | ||
const method = request._core.decorations.toolkit[i]; | ||
this[method] = request._core._decorations.toolkit[method]; | ||
} | ||
@@ -157,0 +146,0 @@ } |
@@ -5,3 +5,3 @@ { | ||
"homepage": "http://hapijs.com", | ||
"version": "17.0.2", | ||
"version": "17.1.0", | ||
"repository": { | ||
@@ -8,0 +8,0 @@ "type": "git", |
@@ -11,3 +11,3 @@ <img src="https://raw.github.com/hapijs/hapi/master/images/17.png" align="right"/> | ||
Development version: **17.0.x** ([release notes](https://github.com/hapijs/hapi/issues?labels=release+notes&page=1&state=closed)) | ||
Development version: **17.1.x** ([release notes](https://github.com/hapijs/hapi/issues?labels=release+notes&page=1&state=closed)) | ||
[![Build Status](https://secure.travis-ci.org/hapijs/hapi.svg?branch=master)](http://travis-ci.org/hapijs/hapi) | ||
@@ -14,0 +14,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
166808
4113