Comparing version 14.2.0 to 15.0.1
124
lib/auth.js
@@ -90,8 +90,3 @@ 'use strict'; | ||
const transfer = (response, data) => { | ||
return next(response, data && data.credentials); | ||
}; | ||
const reply = request.server._replier.interface(request, strategy.realm, transfer); | ||
const reply = request.server._replier.interface(request, strategy.realm, { data: true }, (response) => next(response, reply._data && reply._data.credentials)); | ||
strategy.methods.authenticate(request, reply); | ||
@@ -138,3 +133,3 @@ }; | ||
const access = options.access[i]; | ||
access.scope = internals.scope(access); | ||
access.scope = internals.setupScope(access); | ||
} | ||
@@ -164,3 +159,3 @@ } | ||
internals.scope = function (access) { | ||
internals.setupScope = function (access) { | ||
@@ -209,2 +204,19 @@ if (!access.scope) { | ||
internals.Auth.access = function (request, route) { | ||
const auth = request.connection.auth; | ||
const config = auth.lookup(route); | ||
if (!config) { | ||
return true; | ||
} | ||
const credentials = request.auth.credentials; | ||
if (!credentials) { | ||
return false; | ||
} | ||
return !internals.access(request, config, credentials, 'bypass'); | ||
}; | ||
internals.Auth.prototype._authenticate = function (request, next) { | ||
@@ -257,3 +269,3 @@ | ||
const reply = request.server._replier.interface(request, strategy.realm, exit); | ||
const reply = request.server._replier.interface(request, strategy.realm, {}, exit); | ||
strategy.methods.payload(request, reply); | ||
@@ -282,3 +294,3 @@ }); | ||
const reply = request.server._replier.interface(request, strategy.realm, exit); | ||
const reply = request.server._replier.interface(request, strategy.realm, {}, exit); | ||
strategy.methods.response(request, reply); | ||
@@ -325,7 +337,7 @@ }); | ||
const name = config.strategies[this.current]; | ||
const after = (err, result) => this.validate(err, result, next); | ||
const after = (err, data) => this.validate(err, data, next); | ||
request._protect.run(after, (exit) => { | ||
const strategy = this.manager._strategies[name]; | ||
const reply = request.server._replier.interface(request, strategy.realm, exit); | ||
const reply = request.server._replier.interface(request, strategy.realm, { data: true }, (err) => exit(err, reply._data)); | ||
strategy.methods.authenticate(request, reply); | ||
@@ -417,61 +429,71 @@ }); | ||
if (!config.access) { | ||
const error = internals.access(request, config, credentials, name); | ||
if (!error) { | ||
return authenticated(); | ||
} | ||
const requestEntity = (credentials.user ? 'user' : 'app'); | ||
request._log(error.tags, error.data); | ||
return next(error.err); | ||
} | ||
}; | ||
const scopeErrors = []; | ||
for (let i = 0; i < config.access.length; ++i) { | ||
const access = config.access[i]; | ||
// Check entity | ||
internals.access = function (request, config, credentials, name) { | ||
const entity = access.entity; | ||
if (entity && | ||
entity !== 'any' && | ||
entity !== requestEntity) { | ||
if (!config.access) { | ||
return null; | ||
} | ||
continue; | ||
} | ||
const requestEntity = (credentials.user ? 'user' : 'app'); | ||
// Check scope | ||
const scopeErrors = []; | ||
for (let i = 0; i < config.access.length; ++i) { | ||
const access = config.access[i]; | ||
let scope = access.scope; | ||
if (scope) { | ||
if (!credentials.scope) { | ||
scopeErrors.push(scope); | ||
continue; | ||
} | ||
// Check entity | ||
scope = internals.expandScope(request, scope); | ||
if (!internals.validateScope(credentials, scope, 'required') || | ||
!internals.validateScope(credentials, scope, 'selection') || | ||
!internals.validateScope(credentials, scope, 'forbidden')) { | ||
const entity = access.entity; | ||
if (entity && | ||
entity !== 'any' && | ||
entity !== requestEntity) { | ||
scopeErrors.push(scope); | ||
continue; | ||
} | ||
continue; | ||
} | ||
// Check scope | ||
let scope = access.scope; | ||
if (scope) { | ||
if (!credentials.scope) { | ||
scopeErrors.push(scope); | ||
continue; | ||
} | ||
return authenticated(); | ||
scope = internals.expandScope(request, scope); | ||
if (!internals.validateScope(credentials, scope, 'required') || | ||
!internals.validateScope(credentials, scope, 'selection') || | ||
!internals.validateScope(credentials, scope, 'forbidden')) { | ||
scopeErrors.push(scope); | ||
continue; | ||
} | ||
} | ||
// Scope error | ||
return null; | ||
} | ||
if (scopeErrors.length) { | ||
request._log(['auth', 'scope', 'error', name], { got: credentials.scope, need: scopeErrors }); | ||
return next(Boom.forbidden('Insufficient scope')); | ||
} | ||
// Scope error | ||
// Entity error | ||
if (scopeErrors.length) { | ||
const data = { got: credentials.scope, need: scopeErrors }; | ||
return { err: Boom.forbidden('Insufficient scope', data), tags: ['auth', 'scope', 'error', name], data }; | ||
} | ||
if (requestEntity === 'app') { | ||
request._log(['auth', 'entity', 'user', 'error', name]); | ||
return next(Boom.forbidden('Application credentials cannot be used on a user endpoint')); | ||
} | ||
// Entity error | ||
request._log(['auth', 'entity', 'app', 'error', name]); | ||
return next(Boom.forbidden('User credentials cannot be used on an application endpoint')); | ||
if (requestEntity === 'app') { | ||
return { err: Boom.forbidden('Application credentials cannot be used on a user endpoint'), tags: ['auth', 'entity', 'user', 'error', name] }; | ||
} | ||
return { err: Boom.forbidden('User credentials cannot be used on an application endpoint'), tags: ['auth', 'entity', 'app', 'error', name] }; | ||
}; | ||
@@ -478,0 +500,0 @@ |
@@ -5,3 +5,2 @@ 'use strict'; | ||
const Events = require('events'); | ||
const Http = require('http'); | ||
@@ -14,5 +13,7 @@ const Https = require('https'); | ||
const Hoek = require('hoek'); | ||
const Podium = require('podium'); | ||
const Shot = require('shot'); | ||
const Statehood = require('statehood'); | ||
const Auth = require('./auth'); | ||
const Compression = require('./compression'); | ||
const Cors = require('./cors'); | ||
@@ -38,3 +39,3 @@ const Ext = require('./ext'); | ||
Events.EventEmitter.call(this); | ||
Podium.call(this, internals.Connection._events); | ||
@@ -71,8 +72,8 @@ this.settings = options; // options cloned in server.connection() | ||
this._extensions = { | ||
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) | ||
onRequest: new Ext('onRequest', this.server), | ||
onPreAuth: new Ext('onPreAuth', this.server), | ||
onPostAuth: new Ext('onPostAuth', this.server), | ||
onPreHandler: new Ext('onPreHandler', this.server), | ||
onPostHandler: new Ext('onPostHandler', this.server), | ||
onPreResponse: new Ext('onPreResponse', this.server) | ||
}; | ||
@@ -82,2 +83,3 @@ | ||
this._load = server._heavy.policy(this.settings.load); | ||
this._compression = new Compression(); | ||
this.states = new Statehood.Definitions(this.settings.state); | ||
@@ -118,5 +120,16 @@ this.auth = new Auth(this); | ||
Hoek.inherits(internals.Connection, Events.EventEmitter); | ||
Hoek.inherits(internals.Connection, Podium); | ||
internals.Connection._events = [ | ||
{ name: 'route', spread: true }, | ||
{ name: 'request-internal', spread: true, tags: true }, | ||
{ name: 'request', spread: true, tags: true }, | ||
{ name: 'request-error', spread: true }, | ||
'response', | ||
'tail' | ||
]; | ||
internals.Connection.prototype._init = function () { | ||
@@ -361,2 +374,14 @@ | ||
internals.Connection.prototype.decoder = function (encoding, decoder) { | ||
return this._compression.addDecoder(encoding, decoder); | ||
}; | ||
internals.Connection.prototype.encoder = function (encoding, encoder) { | ||
return this._compression.addEncoder(encoding, encoder); | ||
}; | ||
internals.Connection.prototype._ext = function (event) { | ||
@@ -399,3 +424,3 @@ | ||
const vhost = vhosts[i]; | ||
const record = this._router.add({ method: route.method, path: route.path, vhost: vhost, analysis: route._analysis, id: route.settings.id }, route); | ||
const record = this._router.add({ method: route.method, path: route.path, vhost, analysis: route._analysis, id: route.settings.id }, route); | ||
route.fingerprint = record.fingerprint; | ||
@@ -405,3 +430,3 @@ route.params = record.params; | ||
this.emit('route', route.public, this, plugin); | ||
this.emit('route', [route.public, this, plugin]); | ||
}; | ||
@@ -408,0 +433,0 @@ |
@@ -37,2 +37,3 @@ 'use strict'; | ||
}, | ||
compression: {}, | ||
cors: false, // CORS headers | ||
@@ -47,2 +48,3 @@ files: { | ||
}, | ||
log: false, // Enables request level log collection | ||
payload: { | ||
@@ -55,3 +57,4 @@ failAction: 'error', | ||
uploads: Os.tmpDir(), | ||
defaultContentType: 'application/json' | ||
defaultContentType: 'application/json', | ||
compression: {} | ||
}, | ||
@@ -58,0 +61,0 @@ response: { |
@@ -13,3 +13,3 @@ 'use strict'; | ||
exports = module.exports = internals.Ext = function (server) { | ||
exports = module.exports = internals.Ext = function (type, server) { | ||
@@ -20,2 +20,3 @@ this._topo = new Topo(); | ||
this.type = type; | ||
this.nodes = null; | ||
@@ -22,0 +23,0 @@ }; |
@@ -71,7 +71,7 @@ 'use strict'; | ||
const timer = new Hoek.Bench(); | ||
const finalize = (response, data) => { | ||
const finalize = (response) => { | ||
if (response === null) { // reply.continue() | ||
response = Response.wrap(null, request); | ||
return response._prepare(null, finalize); | ||
return response._prepare(finalize); | ||
} | ||
@@ -92,3 +92,3 @@ | ||
const reply = request.server._replier.interface(request, request.route.realm, finalize); | ||
const reply = request.server._replier.interface(request, request.route.realm, {}, finalize); | ||
const bind = request.route.settings.bind; | ||
@@ -210,3 +210,3 @@ | ||
const result = { name: name }; | ||
const result = { name }; | ||
const argsNotation = !!methodParts[2]; | ||
@@ -259,7 +259,7 @@ const methodArgs = (argsNotation ? (methodParts[3] || '').split(/\s*\,\s*/) : null); | ||
const timer = new Hoek.Bench(); | ||
const finalize = (response, data) => { | ||
const finalize = (response) => { | ||
if (response === null) { // reply.continue() | ||
response = Response.wrap(null, request); | ||
return response._prepare(null, finalize); | ||
return response._prepare(finalize); | ||
} | ||
@@ -290,3 +290,3 @@ | ||
const reply = request.server._replier.interface(request, request.route.realm, finalize); | ||
const reply = request.server._replier.interface(request, request.route.realm, {}, finalize); | ||
const bind = request.route.settings.bind; | ||
@@ -293,0 +293,0 @@ |
@@ -99,12 +99,4 @@ 'use strict'; | ||
const onFulfilled = (outcome) => { | ||
return methodNext(null, outcome); | ||
}; | ||
const onRejected = (err) => { | ||
return methodNext(err); | ||
}; | ||
const onFulfilled = (outcome) => methodNext(null, outcome); | ||
const onRejected = (err) => methodNext(err); | ||
result.then(onFulfilled, onRejected); | ||
@@ -146,6 +138,6 @@ }; | ||
return Hoek.nextTick(methodNext)(Boom.badImplementation('Invalid method key when invoking: ' + name, { name: name, args: args })); | ||
return Hoek.nextTick(methodNext)(Boom.badImplementation('Invalid method key when invoking: ' + name, { name, args })); | ||
} | ||
cache.get({ id: key, args: args }, methodNext); | ||
cache.get({ id: key, args }, methodNext); | ||
}; | ||
@@ -152,0 +144,0 @@ |
@@ -8,3 +8,3 @@ 'use strict'; | ||
const Items = require('items'); | ||
const Kilt = require('kilt'); | ||
const Podium = require('podium'); | ||
const Connection = require('./connection'); | ||
@@ -24,3 +24,3 @@ const Ext = require('./ext'); | ||
Kilt.call(this, connections, server._events); | ||
Podium.call(this, [connections && connections.length ? connections : Connection._events, server._events]); | ||
@@ -43,7 +43,7 @@ this._parent = parent; | ||
_extensions: { | ||
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) | ||
onPreAuth: new Ext('onPreAuth', this.root), | ||
onPostAuth: new Ext('onPostAuth', this.root), | ||
onPreHandler: new Ext('onPreHandler', this.root), | ||
onPostHandler: new Ext('onPostHandler', this.root), | ||
onPreResponse: new Ext('onPreResponse', this.root) | ||
}, | ||
@@ -91,3 +91,3 @@ modifiers: { | ||
Hoek.inherits(internals.Plugin, Kilt); | ||
Hoek.inherits(internals.Plugin, Podium); | ||
@@ -327,3 +327,3 @@ | ||
this.root._registring = false; | ||
return callback(err); | ||
return Hoek.nextTick(callback)(err); | ||
}); | ||
@@ -357,2 +357,8 @@ }; | ||
internals.Plugin.prototype.decoder = function (encoding, decoder) { | ||
this._apply('decoder', Connection.prototype.decoder, [encoding, decoder]); | ||
}; | ||
internals.Plugin.prototype.decorate = function (type, property, method, options) { | ||
@@ -409,2 +415,20 @@ | ||
internals.Plugin.prototype.emit = function (criteria, data, callback) { | ||
this.root._events.emit(criteria, data, callback); | ||
}; | ||
internals.Plugin.prototype.encoder = function (encoding, encoder) { | ||
this._apply('encoder', Connection.prototype.encoder, [encoding, encoder]); | ||
}; | ||
internals.Plugin.prototype.event = function (event) { | ||
this.root._events.registerEvent(event); | ||
}; | ||
internals.Plugin.prototype.expose = function (key, value) { | ||
@@ -487,21 +511,12 @@ | ||
tags = (Array.isArray(tags) ? tags : [tags]); | ||
const now = (timestamp ? (timestamp instanceof Date ? timestamp.getTime() : timestamp) : Date.now()); | ||
tags = [].concat(tags); | ||
timestamp = (timestamp ? (timestamp instanceof Date ? timestamp.getTime() : timestamp) : Date.now()); | ||
const internal = !!_internal; | ||
const event = { | ||
timestamp: now, | ||
tags: tags, | ||
data: data, | ||
internal: !!_internal | ||
}; | ||
const update = (typeof data !== 'function' ? { timestamp, tags, data, internal } : () => { | ||
const tagsMap = Hoek.mapToObject(event.tags); | ||
this.root._events.emit('log', event, tagsMap); | ||
return { timestamp, tags, data: data(), internal }; | ||
}); | ||
if (this.root._settings.debug && | ||
this.root._settings.debug.log && | ||
Hoek.intersect(tagsMap, this.root._settings.debug.log, true)) { | ||
console.error('Debug:', event.tags.join(', '), (data ? '\n ' + (data.stack || (typeof data === 'object' ? Hoek.stringify(data) : data)) : '')); | ||
} | ||
this.root._events.emit({ name: 'log', tags }, update); | ||
}; | ||
@@ -508,0 +523,0 @@ |
@@ -55,12 +55,10 @@ 'use strict'; | ||
if (!this.domain) { | ||
return enter(finish); | ||
if (this.domain) { | ||
this._error = (err) => { | ||
return finish(Boom.badImplementation('Uncaught error', err)); | ||
}; | ||
} | ||
this._error = (err) => { | ||
return finish(Boom.badImplementation('Uncaught error', err)); | ||
}; | ||
enter(finish); | ||
return enter(finish); | ||
}; | ||
@@ -67,0 +65,0 @@ |
@@ -59,3 +59,3 @@ 'use strict'; | ||
reply(error, result, data); -> error || result (respond) + data | ||
reply(...).takeover(); -> ... (respond) + data | ||
reply(..., data).takeover(); -> ... (respond) + data | ||
@@ -66,6 +66,8 @@ reply.continue(data); -> (continue) + data | ||
internals.Reply.prototype.interface = function (request, realm, next) { // next(err || response, data); | ||
internals.Reply.prototype.interface = function (request, realm, options, next) { // next(err || response, data); | ||
const reply = (err, response, data) => { | ||
Hoek.assert(data === undefined || options.data, 'Reply interface does not allow a third argument'); | ||
reply._data = data; // Held for later | ||
@@ -75,2 +77,3 @@ return reply.response(err !== null && err !== undefined ? err : response); | ||
reply._settings = options; | ||
reply._replied = false; | ||
@@ -111,3 +114,17 @@ reply._next = Hoek.once(next); | ||
this._next(null, data); | ||
if (data !== undefined) { | ||
if (this._settings.data) { | ||
this._data = data; | ||
} | ||
else if (this._settings.postHandler) { | ||
const next = this._next; | ||
this._next = (result) => next(null, result); // Shift argument to signal not to stop processing other extensions | ||
return this.response(data); | ||
} | ||
else { | ||
throw new Error('reply.continue() does not allow any arguments'); | ||
} | ||
} | ||
this._next(null); | ||
this._next = null; | ||
@@ -142,3 +159,3 @@ }; | ||
if (response.isBoom) { | ||
this._next(response, this._data); | ||
this._next(response); | ||
this._next = null; | ||
@@ -157,3 +174,3 @@ return response; | ||
response._prepare(this._data, this._next); | ||
response._prepare(this._next); | ||
this._next = null; | ||
@@ -175,3 +192,3 @@ } | ||
this.send = undefined; | ||
this._prepare(reply._data, reply._next); | ||
this._prepare(reply._next); | ||
this._next = null; | ||
@@ -178,0 +195,0 @@ }; |
@@ -5,9 +5,7 @@ 'use strict'; | ||
const Events = require('events'); | ||
const Url = require('url'); | ||
const Accept = require('accept'); | ||
const Boom = require('boom'); | ||
const Hoek = require('hoek'); | ||
const Items = require('items'); | ||
const Peekaboo = require('peekaboo'); | ||
const Podium = require('podium'); | ||
const Cors = require('./cors'); | ||
@@ -65,3 +63,3 @@ const Protect = require('./protect'); | ||
Events.EventEmitter.call(this); | ||
Podium.call(this, ['finish', { name: 'peek', spread: true }, 'disconnect']); | ||
@@ -121,3 +119,3 @@ // Take measurement as soon as possible | ||
host: req.headers.host ? req.headers.host.replace(/\s/g, '') : '', | ||
acceptEncoding: Accept.encoding(this.headers['accept-encoding'], ['identity', 'gzip', 'deflate']) | ||
acceptEncoding: this.connection._compression.accept(this) | ||
}; | ||
@@ -140,4 +138,4 @@ | ||
this.raw = { | ||
req: req, | ||
res: res | ||
req, | ||
res | ||
}; | ||
@@ -199,3 +197,3 @@ | ||
Hoek.inherits(internals.Request, Events.EventEmitter); | ||
Hoek.inherits(internals.Request, Podium); | ||
@@ -230,26 +228,20 @@ | ||
tags = (Array.isArray(tags) ? tags : [tags]); | ||
const now = (timestamp ? (timestamp instanceof Date ? timestamp.getTime() : timestamp) : Date.now()); | ||
tags = [].concat(tags); | ||
timestamp = (timestamp ? (timestamp instanceof Date ? timestamp.getTime() : timestamp) : Date.now()); | ||
const internal = !!_internal; | ||
const event = { | ||
request: this.id, | ||
timestamp: now, | ||
tags: tags, | ||
data: data, | ||
internal: !!_internal | ||
}; | ||
let update = (typeof data !== 'function' ? [this, { request: this.id, timestamp, tags, data, internal }] : () => { | ||
const tagsMap = Hoek.mapToObject(event.tags); | ||
return [this, { request: this.id, timestamp, tags, data: data(), internal }]; | ||
}); | ||
// Add to request array | ||
if (this.route.settings.log) { | ||
if (typeof data === 'function') { | ||
update = update(); | ||
} | ||
this._logger.push(event); | ||
this.connection.emit(_internal ? 'request-internal' : 'request', this, event, tagsMap); | ||
this._logger.push(update[1]); // Add to request array | ||
} | ||
if (this.server._settings.debug && | ||
this.server._settings.debug.request && | ||
Hoek.intersect(tagsMap, this.server._settings.debug.request, true)) { | ||
console.error('Debug:', event.tags.join(', '), (data ? '\n ' + (data.stack || (typeof data === 'object' ? Hoek.stringify(data) : data)) : '')); | ||
} | ||
this.connection.emit({ name: internal ? 'request-internal' : 'request', tags }, update); | ||
}; | ||
@@ -266,2 +258,4 @@ | ||
Hoek.assert(this.route.settings.log, 'Request logging is disabled'); | ||
if (typeof tags === 'boolean') { | ||
@@ -360,3 +354,3 @@ internal = tags; | ||
this.raw.req.socket.setTimeout(this.route.settings.timeout.socket || 0); // Value can be false or positive | ||
this.raw.req.socket.setTimeout(this.route.settings.timeout.socket || 0); // Value can be false or positive | ||
} | ||
@@ -366,7 +360,7 @@ | ||
if (serverTimeout) { | ||
serverTimeout = Math.floor(serverTimeout - this._bench.elapsed()); // Calculate the timeout from when the request was constructed | ||
serverTimeout = Math.floor(serverTimeout - this._bench.elapsed()); // Calculate the timeout from when the request was constructed | ||
const timeoutReply = () => { | ||
this._log(['request', 'server', 'timeout', 'error'], { timeout: serverTimeout, elapsed: this._bench.elapsed() }); | ||
this._reply(Boom.serverTimeout()); | ||
this._reply(Boom.serverUnavailable()); | ||
}; | ||
@@ -381,2 +375,4 @@ | ||
// Execute lifecycle steps | ||
const each = (func, next) => { | ||
@@ -391,3 +387,3 @@ | ||
if (typeof func !== 'function') { // Extension point | ||
return this._invoke(func, next); | ||
return this._invoke(func, next); // next() called with response object which ends processing (treated like error) | ||
} | ||
@@ -398,6 +394,3 @@ | ||
Items.serial(this._route._cycle, each, (err) => { | ||
return this._reply(err); | ||
}); | ||
return Items.serial(this._route._cycle, each, (err) => this._reply(err)); | ||
}; | ||
@@ -410,9 +403,21 @@ | ||
Items.serial(event.nodes, (ext, next) => { | ||
const each = (ext, next) => { | ||
const reply = this.server._replier.interface(this, ext.plugin.realm, next); | ||
const options = { postHandler: (event.type === 'onPostHandler' || event.type === 'onPreResponse') }; | ||
const finalize = (result, override) => { | ||
if (override) { | ||
this._setResponse(override); | ||
} | ||
return next(result); // next() called with response object which ends processing (treated like error) | ||
}; | ||
const reply = this.server._replier.interface(this, ext.plugin.realm, options, finalize); | ||
const bind = (ext.bind || ext.plugin.realm.settings.bind); | ||
ext.func.call(bind, this, reply); | ||
}, exit); | ||
}; | ||
Items.serial(event.nodes, each, exit); | ||
}); | ||
@@ -446,3 +451,3 @@ }; | ||
if (exit) { | ||
if (exit) { // Can be a valid response or error (if returned from an ext, already handled because this.response is also set) | ||
this._setResponse(Response.wrap(exit, this)); | ||
@@ -455,10 +460,7 @@ } | ||
if (err) { // err can be valid response or error | ||
if (err) { // Can be valid response or error | ||
this._setResponse(Response.wrap(err, this)); | ||
} | ||
Transmit.send(this, () => { | ||
return this._finalize(); | ||
}); | ||
return Transmit.send(this, () => this._finalize()); | ||
}; | ||
@@ -470,3 +472,3 @@ | ||
this._invoke(this._route._extensions.onPreResponse, transmit); | ||
return this._invoke(this._route._extensions.onPreResponse, transmit); | ||
}; | ||
@@ -483,3 +485,3 @@ | ||
this.connection.emit('request-error', this, this.response._error); | ||
this.connection.emit('request-error', [this, this.response._error]); | ||
this._log(this.response._error.isDeveloperError ? ['internal', 'implementation', 'error'] : ['internal', 'error'], this.response._error); | ||
@@ -541,3 +543,3 @@ } | ||
this._tails[tailId] = name; | ||
this._log(['tail', 'add'], { name: name, id: tailId }); | ||
this._log(['tail', 'add'], { name, id: tailId }); | ||
@@ -547,3 +549,3 @@ const drop = () => { | ||
if (!this._tails[tailId]) { | ||
this._log(['tail', 'remove', 'error'], { name: name, id: tailId }); // Already removed | ||
this._log(['tail', 'remove', 'error'], { name, id: tailId }); // Already removed | ||
return; | ||
@@ -557,7 +559,7 @@ } | ||
this._log(['tail', 'remove', 'last'], { name: name, id: tailId }); | ||
this._log(['tail', 'remove', 'last'], { name, id: tailId }); | ||
this.connection.emit('tail', this); | ||
} | ||
else { | ||
this._log(['tail', 'remove'], { name: name, id: tailId }); | ||
this._log(['tail', 'remove'], { name, id: tailId }); | ||
} | ||
@@ -572,7 +574,3 @@ }; | ||
const state = { | ||
name: name, | ||
value: value | ||
}; | ||
const state = { name, value }; | ||
if (options) { | ||
@@ -589,5 +587,3 @@ Hoek.assert(!options.autoValue, 'Cannot set autoValue directly in a response'); | ||
const state = { | ||
name: name | ||
}; | ||
const state = { name }; | ||
@@ -603,3 +599,3 @@ state.options = Hoek.clone(options || {}); | ||
return (this.listeners('finish').length || this.listeners('peek').length ? new Peekaboo(this) : null); | ||
return (this.hasListeners('finish') || this.hasListeners('peek') ? new Response.Peek(this) : null); | ||
}; | ||
@@ -606,0 +602,0 @@ |
@@ -6,6 +6,5 @@ 'use strict'; | ||
const Stream = require('stream'); | ||
const Events = require('events'); | ||
const Boom = require('boom'); | ||
const Hoek = require('hoek'); | ||
const Peekaboo = require('peekaboo'); | ||
const Podium = require('podium'); | ||
@@ -20,3 +19,3 @@ | ||
Events.EventEmitter.call(this); | ||
Podium.call(this, ['finish', { name: 'peek', spread: true }]); | ||
@@ -41,3 +40,4 @@ options = options || {}; | ||
passThrough: true, | ||
varyEtag: false | ||
varyEtag: false, | ||
message: null | ||
}; | ||
@@ -60,3 +60,3 @@ | ||
Hoek.inherits(internals.Response, Events.EventEmitter); | ||
Hoek.inherits(internals.Response, Podium); | ||
@@ -116,2 +116,9 @@ | ||
internals.Response.prototype.message = function (httpMessage) { | ||
this.settings.message = httpMessage; | ||
return this; | ||
}; | ||
internals.Response.prototype.header = function (key, value, options) { | ||
@@ -136,17 +143,2 @@ | ||
// Ensure key and values do not include non-ascii text | ||
if (value !== undefined && | ||
value !== null) { | ||
const headerValues = [key].concat(value); | ||
for (let i = 0; i < headerValues.length; ++i) { | ||
const header = headerValues[i]; | ||
const buffer = Buffer.isBuffer(header) ? header : new Buffer(header.toString()); | ||
for (let j = 0; j < buffer.length; ++j) { | ||
Hoek.assert((buffer[j] & 0x7f) === buffer[j], 'Header value cannot contain or convert into non-ascii characters:', key); | ||
} | ||
} | ||
} | ||
if ((!append && override) || | ||
@@ -229,6 +221,7 @@ !this.headers[key]) { | ||
const etagBase = options.etag.slice(0, -1); | ||
if (etag === etagBase + '-gzip"' || | ||
etag === etagBase + '-deflate"') { | ||
return true; | ||
const encoders = request.connection._compression.encodings; | ||
for (let j = 0; j < encoders.length; ++j) { | ||
if (etag === etagBase + `-${encoders[j]}"`) { | ||
return true; | ||
} | ||
} | ||
@@ -462,3 +455,3 @@ } | ||
internals.Response.prototype._prepare = function (data, next) { | ||
internals.Response.prototype._prepare = function (next) { | ||
@@ -468,3 +461,3 @@ this._passThrough(); | ||
if (this.variety !== 'promise') { | ||
return this._processPrepare(data, next); | ||
return this._processPrepare(next); | ||
} | ||
@@ -475,7 +468,7 @@ | ||
if (source instanceof Error) { | ||
return next(Boom.wrap(source), data); | ||
return next(Boom.wrap(source)); | ||
} | ||
if (source instanceof internals.Response) { | ||
return source._processPrepare(data, next); | ||
return source._processPrepare(next); | ||
} | ||
@@ -485,6 +478,17 @@ | ||
this._passThrough(); | ||
this._processPrepare(data, next); | ||
this._processPrepare(next); | ||
}; | ||
this.source.then(onDone, onDone); | ||
const onError = (source) => { | ||
if (!(source instanceof Error)) { | ||
const err = new Error('Rejected promise'); | ||
err.data = source; | ||
return next(Boom.wrap(err)); | ||
} | ||
return next(Boom.wrap(source)); | ||
}; | ||
this.source.then(onDone, onError); | ||
}; | ||
@@ -529,12 +533,9 @@ | ||
internals.Response.prototype._processPrepare = function (data, next) { | ||
internals.Response.prototype._processPrepare = function (next) { | ||
if (!this._processors.prepare) { | ||
return next(this, data); | ||
return next(this); | ||
} | ||
this._processors.prepare(this, (prepared) => { | ||
return next(prepared, data); | ||
}); | ||
return this._processors.prepare(this, next); | ||
}; | ||
@@ -611,3 +612,3 @@ | ||
return (this.listeners('finish').length || this.listeners('peek').length ? new Peekaboo(this) : null); | ||
return (this.hasListeners('finish') || this.hasListeners('peek') ? new internals.Peek(this) : null); | ||
}; | ||
@@ -705,1 +706,22 @@ | ||
}; | ||
internals.Response.Peek = internals.Peek = function (podium) { | ||
Stream.Transform.call(this); | ||
this._podium = podium; | ||
this.once('finish', () => { | ||
podium.emit('finish'); | ||
}); | ||
}; | ||
Hoek.inherits(internals.Peek, Stream.Transform); | ||
internals.Peek.prototype._transform = function (chunk, encoding, callback) { | ||
this._podium.emit('peek', [chunk, encoding]); | ||
this.push(chunk, encoding); | ||
callback(); | ||
}; |
@@ -63,3 +63,3 @@ 'use strict'; | ||
base = Hoek.applyToDefaultsWithShallow(base, realm.settings, ['bind']); | ||
this.settings = Hoek.applyToDefaultsWithShallow(base, config || {}, ['bind']); | ||
this.settings = Hoek.applyToDefaultsWithShallow(base, config || {}, ['bind', 'validate.headers', 'validate.payload', 'validate.params', 'validate.query']); | ||
this.settings.handler = handler; | ||
@@ -95,3 +95,6 @@ this.settings = Schema.apply('routeConfig', this.settings, routeDisplay); | ||
settings: this.settings, | ||
fingerprint: this.fingerprint | ||
fingerprint: this.fingerprint, | ||
auth: { | ||
access: (request) => Auth.access(request, this.public) | ||
} | ||
}; | ||
@@ -149,2 +152,4 @@ | ||
} | ||
this.settings.payload.decoders = this.connection._compression._decoders; // Reference the shared object to keep up to date | ||
} | ||
@@ -245,3 +250,3 @@ | ||
const ext = new Ext(this.server); | ||
const ext = new Ext(type, this.server); | ||
@@ -248,0 +253,0 @@ const events = this.settings.ext[type]; |
@@ -72,2 +72,3 @@ 'use strict'; | ||
.allow(false), | ||
compression: Joi.object().pattern(/.+/, Joi.object()), | ||
cors: Joi.object({ | ||
@@ -100,2 +101,3 @@ origin: Joi.array().min(1), | ||
jsonp: Joi.string(), | ||
log: Joi.boolean(), | ||
payload: Joi.object({ | ||
@@ -113,3 +115,4 @@ output: Joi.string().valid('data', 'stream', 'file'), | ||
timeout: Joi.number().integer().positive().allow(false), | ||
defaultContentType: Joi.string() | ||
defaultContentType: Joi.string(), | ||
compression: Joi.object().pattern(/.+/, Joi.object()) | ||
}), | ||
@@ -119,3 +122,6 @@ plugins: Joi.object(), | ||
emptyStatusCode: Joi.number().valid(200, 204), | ||
failAction: Joi.string().valid('error', 'log'), | ||
failAction: [ | ||
Joi.string().valid('error', 'log'), | ||
Joi.func() | ||
], | ||
modify: Joi.boolean(), | ||
@@ -196,4 +202,4 @@ options: Joi.object(), | ||
debug: Joi.object({ | ||
request: Joi.array().allow(false), | ||
log: Joi.array().allow(false) | ||
request: Joi.array().items(Joi.string()).single().allow(false), | ||
log: Joi.array().items(Joi.string()).single().allow(false) | ||
}).allow(false), | ||
@@ -200,0 +206,0 @@ load: Joi.object(), |
@@ -5,3 +5,2 @@ 'use strict'; | ||
const Events = require('events'); | ||
const Catbox = require('catbox'); | ||
@@ -18,2 +17,3 @@ const CatboxMemory = require('catbox-memory'); | ||
const Plugin = require('./plugin'); | ||
const Podium = require('podium'); | ||
const Promises = require('./promises'); | ||
@@ -41,9 +41,9 @@ const Reply = require('./reply'); | ||
this._caches = {}; // Cache clients | ||
this._handlers = {}; // Registered handlers | ||
this._methods = new Methods(this); // Server methods | ||
this._caches = {}; // Cache clients | ||
this._handlers = {}; // Registered handlers | ||
this._methods = new Methods(this); // Server methods | ||
this._events = new Events.EventEmitter(); // Server-only events | ||
this._dependencies = []; // Plugin dependencies | ||
this._registrations = {}; // Tracks plugins registered before connection added | ||
this._events = new Podium([{ name: 'log', tags: true }, 'start', 'stop']); // Server-only events | ||
this._dependencies = []; // Plugin dependencies | ||
this._registrations = {}; // Tracks plugins registered before connection added | ||
this._heavy = new Heavy(this._settings.load); | ||
@@ -54,13 +54,13 @@ this._mime = new Mimos(this._settings.mime); | ||
this._decorations = {}; | ||
this._plugins = {}; // Exposed plugin properties by name | ||
this._plugins = {}; // Exposed plugin properties by name | ||
this._app = {}; | ||
this._registring = false; // true while register() is waiting for plugin callbacks | ||
this._state = 'stopped'; // 'stopped', 'initializing', 'initialized', 'starting', 'started', 'stopping', 'invalid' | ||
this._registring = false; // true while register() is waiting for plugin callbacks | ||
this._state = 'stopped'; // 'stopped', 'initializing', 'initialized', 'starting', 'started', 'stopping', 'invalid' | ||
this._extensionsSeq = 0; // Used to keep absolute order of extensions based on the order added across locations | ||
this._extensionsSeq = 0; // Used to keep absolute order of extensions based on the order added across locations | ||
this._extensions = { | ||
onPreStart: new Ext(this), | ||
onPostStart: new Ext(this), | ||
onPreStop: new Ext(this), | ||
onPostStop: new Ext(this) | ||
onPreStart: new Ext('onPreStart', this), | ||
onPostStart: new Ext('onPostStart', this), | ||
onPreStop: new Ext('onPreStop', this), | ||
onPostStop: new Ext('onPostStop', this) | ||
}; | ||
@@ -73,6 +73,25 @@ | ||
if (!this._caches._default) { | ||
this._createCache([{ engine: CatboxMemory }]); // Defaults to memory-based | ||
this._createCache([{ engine: CatboxMemory }]); // Defaults to memory-based | ||
} | ||
Plugin.call(this, this, [], '', null); | ||
// Subscribe to server log events | ||
if (this._settings.debug) { | ||
const debug = (request, event) => { | ||
const data = event.data; | ||
console.error('Debug:', event.tags.join(', '), (data ? '\n ' + (data.stack || (typeof data === 'object' ? Hoek.stringify(data) : data)) : '')); | ||
}; | ||
if (this._settings.debug.log) { | ||
this._events.on({ name: 'log', filter: this._settings.debug.log }, (event) => debug(null, event)); | ||
} | ||
if (this._settings.debug.request) { | ||
this.on({ name: 'request', filter: this._settings.debug.request }, debug); | ||
this.on({ name: 'request-internal', filter: this._settings.debug.request }, debug); | ||
} | ||
} | ||
}; | ||
@@ -114,3 +133,3 @@ | ||
this._caches[name] = { | ||
client: client, | ||
client, | ||
segments: {}, | ||
@@ -150,3 +169,3 @@ shared: config.shared || false | ||
root.connections.push(connection); | ||
root.addEmitter(connection); | ||
root.registerPodium(connection); | ||
root._single(); | ||
@@ -177,8 +196,4 @@ | ||
if (this._state === 'started') { | ||
Items.serial(this.connections, (connectionItem, next) => { | ||
connectionItem._start(next); | ||
}, callback); | ||
return; | ||
const each = (connectionItem, next) => connectionItem._start(next); | ||
return Items.parallel(this.connections, each, callback); | ||
} | ||
@@ -292,9 +307,5 @@ | ||
const each = (connectionItem, next) => { | ||
const each = (connectionItem, next) => connectionItem._start(next); | ||
Items.parallel(this.connections, each, (err) => { | ||
connectionItem._start(next); | ||
}; | ||
Items.serial(this.connections, each, (err) => { | ||
if (err) { | ||
@@ -305,12 +316,14 @@ this._state = 'invalid'; | ||
this._events.emit('start'); | ||
this._invoke('onPostStart', (err) => { | ||
this._events.emit('start', null, () => { | ||
if (err) { | ||
this._state = 'invalid'; | ||
return callback(err); | ||
} | ||
this._invoke('onPostStart', (err) => { | ||
this._state = 'started'; | ||
return callback(); | ||
if (err) { | ||
this._state = 'invalid'; | ||
return callback(err); | ||
} | ||
this._state = 'started'; | ||
return callback(); | ||
}); | ||
}); | ||
@@ -347,9 +360,5 @@ }); | ||
const each = (connection, next) => { | ||
const each = (connection, next) => connection._stop(options, next); | ||
Items.parallel(this.connections, each, (err) => { | ||
connection._stop(options, next); | ||
}; | ||
Items.serial(this.connections, each, (err) => { | ||
if (err) { | ||
@@ -365,13 +374,15 @@ this._state = 'invalid'; | ||
this._events.emit('stop'); | ||
this._heavy.stop(); | ||
this._invoke('onPostStop', (err) => { | ||
this._events.emit('stop', null, () => { | ||
if (err) { | ||
this._state = 'invalid'; | ||
return callback(err); | ||
} | ||
this._heavy.stop(); | ||
this._invoke('onPostStop', (err) => { | ||
this._state = 'stopped'; | ||
return callback(); | ||
if (err) { | ||
this._state = 'invalid'; | ||
return callback(err); | ||
} | ||
this._state = 'stopped'; | ||
return callback(); | ||
}); | ||
}); | ||
@@ -378,0 +389,0 @@ }); |
@@ -7,3 +7,2 @@ 'use strict'; | ||
const Stream = require('stream'); | ||
const Zlib = require('zlib'); | ||
const Ammo = require('ammo'); | ||
@@ -70,16 +69,11 @@ const Boom = require('boom'); | ||
if (!response._isPayloadSupported()) { | ||
if (!response._isPayloadSupported() && | ||
request.method !== 'head') { | ||
// Close unused file streams | ||
response._close(); | ||
// Set empty stream | ||
response._close(); // Close unused file streams | ||
response._payload = new internals.Empty(); | ||
if (request.method !== 'head') { | ||
delete response.headers['content-length']; | ||
} | ||
return Auth.response(request, next); // Must be last in case requires access to headers | ||
delete response.headers['content-length']; | ||
return Auth.response(request, next); // Must be last in case requires access to headers | ||
} | ||
@@ -107,2 +101,7 @@ | ||
if (!response._isPayloadSupported()) { | ||
response._close(); // Close unused file streams | ||
response._payload = new internals.Empty(); // Set empty stream | ||
} | ||
internals.content(response, true); | ||
@@ -164,5 +163,3 @@ return Auth.response(request, next); // Must be last in case requires access to headers | ||
const mime = request.server.mime.type(response.headers['content-type'] || 'application/octet-stream'); | ||
let encoding = (request.connection.settings.compression && mime.compressible && !response.headers['content-encoding'] ? request.info.acceptEncoding : null); | ||
encoding = (encoding === 'identity' ? null : encoding); | ||
const encoding = request.connection._compression.encoding(response); | ||
@@ -211,9 +208,6 @@ // Range | ||
if (request.headers['accept-encoding']) { | ||
response.vary('accept-encoding'); | ||
} | ||
let compressor = null; | ||
if (encoding && | ||
length !== 0 && | ||
response.statusCode !== 206 && | ||
response._isPayloadSupported()) { | ||
@@ -224,3 +218,3 @@ | ||
compressor = (encoding === 'gzip' ? Zlib.createGzip() : Zlib.createDeflate()); | ||
compressor = request.connection._compression.encoder(request, encoding); | ||
} | ||
@@ -315,5 +309,3 @@ | ||
if (isInjection) { | ||
request.raw.res._hapi = { | ||
request: request | ||
}; | ||
request.raw.res._hapi = { request }; | ||
@@ -358,2 +350,6 @@ if (response.variety === 'plain') { | ||
if (response.settings.message) { | ||
res.statusMessage = response.settings.message; | ||
} | ||
return null; | ||
@@ -433,3 +429,3 @@ }; | ||
const type = response.headers['content-type']; | ||
let type = response.headers['content-type']; | ||
if (!type) { | ||
@@ -441,11 +437,12 @@ if (response._contentType) { | ||
} | ||
else if ((!response._contentType || !postMarshal) && | ||
else { | ||
type = type.trim(); | ||
if ((!response._contentType || !postMarshal) && | ||
response.settings.charset && | ||
type.match(/^(?:text\/)|(?:application\/(?:json)|(?:javascript))/)) { | ||
const hasParams = (type.indexOf(';') !== -1); | ||
if (!hasParams || | ||
!type.match(/[; ]charset=/)) { | ||
response.type(type + (hasParams ? ', ' : '; ') + 'charset=' + (response.settings.charset)); | ||
if (!type.match(/; *charset=/)) { | ||
const semi = (type[type.length - 1] === ';'); | ||
response.type(type + (semi ? ' ' : '; ') + 'charset=' + (response.settings.charset)); | ||
} | ||
} | ||
@@ -480,3 +477,3 @@ } | ||
if (typeof autoValue !== 'function') { | ||
states.push({ name: name, value: autoValue }); | ||
states.push({ name, value: autoValue }); | ||
return nextKey(); | ||
@@ -491,3 +488,3 @@ } | ||
states.push({ name: name, value: value }); | ||
states.push({ name, value }); | ||
return nextKey(); | ||
@@ -494,0 +491,0 @@ }); |
@@ -84,3 +84,3 @@ 'use strict'; | ||
const error = (err.isBoom ? err : Boom.badRequest(err.message, err)); | ||
error.output.payload.validation = { source: source, keys: [] }; | ||
error.output.payload.validation = { source, keys: [] }; | ||
if (err.details) { | ||
@@ -118,3 +118,3 @@ for (let i = 0; i < err.details.length; ++i) { | ||
const reply = request.server._replier.interface(request, request.route.realm, exit); | ||
const reply = request.server._replier.interface(request, request.route.realm, {}, exit); | ||
request.route.settings.validate.failAction(request, reply, source, error); | ||
@@ -144,3 +144,4 @@ }); | ||
return schema(request[source], localOptions, exit); | ||
const bind = request.route.settings.bind; | ||
return schema.call(bind, request[source], localOptions, exit); | ||
}); | ||
@@ -204,3 +205,15 @@ }; | ||
return next(Boom.badImplementation(err.message)); | ||
// Return error | ||
if (typeof request.route.settings.response.failAction !== 'function') { | ||
return next(Boom.badImplementation(err.message)); | ||
} | ||
// Custom handler | ||
request._protect.run(next, (exit) => { | ||
const reply = request.server._replier.interface(request, request.route.realm, {}, exit); | ||
request.route.settings.response.failAction(request, reply, err); | ||
}); | ||
}; | ||
@@ -207,0 +220,0 @@ |
@@ -5,3 +5,3 @@ { | ||
"homepage": "http://hapijs.com", | ||
"version": "14.2.0", | ||
"version": "15.0.1", | ||
"repository": { | ||
@@ -19,3 +19,3 @@ "type": "git", | ||
"engines": { | ||
"node": ">=4.0.0" | ||
"node": ">=4.5.0" | ||
}, | ||
@@ -25,3 +25,3 @@ "dependencies": { | ||
"ammo": "2.x.x", | ||
"boom": "3.x.x", | ||
"boom": "4.x.x", | ||
"call": "3.x.x", | ||
@@ -36,8 +36,7 @@ "catbox": "7.x.x", | ||
"joi": "9.x.x", | ||
"kilt": "2.x.x", | ||
"mimos": "3.x.x", | ||
"peekaboo": "2.x.x", | ||
"podium": "^1.2.x", | ||
"shot": "3.x.x", | ||
"statehood": "4.x.x", | ||
"subtext": "4.x.x", | ||
"statehood": "5.x.x", | ||
"subtext": "^4.2.x", | ||
"topo": "2.x.x" | ||
@@ -49,3 +48,3 @@ }, | ||
"inert": "4.x.x", | ||
"lab": "10.x.x", | ||
"lab": "11.x.x", | ||
"vision": "4.x.x", | ||
@@ -55,7 +54,7 @@ "wreck": "8.x.x" | ||
"scripts": { | ||
"test": "lab -a code -t 100 -L", | ||
"test-tap": "lab -a code -r tap -o tests.tap", | ||
"test-cov-html": "lab -a code -r html -o coverage.html" | ||
"test": "lab -a code -t 100 -L -m 3000", | ||
"test-tap": "lab -a code -r tap -o tests.tap -m 3000", | ||
"test-cov-html": "lab -a code -r html -o coverage.html -m 3000" | ||
}, | ||
"license": "BSD-3-Clause" | ||
} |
@@ -5,7 +5,5 @@ <img src="https://raw.github.com/hapijs/hapi/master/images/hapi.png" /> | ||
<a href="https://sideway.com"><img src="https://www.sideway.com/sideway.png" align="right" /></a> | ||
Lead Maintainer: [Eran Hammer](https://github.com/hueniverse) | ||
**hapi** is sponsored by [Sideway](https://sideway.com). | ||
**hapi** is sponsored by Benjamin Flesch and (StriveWire)[https://strivewire.com/]. StriveWire is currently hiring full stack developers in Hamburg, Germany. | ||
@@ -16,3 +14,3 @@ **hapi** is a simple to use configuration-centric framework with built-in support for input validation, caching, | ||
Development version: **14.2.x** ([release notes](https://github.com/hapijs/hapi/issues?labels=release+notes&page=1&state=closed)) | ||
Development version: **15.0.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) | ||
@@ -19,0 +17,0 @@ |
179139
18
24
4372
18
+ Addedpodium@^1.2.x
+ Addedbourne@1.3.3(transitive)
+ Addedpodium@1.4.1(transitive)
+ Addedstatehood@5.1.1(transitive)
- Removedkilt@2.x.x
- Removedpeekaboo@2.x.x
- Removedboom@3.2.2(transitive)
- Removedkilt@2.0.2(transitive)
- Removedpeekaboo@2.0.2(transitive)
- Removedstatehood@4.1.0(transitive)
Updatedboom@4.x.x
Updatedstatehood@5.x.x
Updatedsubtext@^4.2.x