@@ -671,2 +671,9 @@ # Coding Conventions | ||
### Variable names | ||
- `err` is reserved for errors received via a callback. Use `error` for local function variables. | ||
### Callback | ||
- First argument must always be `err` | ||
- If a function takes a `callback` argument, it **must** be called on `process.nextTick()`. Otherwise, the argument name **must** be `next` to clearly declare that it may get called on same tick. |
// Load modules | ||
var Crypto = require('crypto'); | ||
var Hawk = require('hawk'); | ||
var Bcrypt = require('bcrypt'); | ||
var Hapi = require('../lib'); | ||
@@ -10,17 +10,5 @@ | ||
var internals = { | ||
salt: '' + Date.now() | ||
}; | ||
var internals = {}; | ||
internals.hashPassword = function (password) { | ||
var hash = Crypto.createHash('sha256'); | ||
hash.update(password); | ||
hash.update(internals.salt); | ||
return hash.digest('base64'); | ||
}; | ||
internals.users = { | ||
@@ -34,3 +22,3 @@ john: { | ||
internals.passwords = { | ||
john: 'john' | ||
john: '$2a$10$iqJSHD.BGr0E2IxQwYgJmeP3NvhPrXAeLSaGCj6IR/XU5QtjVu5Tm' // password: secret | ||
}; | ||
@@ -48,5 +36,8 @@ | ||
internals.loadUser = function (username, callback) { | ||
internals.validate = function (username, password, callback) { | ||
callback(null, internals.users[username], internals.hashPassword(internals.passwords[username])); | ||
Bcrypt.compare(password, internals.passwords[username], function (err, isValid) { | ||
callback(null, isValid , internals.users[username]); | ||
}); | ||
}; | ||
@@ -64,3 +55,3 @@ | ||
if (internals.credentials[id]) { | ||
return Hawk.getAuthorizationHeader('http://' + server.settings.host + ':' + server.settings.port + path, 'GET', { credentials: internals.credentials[id] }); | ||
return Hawk.client.header(server.info.uri + path, 'GET', { credentials: internals.credentials[id] }).field; | ||
} | ||
@@ -82,7 +73,2 @@ else { | ||
auth: { | ||
'default': { | ||
scheme: 'basic', | ||
loadUserFunc: internals.loadUser, | ||
hashPasswordFunc: internals.hashPassword | ||
}, | ||
'hawk': { | ||
@@ -94,4 +80,3 @@ scheme: 'hawk', | ||
scheme: 'basic', | ||
loadUserFunc: internals.loadUser, | ||
hashPasswordFunc: internals.hashPassword | ||
validateFunc: internals.validate | ||
} | ||
@@ -101,5 +86,5 @@ } | ||
var http = new Hapi.Server(0, config); | ||
var server = new Hapi.Server(8000, config); | ||
http.route([ | ||
server.route([ | ||
{ method: 'GET', path: '/basic', config: { handler: internals.handler, auth: { strategies: ['basic'] } } }, | ||
@@ -110,12 +95,12 @@ { method: 'GET', path: '/hawk', config: { handler: internals.handler, auth: { strategies: ['hawk'] } } }, | ||
http.start(function () { | ||
server.start(function () { | ||
console.log('\nBasic request to /basic:'); | ||
console.log('curl ' + http.settings.uri + '/basic -H "Authorization: Basic ' + (new Buffer('john:john', 'utf8')).toString('base64') + '"'); | ||
console.log('curl ' + server.info.uri + '/basic -H "Authorization: Basic ' + (new Buffer('john:secret', 'utf8')).toString('base64') + '"'); | ||
console.log('\nHawk request to /hawk:'); | ||
console.log('curl ' + http.settings.uri + '/hawk -H \'Authorization: ' + internals.hawkHeader('john', '/hawk', http) + '\''); | ||
console.log('curl ' + server.info.uri + '/hawk -H \'Authorization: ' + internals.hawkHeader('john', '/hawk', server) + '\''); | ||
console.log('\nBasic request to /multiple:'); | ||
console.log('curl ' + http.settings.uri + '/multiple -H "Authorization: Basic ' + (new Buffer('john:john', 'utf8')).toString('base64') + '"'); | ||
console.log('curl ' + server.info.uri + '/multiple -H "Authorization: Basic ' + (new Buffer('john:secret', 'utf8')).toString('base64') + '"'); | ||
console.log('\nHawk request to /multiple:'); | ||
console.log('curl ' + http.settings.uri + '/multiple -H \'Authorization: ' + internals.hawkHeader('john', '/multiple', http) + '\''); | ||
console.log('curl ' + server.info.uri + '/multiple -H \'Authorization: ' + internals.hawkHeader('john', '/multiple', server) + '\''); | ||
}); | ||
@@ -122,0 +107,0 @@ }; |
@@ -11,5 +11,5 @@ // Load modules | ||
internals.profile = function (request) { | ||
internals.profile = function () { | ||
request.reply({ | ||
this.reply({ | ||
'id': 'fa0dbda9b1b', | ||
@@ -21,5 +21,5 @@ 'name': 'John Doe' | ||
internals.activeItem = function (request) { | ||
internals.activeItem = function () { | ||
request.reply({ | ||
this.reply({ | ||
'id': '55cf687663', | ||
@@ -31,8 +31,8 @@ 'name': 'Active Item' | ||
internals.item = function (request) { | ||
internals.item = function () { | ||
setTimeout(function () { | ||
request.reply({ | ||
'id': request.params.id, | ||
this.reply({ | ||
'id': this.params.id, | ||
'name': 'Item' | ||
@@ -54,11 +54,11 @@ }); | ||
var http = new Hapi.Server(8080, config); | ||
var server = new Hapi.Server(8000, config); | ||
http.route([ | ||
server.route([ | ||
{ method: 'GET', path: '/profile', config: { handler: internals.profile, cache: { expiresIn: 30000 } } }, | ||
{ method: 'GET', path: '/item', config: { handler: internals.activeItem } }, | ||
{ method: 'GET', path: '/item/{id}', config: { handler: internals.item, cache: { expiresIn: 20000, staleIn: 10000, staleTimeout: 500 } } } | ||
{ method: 'GET', path: '/item/{id}', config: { handler: internals.item, cache: { mode: 'server', expiresIn: 20000, staleIn: 10000, staleTimeout: 500 } } } | ||
]); | ||
http.start(); | ||
server.start(); | ||
}; | ||
@@ -65,0 +65,0 @@ |
@@ -8,3 +8,3 @@ { | ||
"host": "localhost", | ||
"port": 8001, | ||
"port": 8000, | ||
"options": { | ||
@@ -15,3 +15,3 @@ "labels": ["api", "nasty"] | ||
{ | ||
"port": 8002, | ||
"port": 8001, | ||
"options": { | ||
@@ -18,0 +18,0 @@ "labels": ["api", "nice"] |
// Load modules | ||
var Crypto = require('crypto'); | ||
var Hapi = require('../lib'); | ||
@@ -9,43 +8,61 @@ | ||
var internals = { | ||
salt: '' + Date.now() | ||
var internals = {}; | ||
internals.users = { | ||
john: { | ||
id: 'john', | ||
password: 'password', | ||
name: 'John Doe' | ||
} | ||
}; | ||
internals.hashPassword = function (password) { | ||
internals.home = function () { | ||
var hash = Crypto.createHash('sha256'); | ||
hash.update(password); | ||
hash.update(internals.salt); | ||
return hash.digest('base64'); | ||
this.reply('<html><head><title>Login page</title></head><body><h3>Welcome ' + this.auth.credentials.name + '!</h3><br/><form method="get" action="/logout"><input type="submit" value="Logout"></form></body></html>'); | ||
}; | ||
internals.users = { | ||
john: { | ||
id: 'john', | ||
password: internals.hashPassword('john') | ||
internals.login = function () { | ||
if (this.auth.isAuthenticated) { | ||
return this.reply.redirect('/'); | ||
} | ||
}; | ||
var message = ''; | ||
var account = null; | ||
internals.validateCookie = function (session, callback) { | ||
if (this.method === 'post') { | ||
if (!this.payload.username || | ||
!this.payload.password) { | ||
var password = internals.users[session.id].password; | ||
message = 'Missing username or password'; | ||
} | ||
else { | ||
account = internals.users[this.payload.username]; | ||
if (!account || | ||
account.password !== this.payload.password) { | ||
callback(password === session.password ? null : new Error('Invalid credentials'), null); | ||
}; | ||
message = 'Invalid username or password'; | ||
} | ||
} | ||
} | ||
if (this.method === 'get' || | ||
message) { | ||
internals.handler = function (request) { | ||
return this.reply('<html><head><title>Login page</title></head><body>' + (message ? '<h3>' + message + '</h3><br/>' : '') + '<form method="post" action="/login">Username: <input type="text" name="username"><br>Password: <input type="password" name="password"><br/><input type="submit" value="Login"></form></body></html>'); | ||
} | ||
request.reply('Success'); | ||
this.auth.session.set(account); | ||
return this.reply.redirect('/'); | ||
}; | ||
internals.loginHandler = function (request) { | ||
internals.logout = function () { | ||
request.auth.session.set(internals.users.john); | ||
request.reply('Success'); | ||
this.auth.session.clear(); | ||
return this.reply.redirect('/'); | ||
}; | ||
@@ -60,24 +77,17 @@ | ||
password: 'secret', | ||
ttl: 60 * 1000, // Expire after a minute | ||
cookie: 'sid', // Cookie name | ||
clearInvalid: true, | ||
validateFunc: internals.validateCookie | ||
cookie: 'sid-example', | ||
redirectTo: '/login', | ||
isSecure: false | ||
} | ||
}; | ||
var http = new Hapi.Server(0, config); | ||
var server = new Hapi.Server('localhost', 8000, config); | ||
http.route([ | ||
{ method: 'GET', path: '/', config: { handler: internals.handler, auth: { strategies: ['default'] } } }, | ||
{ method: 'GET', path: '/login', config: { handler: internals.loginHandler, auth: false } } | ||
server.route([ | ||
{ method: 'GET', path: '/', config: { handler: internals.home, auth: true } }, | ||
{ method: '*', path: '/login', config: { handler: internals.login, auth: { mode: 'try' } } }, | ||
{ method: 'GET', path: '/logout', config: { handler: internals.logout, auth: true } } | ||
]); | ||
http.start(function () { | ||
console.log('\nLogin with the following command'); | ||
console.log('curl ' + http.settings.uri + '/login -I'); | ||
console.log('\nCopy the Set-Cookie value up until the ;'); | ||
console.log('\nAuthenticate request to /:'); | ||
console.log('curl ' + http.settings.uri + '/ -H \"Cookie: [paste cookie value here]"'); | ||
}); | ||
server.start(); | ||
}; | ||
@@ -84,0 +94,0 @@ |
@@ -1,3 +0,1 @@ | ||
// Demonstrates how to hide error messages from the client | ||
// Load modules | ||
@@ -13,5 +11,5 @@ | ||
internals.get = function (request) { | ||
internals.get = function () { | ||
request.reply(new Error('Here is my error')); | ||
this.reply(new Error('Here is my error')); | ||
}; | ||
@@ -22,2 +20,4 @@ | ||
// Demonstrates how to hide error messages from the client | ||
if (request.response().isBoom) { | ||
@@ -34,8 +34,8 @@ var error = request.response(); | ||
var http = new Hapi.Server(8080); | ||
var server = new Hapi.Server(8000); | ||
http.ext('onPreResponse', internals.onPreResponse); | ||
server.ext('onPreResponse', internals.onPreResponse); | ||
http.route({ method: 'GET', path: '/', handler: internals.get }); | ||
http.start(); | ||
server.route({ method: 'GET', path: '/', handler: internals.get }); | ||
server.start(); | ||
}; | ||
@@ -42,0 +42,0 @@ |
@@ -19,3 +19,3 @@ // Load modules | ||
Hapi.Log.event('onRequest'); | ||
console.log('onRequest'); | ||
next(); | ||
@@ -27,3 +27,3 @@ }; | ||
Hapi.Log.event('onPreHandler1: ' + request.method); | ||
console.log('onPreHandler1: ' + request.method); | ||
next(); | ||
@@ -35,3 +35,3 @@ }; | ||
Hapi.Log.event('onPreHandler2: ' + request.path); | ||
console.log('onPreHandler2: ' + request.path); | ||
next(); | ||
@@ -43,3 +43,3 @@ }; | ||
Hapi.Log.event('onPostHandler'); | ||
console.log('onPostHandler'); | ||
next(); | ||
@@ -51,10 +51,10 @@ }; | ||
var http = new Hapi.Server(8080); | ||
var server = new Hapi.Server(8000); | ||
http.ext('onRequest', internals.onRequest); // New request, before handing over to the router (allows changes to method and url) | ||
http.ext('onPreHandler', [internals.onPreHandler1, internals.onPreHandler2]); // After validation and body parsing, before route handler | ||
http.ext('onPostHandler', internals.onPostHandler); // After route handler returns, before setting response | ||
server.ext('onRequest', internals.onRequest); // New request, before handing over to the router (allows changes to method and url) | ||
server.ext('onPreHandler', [internals.onPreHandler1, internals.onPreHandler2]); // After validation and body parsing, before route handler | ||
server.ext('onPostHandler', internals.onPostHandler); // After route handler returns, before setting response | ||
http.route({ method: 'GET', path: '/', handler: internals.get }); | ||
http.start(); | ||
server.route({ method: 'GET', path: '/', handler: internals.get }); | ||
server.start(); | ||
}; | ||
@@ -61,0 +61,0 @@ |
@@ -1,3 +0,1 @@ | ||
// Example serves a favicon.ico file that is cached on the client for a day | ||
// Load modules | ||
@@ -15,9 +13,9 @@ | ||
var http = new Hapi.Server(8080); | ||
var server = new Hapi.Server(8000, { files: { relativeTo: 'routes' } }); | ||
http.route([ | ||
{ method: 'GET', path: '/favicon.ico', handler: { file: './favicon.ico' }, config: { cache: { expiresIn: 86400000, privacy: 'public' } } } | ||
]); | ||
// Serves a favicon.ico file that is cached on the client for a day | ||
http.start(); | ||
server.route({ method: 'GET', path: '/favicon.ico', handler: { file: './favicon.ico' }, config: { cache: { expiresIn: 86400000, privacy: 'public' } } }); | ||
server.route({ method: 'GET', path: '/', handler: function () { this.reply('hello world!'); } }); | ||
server.start(); | ||
}; | ||
@@ -24,0 +22,0 @@ |
@@ -24,25 +24,36 @@ // Load modules | ||
var server = new Hapi.Server(8080, { cache: 'redis' }); | ||
var server = new Hapi.Server(8000); | ||
server.helper('user', internals.user, { cache: { expiresIn: 2000, staleIn: 1000, staleTimeout: 100 } }); | ||
server.start(function () { | ||
server.helpers.user(4, function (result1) { | ||
server.helpers.user('john', function (result1) { | ||
console.log(result1); | ||
console.log(result1); // Gen: 1 | ||
setTimeout(function () { | ||
setTimeout(function () { | ||
server.helpers.user(4, function (result2) { | ||
server.helpers.user('john', function (result2) { | ||
console.log(result2); | ||
console.log(result2); // Gen: 1 | ||
setTimeout(function () { | ||
setTimeout(function () { | ||
server.helpers.user(4, function (result3) { | ||
server.helpers.user('john', function (result3) { | ||
console.log(result3); | ||
}); | ||
}, 50); | ||
}); | ||
}, 1010); | ||
console.log(result3); // Gen: 1 | ||
setTimeout(function () { | ||
server.helpers.user('john', function (result4) { | ||
console.log(result4); // Gen: 2 | ||
server.stop(); | ||
}); | ||
}, 50); | ||
}); | ||
}, 1010); | ||
}); | ||
}, 50); | ||
}); | ||
}); | ||
@@ -49,0 +60,0 @@ }; |
@@ -11,5 +11,5 @@ // Load modules | ||
internals.get = function (request) { | ||
internals.get = function () { | ||
request.reply('Success!'); | ||
this.reply('Success!'); | ||
}; | ||
@@ -20,3 +20,3 @@ | ||
var server = new Hapi.Server(8080); | ||
var server = new Hapi.Server(8000); | ||
server.route({ method: 'GET', path: '/', handler: internals.get }); | ||
@@ -23,0 +23,0 @@ |
@@ -52,5 +52,5 @@ // Load modules | ||
var http = new Hapi.Server(8080); | ||
var server = new Hapi.Server(8000); | ||
http.route({ | ||
server.route({ | ||
method: 'GET', | ||
@@ -70,3 +70,3 @@ path: '/', | ||
http.start(); | ||
server.start(); | ||
}; | ||
@@ -73,0 +73,0 @@ |
@@ -13,9 +13,12 @@ // Load modules | ||
var http = new Hapi.Server(8080); | ||
var server = new Hapi.Server(8000); | ||
http.route({ method: 'GET', path: '/', handler: { proxy: { host: 'google.com', port: 80 } } }); | ||
http.route({ method: 'GET', path: '/images/srpr/logo3w.png', handler: { proxy: { host: 'google.com', port: 80 } } }); | ||
http.route({ method: 'POST', path: '/', handler: { proxy: { host: 'google.com', port: 80 } } }); | ||
var mapper = function (request, callback) { | ||
http.start(); | ||
callback(null, 'http://www.google.com/search?q=' + request.params.term); | ||
}; | ||
server.route({ method: '*', path: '/{p*}', handler: { proxy: { host: 'google.com', port: 80 } } }); | ||
server.route({ method: 'GET', path: '/hapi/{term}', handler: { proxy: { mapUri: mapper } } }); | ||
server.start(); | ||
}; | ||
@@ -22,0 +25,0 @@ |
@@ -11,4 +11,4 @@ // The following initializes a socket.io server. | ||
var Hapi = require('../'); | ||
var SocketIO = require('socket.io'); | ||
var Hapi = require('../'); | ||
@@ -23,18 +23,15 @@ | ||
internals.server = new Hapi.Server(8080); | ||
internals.server.route({ method: 'GET', path: '/', handler: internals.helloHandler }); | ||
var server = new Hapi.Server(8000); | ||
internals.server.start(internals.startSocketIO); | ||
}; | ||
var helloHandler = function () { | ||
this.reply('Hello'); | ||
}; | ||
internals.startSocketIO = function () { | ||
server.route({ method: 'GET', path: '/', handler: helloHandler }); | ||
var io = SocketIO.listen(internals.server.listener); | ||
}; | ||
server.start(function () { | ||
internals.helloHandler = function () { | ||
this.reply('Hello'); | ||
var io = SocketIO.listen(server.listener); | ||
}); | ||
}; | ||
@@ -41,0 +38,0 @@ |
@@ -11,8 +11,5 @@ // Load modules | ||
internals.echo = function (request) { | ||
internals.echo = function () { | ||
request.reply.stream(request.raw.req) | ||
.type(request.raw.req.headers['Content-Type']) | ||
.bytes(request.raw.req.headers['Content-Length']) | ||
.send(); | ||
this.reply(this.raw.req); | ||
}; | ||
@@ -23,5 +20,5 @@ | ||
var http = new Hapi.Server(8080); | ||
http.route({ method: 'POST', path: '/', config: { handler: internals.echo, payload: 'stream' } }); | ||
http.start(); | ||
var server = new Hapi.Server(8000); | ||
server.route({ method: 'POST', path: '/', config: { handler: internals.echo, payload: 'stream' } }); | ||
server.start(); | ||
}; | ||
@@ -28,0 +25,0 @@ |
@@ -11,19 +11,19 @@ // Load modules | ||
internals.get = function (request) { | ||
internals.get = function () { | ||
var tail1 = request.addTail('tail1'); | ||
var tail1 = this.tail('tail1'); | ||
setTimeout(function () { | ||
console.log(1); | ||
request.removeTail(tail1); // Using removeTail() interface | ||
tail1(); | ||
}, 5000); | ||
var tail2 = request.addTail('tail2'); | ||
var tail2 = this.tail('tail2'); | ||
setTimeout(function () { | ||
console.log(2); | ||
tail2(); // Using tail() function interface | ||
tail2(); | ||
}, 2000); | ||
request.reply('Success!\n'); | ||
this.reply('Success!\n'); | ||
}; | ||
@@ -34,9 +34,9 @@ | ||
var http = new Hapi.Server(8080); | ||
http.route({ method: 'GET', path: '/', handler: internals.get }); | ||
http.start(); | ||
var server = new Hapi.Server(8000); | ||
server.route({ method: 'GET', path: '/', handler: internals.get }); | ||
server.start(); | ||
// Listen to tail events | ||
http.on('tail', function (request) { | ||
server.on('tail', function (request) { | ||
@@ -43,0 +43,0 @@ console.log('Wag the dog'); |
@@ -18,11 +18,11 @@ // Load modules | ||
internals.get = function (request) { | ||
internals.get = function () { | ||
request.reply('Success!\n'); | ||
this.reply('Success!\n'); | ||
}; | ||
internals.payload = function (request) { | ||
internals.payload = function () { | ||
request.reply('Success!\n'); | ||
this.reply('Success!\n'); | ||
}; | ||
@@ -33,5 +33,5 @@ | ||
var http = new Hapi.Server(8080); | ||
var server = new Hapi.Server(8000); | ||
http.route([ | ||
server.route([ | ||
{ method: 'GET', path: '/', config: { handler: internals.get, validate: { query: { username: S() } } } }, | ||
@@ -52,5 +52,5 @@ { method: 'GET', path: '/admin', config: { handler: internals.get, validate: { query: { username: S().required().with('password'), password: S() } } } }, | ||
http.route({ method: 'POST', path: '/users/{id}', config: { handler: internals.payload, validate: { query: {}, payload: schema } } }); | ||
server.route({ method: 'POST', path: '/users/{id}', config: { handler: internals.payload, validate: { query: {}, payload: schema } } }); | ||
http.start(); | ||
server.start(); | ||
}; | ||
@@ -63,11 +63,11 @@ | ||
Try various URLs like: | ||
http://localhost:8080/ // success | ||
http://localhost:8080/?username=test // success | ||
http://localhost:8080/admin?username=steve&password=shhhhhh // success | ||
http://localhost:8080/admin?username=steve // fail | ||
http://localhost:8080/users?email=steve@example.com // success | ||
http://localhost:8080/users?email=@example.com // fail | ||
http://localhost:8080/config?choices=1&choices=2 // success | ||
http://localhost:8080/config?choices=1 // success | ||
http://localhost:8080/config // fail | ||
http://localhost:8000/ // success | ||
http://localhost:8000/?username=test // success | ||
http://localhost:8000/admin?username=steve&password=shhhhhh // success | ||
http://localhost:8000/admin?username=steve // fail | ||
http://localhost:8000/users?email=steve@example.com // success | ||
http://localhost:8000/users?email=@example.com // fail | ||
http://localhost:8000/config?choices=1&choices=2 // success | ||
http://localhost:8000/config?choices=1 // success | ||
http://localhost:8000/config // fail | ||
*/ |
@@ -6,2 +6,3 @@ // Load modules | ||
// Declare internals | ||
@@ -8,0 +9,0 @@ |
@@ -7,2 +7,3 @@ // Load modules | ||
// Declare internals | ||
@@ -17,3 +18,3 @@ | ||
request.reply.view(viewName, { title: viewName }).send(); | ||
request.reply.view(viewName, { title: viewName }); | ||
}; | ||
@@ -25,3 +26,3 @@ }; | ||
request.reply.view('index', { pages: Object.keys(Pages.getAll()), title: 'All pages' }).send(); | ||
request.reply.view('index', { pages: Object.keys(Pages.getAll()), title: 'All pages' }); | ||
}; | ||
@@ -32,3 +33,3 @@ | ||
request.reply.view('page', { page: Pages.getPage(request.params.page), title: request.params.page }).send(); | ||
request.reply.view('page', { page: Pages.getPage(request.params.page), title: request.params.page }); | ||
}; | ||
@@ -40,3 +41,3 @@ | ||
Pages.savePage(request.payload.name, request.payload.contents); | ||
request.reply.view('page', { page: Pages.getPage(request.payload.name), title: 'Create page' }).send(); | ||
request.reply.view('page', { page: Pages.getPage(request.payload.name), title: 'Create page' }); | ||
}; | ||
@@ -47,3 +48,3 @@ | ||
request.reply.view('edit', { page: Pages.getPage(request.params.page), title: 'Edit: ' + request.params.page }).send(); | ||
request.reply.view('edit', { page: Pages.getPage(request.params.page), title: 'Edit: ' + request.params.page }); | ||
}; | ||
@@ -55,3 +56,3 @@ | ||
Pages.savePage(request.params.page, request.payload.contents); | ||
request.reply.view('page', { page: Pages.getPage(request.params.page), title: request.params.page }).send(); | ||
request.reply.view('page', { page: Pages.getPage(request.params.page), title: request.params.page }); | ||
}; | ||
@@ -63,11 +64,6 @@ | ||
views: { | ||
engines: { html: 'handlebars' }, | ||
path: Path.join(__dirname, 'views'), | ||
engine: { | ||
module: 'handlebars', | ||
extension: 'html' | ||
}, | ||
layout: true, | ||
partials: { | ||
path: Path.join(__dirname, 'views', 'partials') | ||
} | ||
partialsPath: Path.join(__dirname, 'views', 'partials') | ||
}, | ||
@@ -81,3 +77,3 @@ state: { | ||
var server = new Hapi.Server(3000, options); | ||
var server = new Hapi.Server(8000, options); | ||
server.route({ method: 'GET', path: '/', handler: getPages }); | ||
@@ -84,0 +80,0 @@ server.route({ method: 'GET', path: '/pages/{page}', handler: getPage }); |
@@ -16,3 +16,3 @@ // Load modules | ||
message: 'Hello World!' | ||
}).send(); | ||
}); | ||
}; | ||
@@ -25,10 +25,8 @@ | ||
views: { | ||
path: __dirname + '/templates', | ||
engine: { | ||
module: 'handlebars' | ||
} | ||
engines: { html: 'handlebars' }, | ||
path: __dirname + '/templates' | ||
} | ||
}; | ||
var server = new Hapi.Server(3000, options); | ||
var server = new Hapi.Server(8000, options); | ||
server.route({ method: 'GET', path: '/', handler: handler }); | ||
@@ -35,0 +33,0 @@ server.start(); |
@@ -16,3 +16,3 @@ // Load modules | ||
message: 'Hello World!\n' | ||
}).send(); | ||
}); | ||
}; | ||
@@ -25,7 +25,4 @@ | ||
views: { | ||
engines: { html: 'handlebars' }, | ||
path: __dirname + '/templates', | ||
engine: { | ||
module: 'handlebars', | ||
extension: 'html' | ||
}, | ||
layout: true | ||
@@ -35,3 +32,3 @@ } | ||
var server = new Hapi.Server(3000, options); | ||
var server = new Hapi.Server(8000, options); | ||
server.route({ method: 'GET', path: '/', handler: handler }); | ||
@@ -38,0 +35,0 @@ server.start(); |
@@ -16,3 +16,3 @@ // Load modules | ||
message: 'Hello World!\n' | ||
}).send(); | ||
}); | ||
}; | ||
@@ -25,13 +25,9 @@ | ||
views: { | ||
engines: { html: 'handlebars' }, | ||
path: __dirname + '/templates', | ||
engine: { | ||
module: 'handlebars' | ||
}, | ||
partials: { | ||
path: __dirname + '/templates/withPartials' | ||
} | ||
partialsPath: __dirname + '/templates/withPartials' | ||
} | ||
}; | ||
var server = new Hapi.Server(3000, options); | ||
var server = new Hapi.Server(8000, options); | ||
server.route({ method: 'GET', path: '/', handler: handler }); | ||
@@ -38,0 +34,0 @@ server.start(); |
@@ -16,3 +16,3 @@ // Load modules | ||
message: 'Index - Hello World!' | ||
}).send(); | ||
}); | ||
}; | ||
@@ -25,3 +25,3 @@ | ||
message: 'About - Hello World!' | ||
}).send(); | ||
}); | ||
}; | ||
@@ -34,7 +34,4 @@ | ||
views: { | ||
engines: { html: 'jade' }, | ||
path: __dirname + '/templates', | ||
engine: { | ||
module: 'jade', | ||
extension: 'jade' | ||
}, | ||
compileOptions: { | ||
@@ -46,3 +43,3 @@ pretty: true | ||
var server = new Hapi.Server(3000, options); | ||
var server = new Hapi.Server(8000, options); | ||
server.route({ method: 'GET', path: '/', handler: rootHandler }); | ||
@@ -49,0 +46,0 @@ server.route({ method: 'GET', path: '/about', handler: aboutHandler }); |
@@ -17,3 +17,3 @@ // Load modules | ||
request.reply.view('index', ctx).send(); | ||
request.reply.view('index', ctx); | ||
}; | ||
@@ -23,3 +23,3 @@ | ||
request.reply.view('handlebars', ctx).send(); | ||
request.reply.view('handlebars', ctx); | ||
}; | ||
@@ -32,11 +32,11 @@ | ||
views: { | ||
engines: { | ||
'html': 'handlebars', | ||
'jade': 'jade' | ||
}, | ||
path: __dirname + '/templates', | ||
engines: { | ||
'html': { module: 'handlebars' }, | ||
'jade': { module: 'jade' } | ||
} | ||
} | ||
}; | ||
var server = new Hapi.Server(3000, options); | ||
var server = new Hapi.Server(8000, options); | ||
server.route({ method: 'GET', path: '/one', handler: oneHandler }); | ||
@@ -43,0 +43,0 @@ server.route({ method: 'GET', path: '/two', handler: twoHandler }); |
// Load modules | ||
var Cryptiles = require('cryptiles'); | ||
var Utils = require('../utils'); | ||
@@ -18,3 +17,3 @@ var Boom = require('boom'); | ||
Utils.assert(options.scheme === 'basic', 'Wrong scheme'); | ||
Utils.assert(options.loadUserFunc, 'Missing required loadUserFunc method in configuration'); | ||
Utils.assert(options.validateFunc, 'Missing required validateFunc method in configuration'); | ||
Utils.assert(server, 'Server is required'); | ||
@@ -34,2 +33,4 @@ | ||
callback = Utils.nextTick(callback); | ||
var req = request.raw.req; | ||
@@ -61,10 +62,16 @@ var authorization = req.headers.authorization; | ||
this.settings.loadUserFunc(username, function (err, credentials, hashedPassword) { | ||
if (!username) { | ||
return callback(Boom.badRequest('HTTP authentication header missing username', 'Basic')); | ||
} | ||
this.settings.validateFunc(username, password, function (err, isValid, credentials) { | ||
credentials = credentials || null; | ||
if (err) { | ||
return callback(err, null, { log: { tags: ['auth', 'basic'], data: err } }); | ||
return callback(err, credentials, { log: { tags: ['auth', 'basic'], data: err } }); | ||
} | ||
if (!credentials) { | ||
return callback(Boom.unauthorized('Bad username or password', 'Basic'), null, { log: { tags: 'user' } }); | ||
if (!isValid) { | ||
return callback(Boom.unauthorized('Bad username or password', 'Basic'), credentials); | ||
} | ||
@@ -76,19 +83,2 @@ | ||
if (hashedPassword === null || | ||
hashedPassword === undefined || | ||
typeof hashedPassword !== 'string') { | ||
return callback(Boom.internal('Bad password received for Basic auth validation'), null, { log: { tags: ['password', 'invalid'] } }); | ||
} | ||
if (typeof self.settings.hashPasswordFunc === 'function') { | ||
password = self.settings.hashPasswordFunc(password); | ||
} | ||
// Check password | ||
if (!Cryptiles.fixedTimeComparison(hashedPassword, password)) { | ||
return callback(Boom.unauthorized('Bad username or password', 'Basic'), null, { log: { tags: 'password' } }); | ||
} | ||
// Authenticated | ||
@@ -95,0 +85,0 @@ |
@@ -19,7 +19,7 @@ // Load modules | ||
Utils.assert(options.scheme === 'cookie', 'Wrong scheme'); | ||
Utils.assert(options.validateFunc, 'Missing required validateFunc method in configuration'); | ||
Utils.assert(!options.validateFunc || typeof options.validateFunc === 'function', 'Invalid validateFunc method in configuration'); | ||
Utils.assert(options.password, 'Missing required password in configuration'); | ||
Utils.assert(!options.appendNext || options.redirectTo, 'Cannot set \'appendNext\' without \'redirectTo\''); | ||
this.settings = Utils.clone(options); // Options can be reused | ||
this.settings = Utils.clone(options); // Options can be reused | ||
this.settings.cookie = this.settings.cookie || 'sid'; | ||
@@ -30,3 +30,3 @@ | ||
password: this.settings.password, | ||
isSecure: !this.settings.allowInsecure, | ||
isSecure: this.settings.isSecure !== false, // Defaults to true | ||
path: '/' | ||
@@ -39,2 +39,4 @@ }; | ||
server.state(this.settings.cookie, cookieOptions); | ||
if (typeof this.settings.appendNext === 'boolean') { | ||
@@ -44,4 +46,2 @@ this.settings.appendNext = (this.settings.appendNext ? 'next' : ''); | ||
server.state(this.settings.cookie, cookieOptions); | ||
return this; | ||
@@ -57,2 +57,4 @@ }; | ||
callback = Utils.nextTick(callback); | ||
var validate = function () { | ||
@@ -67,5 +69,11 @@ | ||
self.settings.validateFunc(session, function (err, override) { | ||
if (!self.settings.validateFunc) { | ||
return callback(null, session); | ||
} | ||
if (err) { | ||
self.settings.validateFunc(session, function (err, isValid, credentials) { | ||
if (err || | ||
!isValid) { | ||
if (self.settings.clearInvalid) { | ||
@@ -75,10 +83,10 @@ request.clearState(self.settings.cookie); | ||
return unauthenticated(Boom.unauthorized('Invalid cookie'), session, { log: { data: err } }); | ||
return unauthenticated(Boom.unauthorized('Invalid cookie'), session, { log: (err ? { data: err } : 'Failed validation') }); | ||
} | ||
if (override) { | ||
request.setState(self.settings.cookie, override); | ||
if (credentials) { | ||
request.setState(self.settings.cookie, credentials); | ||
} | ||
return callback(null, override || session); | ||
return callback(null, credentials || session); | ||
}); | ||
@@ -121,2 +129,3 @@ }; | ||
Utils.assert(session && typeof session === 'object', 'Invalid session'); | ||
request.setState(self.settings.cookie, session); | ||
@@ -123,0 +132,0 @@ }, |
@@ -41,4 +41,6 @@ // Load modules | ||
var isValid = Hawk.server.authenticatePayload(request.rawBody, request.auth.credentials, request.auth.artifacts, request.raw.req.headers['content-type']); | ||
callback = Utils.nextTick(callback); | ||
var isValid = Hawk.server.authenticatePayload(request.rawPayload, request.auth.credentials, request.auth.artifacts, request.raw.req.headers['content-type']); | ||
return callback(isValid ? null : Boom.unauthorized('Payload is invalid')); | ||
@@ -50,44 +52,24 @@ }; | ||
var options = { | ||
contentType: response._headers['Content-Type'] | ||
}; | ||
callback = Utils.nextTick(callback); | ||
if (response._payload && | ||
response._payload.length) { | ||
var payloadHash = Hawk.crypto.initializePayloadHash(request.auth.credentials.algorithm, response._headers['Content-Type']); | ||
options.payload = response._payload.join(''); | ||
response.header('Trailer', 'Server-Authorization'); | ||
response.header('Transfer-Encoding', 'chunked'); | ||
var header = Hawk.server.header(request.auth.credentials, request.auth.artifacts, options); | ||
if (!header) { | ||
return callback(Boom.internal('Failed generating Hawk response authorization header')); | ||
} | ||
response._preview.on('peek', function (chunk) { | ||
response.header('Server-Authorization', header); | ||
return callback(); | ||
} | ||
payloadHash.update(chunk); | ||
}); | ||
if (response.variety === 'stream') { | ||
response._preview.on('finish', function () { | ||
response.header('Trailer', 'Server-Authorization'); | ||
var header = Hawk.server.header(request.auth.credentials, request.auth.artifacts, { hash: Hawk.crypto.finalizePayloadHash(payloadHash) }); | ||
if (header) { | ||
request.raw.res.addTrailers({ 'Server-Authorization': header }); | ||
} | ||
}); | ||
var payload = ''; | ||
response.stream.on('data', function (chunk) { | ||
payload += chunk; | ||
}); | ||
response.stream.once('end', function (chunk) { | ||
payload += chunk; | ||
options.payload = payload; | ||
var header = Hawk.server.header(request.auth.credentials, request.auth.artifacts, options); | ||
if (header) { | ||
request.raw.res.addTrailers({ 'Server-Authorization': header }); | ||
} | ||
}); | ||
} | ||
callback(); | ||
}; | ||
// Load modules | ||
var Boom = require('boom'); | ||
var Oz = require('./oz'); | ||
var Semver = require('semver'); | ||
var Hawk = require('./hawk'); | ||
@@ -41,10 +41,9 @@ var Bewit = require('./bewit'); | ||
Utils.assert(options && typeof options === 'object', 'Invalid strategy options'); | ||
Utils.assert(!options.scheme || ['oz', 'basic', 'hawk', 'cookie', 'bewit'].indexOf(options.scheme) !== -1, name + ' has an unknown scheme: ' + options.scheme); | ||
Utils.assert(!options.scheme || ['basic', 'hawk', 'cookie', 'bewit'].indexOf(options.scheme) !== -1, name, 'has an unknown scheme:', options.scheme); | ||
Utils.assert(options.scheme || options.implementation, name + ' missing both scheme and extension implementation'); | ||
Utils.assert(!options.implementation || (typeof options.implementation === 'object' && typeof options.implementation.authenticate === 'function'), name + ' has invalid extension scheme implementation'); | ||
Utils.assert(!options.defaultMode || !this._defaultStrategy.name, 'Cannot set default required strategy more than once: ' + name + ' (already set to: ' + this._defaultStrategy + ')'); | ||
Utils.assert(!options.implementation || (typeof options.implementation === 'object' && typeof options.implementation.authenticate === 'function'), name, 'has invalid extension scheme implementation'); | ||
Utils.assert(!options.defaultMode || !this._defaultStrategy.name, 'Cannot set default required strategy more than once:', name, '- already set to:', this._defaultStrategy); | ||
options.scheme = options.scheme || 'ext'; | ||
switch (options.scheme) { | ||
case 'oz': this._strategies[name] = new Oz(this.server, options); break; | ||
case 'hawk': this._strategies[name] = new Hawk(this.server, options); break; | ||
@@ -65,5 +64,3 @@ case 'basic': this._strategies[name] = new Basic(this.server, options); break; | ||
this._defaultStrategy.name = name; | ||
if (typeof options.defaultMode === 'string') { | ||
this._defaultStrategy.mode = options.defaultMode; | ||
} | ||
this._defaultStrategy.mode = (typeof options.defaultMode === 'string' ? options.defaultMode : 'required'); | ||
} | ||
@@ -102,13 +99,13 @@ }; | ||
if (typeof options === 'string') { | ||
options = { strategy: options }; | ||
options = { strategies: [options] }; | ||
} | ||
else if (typeof options === 'boolean') { | ||
options = { strategy: 'default' }; | ||
options = { strategies: ['default'] }; | ||
} | ||
options.mode = options.mode || 'required'; | ||
Utils.assert(['required', 'optional', 'try'].indexOf(options.mode) !== -1, 'Unknown authentication mode: ' + options.mode); | ||
Utils.assert(['required', 'optional', 'try'].indexOf(options.mode) !== -1, 'Unknown authentication mode:', options.mode); | ||
Utils.assert(!options.entity || ['user', 'app', 'any'].indexOf(options.entity) !== -1, 'Unknown authentication entity type: ' + options.entity); | ||
Utils.assert(!options.payload || ['required', 'optional'].indexOf(options.payload) !== -1, 'Unknown authentication payload mode: ' + options.entity); | ||
Utils.assert(!options.entity || ['user', 'app', 'any'].indexOf(options.entity) !== -1, 'Unknown authentication entity type:', options.entity); | ||
Utils.assert(!options.payload || ['required', 'optional'].indexOf(options.payload) !== -1, 'Unknown authentication payload mode:', options.entity); | ||
Utils.assert(!(options.strategy && options.strategies), 'Route can only have a auth.strategy or auth.strategies (or use the default) but not both'); | ||
@@ -123,3 +120,3 @@ Utils.assert(!options.strategies || options.strategies.length, 'Cannot have empty auth.strategies array'); | ||
Utils.assert(self._strategies[strategy], 'Unknown authentication strategy: ' + strategy); | ||
Utils.assert(self._strategies[strategy], 'Unknown authentication strategy:', strategy); | ||
hasAuthenticatePayload = hasAuthenticatePayload || typeof self._strategies[strategy].authenticatePayload === 'function'; | ||
@@ -264,5 +261,5 @@ Utils.assert(options.payload !== 'required' || hasAuthenticatePayload, 'Payload validation can only be required when all strategies support it'); | ||
if (config.scope && | ||
(!credentials.scope || credentials.scope.indexOf(config.scope) === -1)) { | ||
(!credentials || !credentials.scope || credentials.scope.indexOf(config.scope) === -1)) { | ||
request._log(['auth', 'scope', 'error'], { got: credentials.scope, need: config.scope }); | ||
request._log(['auth', 'scope', 'error'], { got: credentials && credentials.scope, need: config.scope }); | ||
return next(Boom.forbidden('Insufficient scope (\'' + config.scope + '\' expected)')); | ||
@@ -273,7 +270,7 @@ } | ||
var tos = (config.hasOwnProperty('tos') ? config.tos : null); | ||
var tos = (config.hasOwnProperty('tos') ? config.tos : false); | ||
if (tos && | ||
(!credentials.tos || credentials.tos < tos)) { | ||
(!credentials || !credentials.tos || !Semver.satisfies(credentials.tos, tos))) { | ||
request._log(['auth', 'tos', 'error'], { min: tos, received: credentials.tos }); | ||
request._log(['auth', 'tos', 'error'], { min: tos, received: credentials && credentials.tos }); | ||
return next(Boom.forbidden('Insufficient TOS accepted')); | ||
@@ -297,3 +294,3 @@ } | ||
if (entity === 'user') { | ||
if (!credentials.user) { | ||
if (!credentials || !credentials.user) { | ||
request._log(['auth', 'error'], 'User credentials required'); | ||
@@ -310,3 +307,3 @@ return next(Boom.forbidden('Application credentials cannot be used on a user endpoint')); | ||
if (credentials.user) { | ||
if (credentials && credentials.user) { | ||
request._log(['auth', 'error'], 'App credentials required'); | ||
@@ -370,2 +367,2 @@ return next(Boom.forbidden('User credentials cannot be used on an application endpoint')); | ||
request.auth._strategy.responseHeader(request, response, next); | ||
}; | ||
}; |
@@ -15,3 +15,6 @@ // Load modules | ||
pack: { | ||
cache: 'redis' | ||
cache: 'redis', | ||
app: { | ||
'app-specific': 'value' | ||
} | ||
} | ||
@@ -34,9 +37,6 @@ servers: [ | ||
plugins: { | ||
furball: { | ||
furball: [{ ext: true }, { | ||
version: false, | ||
plugins: '/' | ||
} | ||
}, | ||
permissions: { | ||
ext: true | ||
}] | ||
} | ||
@@ -92,3 +92,3 @@ }]; | ||
sets.push({ pack: pack, plugins: set.plugins, permissions: set.permissions }); | ||
sets.push({ pack: pack, plugins: set.plugins }); | ||
self._packs.push(pack); | ||
@@ -101,3 +101,3 @@ }); | ||
set.pack.allow(set.permissions || {}).require(set.plugins, next); | ||
set.pack.require(set.plugins, next); | ||
}, | ||
@@ -121,3 +121,3 @@ function (err) { | ||
Utils.assert(!err, 'Failed starting plugins: ' + (err && err.message)); | ||
Utils.assert(!err, 'Failed starting plugins:', err && err.message); | ||
return callback(); | ||
@@ -128,4 +128,9 @@ }); | ||
internals.Composer.prototype.stop = function (callback) { | ||
internals.Composer.prototype.stop = function (options, callback) { | ||
if (typeof options === 'function') { | ||
callback = options; | ||
options = {}; | ||
} | ||
callback = callback || function () { }; | ||
@@ -135,9 +140,9 @@ | ||
pack.stop(next); | ||
pack.stop(options, next); | ||
}, | ||
function (err) { | ||
Utils.assert(!err, 'Failed stopping plugins: ' + (err && err.message)); | ||
Utils.assert(!err, 'Failed stopping plugins:', err && err.message); | ||
return callback(); | ||
}); | ||
}; |
@@ -36,5 +36,10 @@ // Load modules | ||
failAction: 'error', // Action on bad cookie - 'error': return 400, 'log': log and continue, 'ignore': continue | ||
clearInvalid: false // Automatically instruct the client to remove the invalid cookie | ||
clearInvalid: false, // Automatically instruct the client to remove the invalid cookie | ||
strictHeader: true // Require an RFC 6265 compliant header format | ||
} | ||
}, | ||
// Location | ||
location: '', // Base uri used to prefix non-absolute outgoing Location headers ('http://example.com:8080'). Must not contain trailing '/'. | ||
@@ -56,5 +61,5 @@ // Payload | ||
timeout: { | ||
socket: null, // Determines how long before closing request socket. Defaults to node (2 minutes) | ||
socket: undefined, // Determines how long before closing request socket. Defaults to node (2 minutes) | ||
client: 10 * 1000, // Determines how long to wait for receiving client payload. Defaults to 10 seconds | ||
server: null // Determines how long to wait for server request processing. Disabled by default | ||
server: false // Determines how long to wait for server request processing. Disabled by default | ||
}, | ||
@@ -79,3 +84,3 @@ | ||
cors: false, // CORS headers on responses and OPTIONS requests (defaults: exports.cors): false -> null, true -> defaults, {} -> override defaults | ||
views: null, // Presentation engine (defaults: exports.views) | ||
views: null, // Views engine | ||
auth: {} // Authentication | ||
@@ -105,2 +110,7 @@ }; | ||
additionalMethods: [], | ||
exposedHeaders: [ | ||
'WWW-Authenticate', | ||
'Server-Authorization' | ||
], | ||
additionalExposedHeaders: [], | ||
credentials: false | ||
@@ -129,3 +139,3 @@ }; | ||
encoding: 'none' // options: 'base64json', 'base64', 'form', 'none' | ||
encoding: 'none' // options: 'base64json', 'base64', 'form', 'iron', 'none' | ||
}; | ||
@@ -137,17 +147,16 @@ | ||
exports.views = { | ||
engines: { | ||
'html': { | ||
module: 'handlebars', | ||
extension: 'html', | ||
cache: {}, | ||
map: {} | ||
} | ||
}, | ||
defaultExtension: '', | ||
path: '', | ||
basePath: '', | ||
compileOptions: {}, | ||
runtimeOptions: {}, | ||
layout: false, | ||
layoutKeyword: 'content', | ||
encoding: 'utf-8', | ||
cache: {}, | ||
allowAbsolutePaths: null, | ||
allowInsecureAccess: null | ||
isCached: true, | ||
allowAbsolutePaths: false, | ||
allowInsecureAccess: false, | ||
partialsPath: '', | ||
contentType: 'text/html' | ||
}; | ||
@@ -42,3 +42,3 @@ // Load modules | ||
Utils.assert(['onRequest', 'onPreAuth', 'onPostAuth', 'onPreHandler', 'onPostHandler', 'onPreResponse'].indexOf(event) !== -1, 'Unknown event type: ' + event); | ||
Utils.assert(['onRequest', 'onPreAuth', 'onPostAuth', 'onPreHandler', 'onPostHandler', 'onPreResponse'].indexOf(event) !== -1, 'Unknown event type', event); | ||
@@ -51,5 +51,5 @@ // Validate rules | ||
Utils.assert(before.indexOf(group) === -1, 'Plugin ext cannot come before itself (' + group + ')'); | ||
Utils.assert(before.indexOf(group) === -1, 'Plugin ext cannot come before itself:', group); | ||
Utils.assert(before.indexOf('?') === -1, 'Plugin ext cannot come before unassociated exts'); | ||
Utils.assert(after.indexOf(group) === -1, 'Plugin ext cannot come after itself (' + group + ')'); | ||
Utils.assert(after.indexOf(group) === -1, 'Plugin ext cannot come after itself:', group); | ||
Utils.assert(after.indexOf('?') === -1, 'Plugin ext cannot come after unassociated exts'); | ||
@@ -77,3 +77,3 @@ | ||
var error = this.sort(event); | ||
Utils.assert(!error, event + ' extension' + (plugin ? ' add by ' + plugin : '') + ' created a dependencies error'); | ||
Utils.assert(!error, event, 'extension', (plugin ? 'add by ' + plugin : ''), 'created a dependencies error'); | ||
}; | ||
@@ -86,3 +86,3 @@ | ||
if (!handlers) { | ||
return callback(); | ||
return Utils.nextTick(callback)(); | ||
} | ||
@@ -92,7 +92,7 @@ | ||
internals.Ext.runProtected(request._log.bind(request), event, next, function (run, protectedNext) { | ||
internals.Ext.runProtected(request._log.bind(request), event, next, function (enter, exit) { | ||
run(function () { | ||
enter(function () { | ||
ext.func(request, protectedNext); | ||
ext.func(request, exit); | ||
}); | ||
@@ -108,7 +108,7 @@ }); | ||
internals.Ext.runProtected = function (log, tags, callback, setup) { | ||
internals.Ext.runProtected = function (log, tags, next, setup) { // setup: function (enter, exit) | ||
var domain = Domain.createDomain(); | ||
// Ensure only one callback returned | ||
// Ensure only one next returned | ||
@@ -119,3 +119,3 @@ var isFinished = false; | ||
if (isFinished) { | ||
log(['duplicate', 'callback', 'error'].concat(tags || [])); | ||
log(['duplicate', 'next', 'error'].concat(tags || [])); | ||
return; | ||
@@ -127,3 +127,3 @@ } | ||
domain.exit(); | ||
return callback.apply(null, arguments); | ||
return next.apply(null, arguments); | ||
}; | ||
@@ -135,3 +135,2 @@ | ||
domain.dispose(); | ||
log(['uncaught'].concat(tags || []), err); // 'uncaught' treated special in request._log | ||
@@ -141,3 +140,3 @@ return finish(Boom.internal('Uncaught error', err)); | ||
// Execute functon | ||
// Execute function | ||
@@ -144,0 +143,0 @@ domain.enter(); |
@@ -7,2 +7,3 @@ // Load modules | ||
var Response = require('./response'); | ||
var Directory = require('./response/directory'); | ||
@@ -116,3 +117,3 @@ // Declare internals | ||
var response = new Response.Directory(paths, { | ||
var response = new Directory(paths, { | ||
@@ -119,0 +120,0 @@ resource: request.path, |
@@ -1,38 +0,19 @@ | ||
// Declare internals | ||
var internals = { | ||
modules: { | ||
error: require('boom'), | ||
boom: require('boom'), | ||
server: require('./server'), | ||
response: require('./response'), | ||
utils: require('./utils'), | ||
types: require('joi').Types, | ||
pack: require('./pack'), | ||
composer: require('./composer') | ||
} | ||
}; | ||
// Export public modules | ||
internals.export = function () { | ||
exports.error = exports.Error = exports.boom = exports.Boom = require('boom'); | ||
exports.Server = require('./server'); | ||
exports.response = require('./response'); | ||
exports.utils = require('./utils'); | ||
exports.types = require('joi').types; | ||
exports.Pack = require('./pack'); | ||
exports.Composer = require('./composer'); | ||
for (var key in internals.modules) { | ||
if (internals.modules.hasOwnProperty(key)) { | ||
module.exports[key] = module.exports[key.charAt(0).toUpperCase() + key.slice(1)] = internals.modules[key]; | ||
} | ||
} | ||
exports.state = { | ||
prepareValue: require('./state').prepareValue | ||
}; | ||
module.exports.createServer = function () { | ||
exports.createServer = function () { | ||
return new module.exports.Server(arguments[0], arguments[1], arguments[2]); | ||
}; | ||
module.exports.state = { | ||
prepareValue: require('./state').prepareValue | ||
}; | ||
return new exports.Server(arguments[0], arguments[1], arguments[2]); | ||
}; | ||
internals.export(); | ||
132
lib/pack.js
@@ -35,3 +35,3 @@ // Load modules | ||
this.settings = { | ||
this._settings = { | ||
requirePath: options.requirePath || process.cwd() + '/node_modules' | ||
@@ -47,2 +47,3 @@ }; | ||
this.events = new Events.EventEmitter(); // Consolidated subscription to all servers' events | ||
this.app = options.app || {}; | ||
@@ -114,4 +115,10 @@ var cacheOptions = Catbox.defaults.apply(options.cache || Defaults.cache); | ||
var callback = (arguments.length === 3 ? arguments[2] : arguments[1]); | ||
var permissions = internals.defaultPermissions; | ||
this._register(plugin, internals.defaultPermissions, options, callback); | ||
if (options instanceof Array) { | ||
permissions = Utils.applyToDefaults(permissions, options[0] || {}); | ||
options = options[1] || null; | ||
} | ||
this._register(plugin, permissions, options, callback); | ||
}; | ||
@@ -128,3 +135,3 @@ | ||
Utils.assert(callback, 'Missing callback'); | ||
Utils.assert(!this._env[plugin.name], 'Plugin already registered: ' + plugin.name); | ||
Utils.assert(!this._env[plugin.name], 'Plugin already registered:', plugin.name); | ||
Utils.assert(plugin.name, 'Plugin missing name'); | ||
@@ -156,3 +163,8 @@ Utils.assert(plugin.name !== '?', 'Plugin name cannot be \'?\''); | ||
length: selection.servers.length, | ||
select: function (/* labels */) { | ||
var labels = Utils.flatten(Array.prototype.slice.call(arguments)); | ||
return step(labels, selection.index); | ||
}, | ||
api: function (/* key, value */) { | ||
@@ -173,18 +185,2 @@ | ||
}); | ||
}, | ||
select: function (/* labels */) { | ||
var labels = Utils.flatten(Array.prototype.slice.call(arguments)); | ||
return step(labels, selection.index); | ||
}, | ||
dependency: function (deps) { | ||
dependencies[plugin.name] = dependencies[plugin.name] || []; | ||
deps = [].concat(deps); | ||
deps.forEach(function (dep) { | ||
if (!self._env[dep]) { | ||
dependencies[plugin.name].push(dep); | ||
} | ||
}); | ||
} | ||
@@ -214,6 +210,2 @@ }; | ||
if (permissions.events) { | ||
methods.events = self.events; | ||
} | ||
if (permissions.ext) { | ||
@@ -232,5 +224,38 @@ methods.ext = function () { | ||
var root = step(); | ||
root.version = Utils.version; | ||
root.version = Utils.version(); | ||
root.hapi = require('./'); | ||
root.app = self.app; | ||
root.path = plugin.path; | ||
root.log = function (tags, data, timestamp) { | ||
tags = (tags instanceof Array ? tags : [tags]); | ||
var now = (timestamp ? (timestamp instanceof Date ? timestamp : new Date(timestamp)) : new Date()); | ||
var event = { | ||
timestamp: now.getTime(), | ||
tags: tags, | ||
data: data | ||
}; | ||
self.events.emit('log', event, Utils.mapToObject(event.tags)); | ||
}; | ||
root.dependency = function (deps) { | ||
dependencies[plugin.name] = dependencies[plugin.name] || []; | ||
deps = [].concat(deps); | ||
deps.forEach(function (dep) { | ||
if (!self._env[dep]) { | ||
dependencies[plugin.name].push(dep); | ||
} | ||
}); | ||
}; | ||
if (permissions.events) { | ||
root.events = self.events; | ||
} | ||
if (permissions.views) { | ||
@@ -253,5 +278,5 @@ root.views = function (options) { | ||
if (permissions.cache) { | ||
root.cache = function (options, segment) { | ||
root.cache = function (options) { | ||
return self._provisionCache(options, 'plugin', plugin.name, segment); | ||
return self._provisionCache(options, 'plugin', plugin.name, options.segment); | ||
}; | ||
@@ -267,3 +292,3 @@ } | ||
Utils.assert(!dependencies[plugin.name].length, 'Plugin \'' + plugin.name + '\' missing dependencies: ' + dependencies[plugin.name].join(', ')); | ||
Utils.assert(!dependencies[plugin.name].length, 'Plugin', plugin.name, 'missing dependencies:', dependencies[plugin.name].join(', ')); | ||
} | ||
@@ -332,4 +357,10 @@ | ||
var callback = (arguments.length === 3 ? arguments[2] : arguments[1]); | ||
var permissions = internals.defaultPermissions; | ||
this._require(name, internals.defaultPermissions, options, callback); | ||
if (options instanceof Array) { | ||
permissions = Utils.applyToDefaults(permissions, options[0] || {}); | ||
options = options[1] || null; | ||
} | ||
this._require(name, permissions, options, callback); | ||
}; | ||
@@ -352,3 +383,3 @@ | ||
if (typeof name === 'string') { | ||
registrations.push({ name: name, options: options }); | ||
registrations.push({ name: name, options: options, permissions: permissions }); | ||
} | ||
@@ -358,3 +389,3 @@ else if (name instanceof Array) { | ||
registrations.push({ name: item, options: null }); | ||
registrations.push({ name: item, options: null, permissions: permissions }); | ||
}); | ||
@@ -365,3 +396,14 @@ } | ||
registrations.push({ name: item, options: name[item] }); | ||
var reg = { | ||
name: item, | ||
options: name[item], | ||
permissions: permissions | ||
}; | ||
if (reg.options instanceof Array) { | ||
reg.permissions = Utils.applyToDefaults(reg.permissions, reg.options[0] || {}); | ||
reg.options = reg.options[1] || null; | ||
} | ||
registrations.push(reg); | ||
}); | ||
@@ -381,3 +423,3 @@ } | ||
Utils.assert(self._env[dep], 'Plugin \'' + deps + '\' missing dependencies: ' + dep); | ||
Utils.assert(self._env[dep], 'Plugin', deps, 'missing dependencies:', dep); | ||
}); | ||
@@ -397,3 +439,3 @@ }); | ||
else if (itemName[0] !== '/') { | ||
itemName = Path.join(self.settings.requirePath, itemName); | ||
itemName = Path.join(self._settings.requirePath, itemName); | ||
} | ||
@@ -413,3 +455,3 @@ | ||
self._register(plugin, permissions, item.options, next, dependencies); | ||
self._register(plugin, item.permissions, item.options, next, dependencies); | ||
}; | ||
@@ -428,5 +470,3 @@ | ||
var stackLine = stack[i]; | ||
if (stackLine[3] === 'internals.Pack.require' || | ||
stackLine[3] === 'internals.Pack.allow.scoped.require') { // The file that calls require is next | ||
if (stackLine[3].lastIndexOf('.require') === stackLine[3].length - 8) { // The file that calls require is next | ||
callerFile = stack[i + 1][0]; | ||
@@ -452,7 +492,19 @@ break; | ||
self._register(name, rights, callback ? options : null, callback || options); | ||
var pluginPermissions = rights; | ||
if (options instanceof Array) { | ||
pluginPermissions = Utils.applyToDefaults(pluginPermissions, options[0] || {}); | ||
options = options[1] || null; | ||
} | ||
self._register(name, pluginPermissions, callback ? options : null, callback || options); | ||
}, | ||
require: function (name, options, callback) { | ||
self._require(name, rights, callback ? options : null, callback || options); | ||
var pluginPermissions = rights; | ||
if (options instanceof Array) { | ||
pluginPermissions = Utils.applyToDefaults(pluginPermissions, options[0] || {}); | ||
options = options[1] || null; | ||
} | ||
self._require(name, pluginPermissions, callback ? options : null, callback || options); | ||
} | ||
@@ -512,3 +564,3 @@ }; | ||
Utils.assert(name, 'Invalid cache policy name'); | ||
Utils.assert(['helper', 'route', 'plugin'].indexOf(type) !== -1, 'Unknown cache policy type: ' + type); | ||
Utils.assert(['helper', 'route', 'plugin'].indexOf(type) !== -1, 'Unknown cache policy type:', type); | ||
@@ -515,0 +567,0 @@ if (type === 'helper') { |
@@ -7,2 +7,3 @@ // Load modules | ||
var Boom = require('boom'); | ||
var Utils = require('./utils'); | ||
@@ -22,4 +23,2 @@ | ||
var req = request.raw.req; | ||
if (request.method === 'get' || | ||
@@ -33,3 +32,3 @@ request.method === 'head') { | ||
var level = request.route.payload; | ||
var level = request.route.payload || (request.route.validate.payload || request.method === 'post' || request.method === 'put' ? 'parse' : 'stream'); | ||
if (level === 'stream') { | ||
@@ -41,2 +40,3 @@ return next(); | ||
var req = request.raw.req; | ||
var contentLength = req.headers['content-length']; | ||
@@ -74,6 +74,2 @@ if (contentLength && | ||
var isGZip = (req.headers['content-encoding'] === 'gzip'); | ||
var encoding = isGZip ? 'base64' : 'utf8'; | ||
req.setEncoding(encoding); | ||
req.on('close', function () { | ||
@@ -89,10 +85,18 @@ | ||
var payload = ''; | ||
req.on('data', function (chunk) { | ||
var buffers = []; | ||
var buffersLength = 0 | ||
if (payload.length + chunk.length > request.server.settings.payload.maxBytes) { | ||
req.on('readable', function () { | ||
var chunk = req.read(); | ||
if (!chunk) { | ||
return; | ||
} | ||
if (buffersLength + chunk.length > request.server.settings.payload.maxBytes) { | ||
return finish(Boom.badRequest('Payload size greater than maximum allowed: ' + request.server.settings.payload.maxBytes)); | ||
} | ||
payload += chunk.toString(encoding); | ||
buffers.push(chunk); | ||
buffersLength += chunk.length; | ||
}); | ||
@@ -102,24 +106,33 @@ | ||
if (isBailed) { | ||
return; // next() already called | ||
} | ||
var review = function () { | ||
var unzip = function () { | ||
if (isBailed) { | ||
return; // next() already called | ||
} | ||
var payloadBuf = new Buffer(payload, encoding); | ||
if (!internals.isDeflate(payloadBuf) && !internals.isGzip(payloadBuf)) { | ||
return finish(Boom.badRequest('Invalid compressed payload')); | ||
var payload = Buffer.concat(buffers, buffersLength); | ||
if (payload.length && | ||
(req.headers['content-encoding'] === 'gzip' || req.headers['content-encoding'] === 'deflate')) { | ||
if (!internals.isDeflate(payload) && !internals.isGzip(payload)) { | ||
return finish(Boom.badRequest('Invalid compressed payload')); | ||
} | ||
Zlib.unzip(payload, function (err, unzipped) { // Err shouldn't exist since the buffer is validated above | ||
return parse(unzipped); | ||
}); | ||
return; | ||
} | ||
Zlib.unzip(payloadBuf, function (err, buffer) { // Err shouldn't exist since the buffer is validated above | ||
var unzipped = buffer.toString(); | ||
request.rawBody = unzipped; | ||
return parse(unzipped); | ||
}); | ||
parse(payload); | ||
}; | ||
var parse = function (result) { | ||
var parse = function (payload) { | ||
if (level !== 'parse') { // 'raw' | ||
request.rawPayload = payload; | ||
if (level !== 'parse') { // 'raw' | ||
return finish(); | ||
@@ -130,3 +143,3 @@ } | ||
if (!result) { | ||
if (!payload.length) { | ||
return finish(); | ||
@@ -137,3 +150,3 @@ } | ||
internals.parse(result, req.headers, function (err, payload) { | ||
internals.parse(payload, req.headers, function (err, result) { | ||
@@ -144,3 +157,3 @@ if (err) { | ||
request.payload = payload; | ||
request.payload = result; | ||
return finish(); | ||
@@ -150,11 +163,4 @@ }); | ||
if (isGZip) { | ||
return unzip(); | ||
} | ||
request.rawBody = payload; | ||
parse(payload); | ||
review(); | ||
}); | ||
req.resume(); | ||
}; | ||
@@ -175,4 +181,6 @@ | ||
internals.parse = function (result, headers, callback) { | ||
internals.parse = function (payload, headers, callback) { | ||
callback = Utils.nextTick(callback); | ||
// Check content type | ||
@@ -186,3 +194,3 @@ | ||
if (mime.match(/^text\/.+$/)) { | ||
return callback(result); | ||
return callback(payload.toString('utf-8')); | ||
} | ||
@@ -197,3 +205,3 @@ | ||
try { | ||
obj = JSON.parse(result); | ||
obj = JSON.parse(payload.toString('utf-8')); | ||
} | ||
@@ -210,3 +218,3 @@ catch (exp) { | ||
if (mime === 'application/x-www-form-urlencoded') { | ||
return callback(null, Querystring.parse(result)); | ||
return callback(null, Querystring.parse(payload.toString('utf-8'))); | ||
} | ||
@@ -247,3 +255,3 @@ | ||
form.writeHeaders(headers); | ||
form.write(new Buffer(result)); | ||
form.write(payload); | ||
return; | ||
@@ -250,0 +258,0 @@ } |
@@ -72,9 +72,9 @@ // Load modules | ||
if (self.settings.isCustomPostResponse || // Custom response method | ||
(isGet && request.route.cache.mode.server)) { // GET/HEAD with Cache | ||
// Parsed payload interface | ||
// Callback interface | ||
if (self.settings.isCustomPostResponse || // Custom response method | ||
(isGet && request.route.cache.mode.server)) { // GET/HEAD with Cache | ||
delete options.headers['accept-encoding']; // Remove until Request supports unzip/deflate | ||
self.httpClient(options, function (err, response, payload) { | ||
self.httpClient(options, function (err, res, payload) { | ||
@@ -87,29 +87,28 @@ // Request handles all redirect responses (3xx) and will return an err if redirection fails | ||
return self.settings.postResponse(request, self.settings, response, payload); | ||
return self.settings.postResponse(request, self.settings, res, payload); | ||
}); | ||
return; | ||
} | ||
else { | ||
// Stream interface | ||
// Streamed payload interface | ||
if (!isGet && | ||
request.rawBody) { | ||
if (!isGet && | ||
request.rawPayload && | ||
request.rawPayload.length) { | ||
options.headers['Content-Type'] = req.headers['content-type']; | ||
options.body = request.rawBody; | ||
} | ||
options.headers['Content-Type'] = req.headers['content-type']; | ||
options.body = request.rawPayload; | ||
} | ||
var reqStream = self.httpClient(options); | ||
var reqStream = self.httpClient(options); | ||
reqStream.on('response', function (resStream) { | ||
reqStream.on('response', function (resStream) { | ||
request.reply(resStream); // Request._respond will pass-through headers and status code | ||
}); | ||
request.reply(resStream); // Request._respond will pass-through headers and status code | ||
}); | ||
if (!isGet && | ||
!request.rawPayload) { | ||
if (!isGet && | ||
request.route.payload === 'stream') { | ||
request.raw.req.pipe(reqStream); | ||
request.raw.req.resume(); | ||
} | ||
request.raw.req.pipe(reqStream); | ||
} | ||
@@ -127,5 +126,5 @@ }); | ||
return function (request, callback) { | ||
return function (request, next) { | ||
return callback(null, baseUrl + request.path + (request.url.search || '')); | ||
return next(null, baseUrl + request.path + (request.url.search || '')); | ||
}; | ||
@@ -135,6 +134,6 @@ }; | ||
internals.postResponse = function (request, settings, response, payload) { | ||
internals.postResponse = function (request, settings, res, payload) { | ||
var contentType = response.headers['content-type']; | ||
var statusCode = response.statusCode; | ||
var contentType = res.headers['content-type']; | ||
var statusCode = res.statusCode; | ||
@@ -145,8 +144,6 @@ if (statusCode >= 400) { | ||
response = request.reply.payload(payload); | ||
var response = request.reply(payload); | ||
if (contentType) { | ||
response.type(contentType); | ||
} | ||
return response.send(); | ||
}; |
@@ -34,11 +34,7 @@ // Load modules | ||
// Pause request | ||
req.pause(); // Must be done before switching event execution context | ||
// Public members | ||
this.server = server; | ||
this._setUrl(req.url); // Sets: url, path, query | ||
this._setMethod(req.method); // Sets: method | ||
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); | ||
@@ -57,3 +53,3 @@ | ||
this.session = null; // Special key used by cookie auth as well as other extension session-based schemes (only one allowed) | ||
this.session = null; // Special key reserved for plugins implementing session support | ||
@@ -63,4 +59,5 @@ this.pre = {}; | ||
received: now, | ||
address: req.connection && req.connection.remoteAddress, | ||
referrer: req.headers.referrer || req.headers.referer | ||
address: (req.connection && req.connection.remoteAddress) || '', | ||
referrer: req.headers.referrer || req.headers.referer || '', | ||
host: req.headers.host ? req.headers.host.replace(/\s/g, '') : '' | ||
}; | ||
@@ -76,15 +73,14 @@ | ||
// query | ||
// params | ||
// rawBody | ||
// payload | ||
// this.query | ||
// this.params | ||
// this.rawPayload | ||
// this.payload | ||
// this.state | ||
// state | ||
// this.setUrl() | ||
// this.setMethod() | ||
// setUrl() | ||
// setMethod() | ||
// this.reply(): { hold(), send(), close(), raw(), payload(), stream(), redirect(), view() } | ||
// this.response() | ||
// reply(): { send(), close(), raw(), payload(), stream(), redirect(), view() } | ||
// response() | ||
// Semi-public members | ||
@@ -97,7 +93,6 @@ | ||
this.setState = this._setState; // Remove once replied | ||
this.clearState = this._clearState; | ||
this.setState = this._setState; // Remove once replied | ||
this.clearState = this._clearState; // Remove once replied | ||
this.tail = this.addTail = this._addTail; // Removed once wagging | ||
this.addTail = this._addTail; // Removed once wagging | ||
// Private members | ||
@@ -206,3 +201,3 @@ | ||
if (this.server.listeners('request').length == 1 && // Pack always listening | ||
if (this.server.listeners('request').length === 1 && // Pack always listening | ||
this.server.settings.debug && | ||
@@ -212,3 +207,6 @@ this.server.settings.debug.request && | ||
console.error(data.stack); | ||
console.error('Unmonitored error: ' + item.tags.join(', ')); | ||
if (data) { | ||
console.error(data instanceof Error ? data.stack : data); | ||
} | ||
} | ||
@@ -220,7 +218,8 @@ }; | ||
if (tags === undefined) { // Other falsy values are legal tags | ||
tags = [].concat(tags || []); | ||
if (!tags.length) { | ||
return this._logger; | ||
} | ||
var filter = Utils.mapToObject(tags instanceof Array ? tags : [tags]); | ||
var filter = Utils.mapToObject(tags); | ||
var result = []; | ||
@@ -325,2 +324,7 @@ | ||
if (self._isReplied) { | ||
self._log(['server', 'timeout']); | ||
return next(true); // Argument is ignored but aborts the series | ||
} | ||
func(self, next); | ||
@@ -342,3 +346,3 @@ }, | ||
if (this._isReplied) { // Prevent any future responses to this request | ||
return callback(); | ||
return Utils.nextTick(callback)(); | ||
} | ||
@@ -356,3 +360,3 @@ | ||
self.raw.res.end(); // End the response in case it wasn't already closed | ||
return finalize(); | ||
return Utils.nextTick(finalize)(); | ||
} | ||
@@ -392,3 +396,3 @@ | ||
self._setResponse(Response.generate(value)); | ||
self._setResponse(Response._generate(value)); | ||
}; | ||
@@ -411,2 +415,3 @@ | ||
delete self.addTail; | ||
delete self.tail; | ||
@@ -444,3 +449,3 @@ if (Object.keys(self._tails).length === 0) { | ||
internals.Request.prototype._replyInterface = function (callback, withProperties) { | ||
internals.Request.prototype._replyInterface = function (next, withProperties) { | ||
@@ -452,2 +457,3 @@ var self = this; | ||
var response = null; | ||
var wasProcessed = false; | ||
@@ -458,4 +464,8 @@ // Chain finalizers | ||
delete self.reply; | ||
return callback(response); | ||
if (wasProcessed) { | ||
return; | ||
} | ||
wasProcessed = true; | ||
return next(response); | ||
}; | ||
@@ -465,5 +475,7 @@ | ||
Utils.assert(result instanceof Stream === false || !self.route.cache.mode.server, 'Cannot reply using a stream when caching enabled'); | ||
response = Response.generate(result); | ||
process(); | ||
delete self.reply; | ||
Utils.assert(!self.route.cache.mode.server || result instanceof Stream === false, 'Cannot reply using a stream when caching enabled'); | ||
response = Response._generate(result, process); | ||
return response; | ||
}; | ||
@@ -475,13 +487,7 @@ | ||
reply.send = function () { | ||
if (response === null) { | ||
response = Response.generate(null); | ||
} | ||
process(); | ||
}; | ||
if (!this.route.cache.mode.server) { | ||
reply.close = function () { | ||
delete self.reply; | ||
response = new Closed(); | ||
@@ -494,28 +500,10 @@ process(); | ||
reply.stream = function (stream) { | ||
reply.redirect = function (uri) { | ||
Utils.assert(stream instanceof Stream, 'request.reply.stream() requires a stream'); | ||
response = Response.generate(stream, process); | ||
return response; | ||
}; | ||
delete self.reply; | ||
reply.payload = function (result) { | ||
Utils.assert(result instanceof Stream === false, 'Must use request.reply.stream() with a Stream object'); | ||
response = Response.generate(result, process); | ||
response = Response._generate(new Response.Redirection(uri), process); | ||
return response; | ||
}; | ||
reply.raw = function (result) { | ||
response = Response.generate(new Response.Raw(self), process); | ||
return response; | ||
}; | ||
reply.redirect = function (uri) { | ||
response = Response.generate(new Response.Redirection(uri), process); | ||
return response; | ||
}; | ||
if (this.server._views || | ||
@@ -526,4 +514,6 @@ this._route.env.views) { | ||
delete self.reply; | ||
var viewsManager = self._route.env.views || self.server._views; | ||
response = Response.generate(new Response.View(viewsManager, template, context, options), process); | ||
response = Response._generate(new Response.View(viewsManager, template, context, options), process); | ||
return response; | ||
@@ -547,5 +537,5 @@ }; | ||
return function (request, callback) { | ||
return function (request, next) { | ||
Ext.runProtected(request._log.bind(request), 'pre', callback, function (run, next) { | ||
Ext.runProtected(request._log.bind(request), 'pre', next, function (enter, exit) { | ||
@@ -557,3 +547,3 @@ var timer = new Utils.Timer(); | ||
request._log(['pre', 'error'], { msec: timer.elapsed(), assign: pre.assign, mode: pre.mode, error: result }); | ||
return next(result); | ||
return exit(result); | ||
} | ||
@@ -566,6 +556,6 @@ | ||
return next(); | ||
return exit(); | ||
}; | ||
run(function () { | ||
enter(function () { | ||
@@ -622,3 +612,3 @@ pre.method(request, finalize); | ||
return callback(); | ||
return Utils.nextTick(callback)(); | ||
} | ||
@@ -657,3 +647,3 @@ | ||
Ext.runProtected(request._log.bind(request), 'handler', callback, function (run, next) { | ||
Ext.runProtected(request._log.bind(request), 'handler', callback, function (enter, exit) { | ||
@@ -670,11 +660,7 @@ var timer = new Utils.Timer(); | ||
request._log(['handler', 'error'], { msec: timer.elapsed() }); | ||
return next(response); | ||
return exit(response); | ||
} | ||
if (request._route.cache.rule.strict) { | ||
Utils.assert(response.varieties.cacheable, 'Attempted to cache non-cacheable item'); // Caught by the runProtected | ||
} | ||
request._log(['handler'], { msec: timer.elapsed() }); | ||
return next(null, response); | ||
return exit(null, response); | ||
}; | ||
@@ -684,3 +670,3 @@ | ||
run(function () { | ||
enter(function () { | ||
@@ -750,8 +736,2 @@ request.reply = request._replyInterface(finalize, true); | ||
internals.Request.prototype.removeTail = function (dropFunc) { | ||
dropFunc(); | ||
}; | ||
internals.Request.prototype._setState = function (name, value, options) { | ||
@@ -765,3 +745,3 @@ | ||
else { | ||
Response.Base.prototype.state.call(this, name, value, options); | ||
Response.Generic.prototype.state.call(this, name, value, options); | ||
} | ||
@@ -779,3 +759,3 @@ }; | ||
else { | ||
Response.Base.prototype.unstate.call(this, name); | ||
Response.Generic.prototype.unstate.call(this, name); | ||
} | ||
@@ -782,0 +762,0 @@ }; |
@@ -12,3 +12,3 @@ // Load modules | ||
// Buffer response (Base -> Generic -> Buffer) | ||
// Buffer response (Generic -> Buffer) | ||
@@ -15,0 +15,0 @@ exports = module.exports = internals.Buffer = function (buffer) { |
@@ -12,3 +12,3 @@ // Load modules | ||
// Cacheable response (Base -> Generic -> Cacheable) | ||
// Cacheable response (Generic -> Cacheable) | ||
@@ -15,0 +15,0 @@ exports = module.exports = internals.Cacheable = function () { |
@@ -12,3 +12,3 @@ // Load modules | ||
// Cached response (Base -> Generic -> Cacheable -> Cached) | ||
// Cached response (Generic -> Cacheable -> Cached) | ||
@@ -15,0 +15,0 @@ exports = module.exports = internals.Cached = function (item, ttl) { |
@@ -9,3 +9,3 @@ // Load modules | ||
// Base response | ||
// Manually Closed response | ||
@@ -12,0 +12,0 @@ exports = module.exports = internals.Closed = function () { |
@@ -17,3 +17,3 @@ // Load modules | ||
// File response (Base -> Generic -> Cacheable -> Directory) | ||
// File response (Generic -> Cacheable -> Directory) | ||
@@ -20,0 +20,0 @@ exports = module.exports = internals.Directory = function (paths, options) { |
@@ -12,3 +12,3 @@ // Load modules | ||
// Empty response (Base -> Generic -> Cacheable -> Empty) | ||
// Empty response (Generic -> Cacheable -> Empty) | ||
@@ -15,0 +15,0 @@ exports = module.exports = internals.Empty = function () { |
@@ -12,3 +12,3 @@ // Load modules | ||
// Error response (Base -> Generic -> Cacheable -> Obj -> Error) | ||
// Error response (Generic -> Cacheable -> Obj -> Error) | ||
@@ -15,0 +15,0 @@ exports = module.exports = internals.Error = function (options) { |
@@ -8,3 +8,3 @@ // Load modules | ||
var Utils = require('../utils'); | ||
var Stream = require('./stream'); | ||
var StreamResponse = require('./stream'); | ||
var LruCache = require('lru-cache'); | ||
@@ -21,3 +21,3 @@ var Crypto = require('crypto'); | ||
// File response (Base -> Generic -> Stream -> File) | ||
// File response (Generic -> Stream -> File) | ||
@@ -29,3 +29,3 @@ exports = module.exports = internals.File = function (filePath, options) { | ||
Stream.call(this, null); | ||
StreamResponse.call(this, null); | ||
this.variety = 'file'; | ||
@@ -40,3 +40,3 @@ this.varieties.file = true; | ||
Utils.inherits(internals.File, Stream); | ||
Utils.inherits(internals.File, StreamResponse); | ||
@@ -61,6 +61,3 @@ | ||
var fileName = Path.basename(self._filePath); | ||
var stream = Fs.createReadStream(self._filePath); | ||
Stream.call(self, stream); | ||
self._headers['Content-Type'] = Mime.lookup(self._filePath) || 'application/octet-stream'; | ||
@@ -81,3 +78,3 @@ self._headers['Content-Length'] = stat.size; | ||
var hash = Crypto.createHash('sha1'); | ||
stream.on('data', function (chunk) { | ||
self._preview.on('peek', function (chunk, encoding) { | ||
@@ -87,3 +84,3 @@ hash.update(chunk); | ||
stream.on('end', function () { | ||
self._preview.on('finish', function () { | ||
@@ -99,4 +96,12 @@ var etag = hash.digest('hex'); | ||
return Stream.prototype._prepare.call(self, request, callback); | ||
return StreamResponse.prototype._prepare.call(self, request, callback); | ||
}); | ||
}; | ||
}; | ||
internals.File.prototype._transmit = function (request, callback) { | ||
this._setStream(Fs.createReadStream(this._filePath)); | ||
return StreamResponse.prototype._transmit.call(this, request, callback); | ||
}; | ||
// Load modules | ||
var Stream = require('stream'); | ||
var Async = require('async'); | ||
var Negotiator = require('negotiator'); | ||
var Zlib = require('zlib'); | ||
var Base = require('./base'); | ||
var Headers = require('./headers'); | ||
@@ -17,3 +17,3 @@ var Utils = require('../utils'); | ||
// Generic response (Base -> Generic) | ||
// Generic response (Generic) | ||
@@ -24,9 +24,15 @@ exports = module.exports = internals.Generic = function () { | ||
Base.call(this); | ||
this.variety = 'generic'; | ||
this.varieties = {}; | ||
this.varieties.generic = true; | ||
this._flags = {}; // Cached | ||
this._states = {}; // Not cached | ||
this._wasPrepared = false; | ||
this._code = 200; | ||
this._payload = []; | ||
this._headers = {}; | ||
this._preview = new internals.Peek(); | ||
@@ -36,11 +42,3 @@ return this; | ||
Utils.inherits(internals.Generic, Base); | ||
internals.Generic.prototype.getTtl = function () { | ||
return this._code === 200 ? this._flags.ttl : 0; | ||
}; | ||
internals.Generic.prototype._prepare = function (request, callback) { | ||
@@ -162,5 +160,7 @@ | ||
if (Buffer.isBuffer(chunk)) { | ||
self._preview.write(chunk); | ||
request.raw.res.write(chunk); | ||
} | ||
else { | ||
self._preview.write(chunk, self._flags.encoding); | ||
request.raw.res.write(chunk, self._flags.encoding); | ||
@@ -171,5 +171,9 @@ } | ||
request.raw.res.end(); | ||
self._preview.end(); | ||
callback(); | ||
setImmediate(function () { | ||
request.raw.res.end(); | ||
callback(); | ||
}); | ||
}; | ||
@@ -181,2 +185,9 @@ | ||
internals.Generic.prototype.code = function (statusCode) { | ||
this._code = statusCode; | ||
return this; | ||
}; | ||
internals.Generic.prototype.header = function (key, value) { | ||
@@ -209,1 +220,61 @@ | ||
}; | ||
internals.Generic.prototype.ttl = function (ttl) { | ||
this._flags.ttl = ttl; | ||
return this; | ||
}; | ||
internals.Generic.prototype.getTtl = function () { | ||
return this._code === 200 ? this._flags.ttl : 0; | ||
}; | ||
internals.Generic.prototype.state = function (name, value, options) { // options: see Defaults.state | ||
var state = { | ||
name: name, | ||
value: value | ||
}; | ||
if (options) { | ||
Utils.assert(!options.autoValue, 'Cannot set autoValue directly in a response'); | ||
state.options = Utils.clone(options); | ||
} | ||
this._states[name] = state; | ||
return this; | ||
}; | ||
internals.Generic.prototype.unstate = function (name) { | ||
var state = { | ||
name: name, | ||
options: { | ||
ttl: 0 | ||
} | ||
}; | ||
this._states[name] = state; | ||
return this; | ||
}; | ||
internals.Peek = function () { | ||
Stream.Writable.call(this); | ||
return this; | ||
}; | ||
Utils.inherits(internals.Peek, Stream.Writable); | ||
internals.Peek.prototype._write = function (chunk, encoding, callback) { | ||
this.emit('peek', chunk, encoding); | ||
callback(); | ||
}; |
// Load modules | ||
var State = require('../state'); | ||
var Utils = require('../utils'); | ||
var Auth = null; // Delay load due to circular dependencies | ||
@@ -15,3 +16,4 @@ | ||
var isAbsolute = (uri.match(/^\w+\:\/\//)); | ||
return (isAbsolute ? uri : request.server.settings.uri + (uri.charAt(0) === '/' ? '' : '/') + uri); | ||
var baseUri = request.server.settings.location || (request.server.info.protocol + '://' + (request.info.host || (request.server.info.host + ':' + request.server.info.port))); | ||
return (isAbsolute || !baseUri ? uri : baseUri + (uri.charAt(0) === '/' ? '' : '/') + uri); | ||
}; | ||
@@ -75,2 +77,3 @@ | ||
response.header('Access-Control-Allow-Headers', request.server.settings.cors._headers); | ||
response.header('Access-Control-Expose-Headers', request.server.settings.cors._exposedHeaders); | ||
@@ -132,3 +135,3 @@ if (request.server.settings.cors.credentials) { | ||
if (!states.length) { | ||
return callback(); | ||
return Utils.nextTick(callback)(); | ||
} | ||
@@ -135,0 +138,0 @@ |
@@ -17,15 +17,15 @@ // Load modules | ||
/* | ||
/-- Raw /-- Stream -----|--- File | ||
Base --| | | ||
\-- Generic --|--- Buffer /-- Text ----|-- Redirection | ||
| | | ||
\-- Cacheable --|--- Empty | ||
| | ||
|-- Directory | ||
| | ||
|--- Object --|-- Error | ||
| | ||
|--- Cached | ||
| | ||
\-- View | ||
/-- Stream -----|--- File | ||
| | ||
Generic --|--- Buffer /-- Text ----|-- Redirection | ||
| | | ||
\-- Cacheable --|--- Empty | ||
| | ||
|-- Directory | ||
| | ||
|--- Object --|-- Error | ||
| | ||
|--- Cached | ||
| | ||
\-- View | ||
*/ | ||
@@ -35,3 +35,2 @@ | ||
exports.Base = internals.Base = require('./base'); | ||
exports.Generic = internals.Generic = require('./generic'); | ||
@@ -47,4 +46,2 @@ exports.Cacheable = internals.Cacheable = require('./cacheable'); | ||
exports.File = internals.File = require('./file'); | ||
exports.Directory = internals.Directory = require('./directory'); | ||
exports.Raw = internals.Raw = require('./raw'); | ||
exports.Redirection = internals.Redirection = require('./redirection'); | ||
@@ -62,3 +59,3 @@ exports.View = internals.View = require('./view'); | ||
exports.generate = function (result, onSend) { | ||
exports._generate = function (result, onSend) { | ||
@@ -97,3 +94,10 @@ var response = null; | ||
if (onSend) { | ||
if (!onSend) { | ||
return response; | ||
} | ||
response.hold = function () { | ||
delete response.hold; | ||
response.send = function () { | ||
@@ -104,4 +108,16 @@ | ||
}; | ||
} | ||
return response; | ||
}; | ||
process.nextTick(function () { | ||
delete response.hold; | ||
if (!response.send) { | ||
delete response.hold; | ||
onSend(); | ||
} | ||
}); | ||
return response; | ||
@@ -171,3 +187,3 @@ }; | ||
var unchanged = new internals.Empty(); | ||
unchanged._code = 304; | ||
unchanged.code(304); | ||
return prepare(unchanged); | ||
@@ -192,3 +208,3 @@ } | ||
var unchanged = new internals.Empty(); | ||
unchanged._code = 304; | ||
unchanged.code(304); | ||
return prepare(unchanged); | ||
@@ -195,0 +211,0 @@ } |
// Load modules | ||
var Cacheable = require('./cacheable'); | ||
var SafeStringify = require('json-stringify-safe') | ||
var Utils = require('../utils'); | ||
@@ -12,3 +11,3 @@ | ||
// Obj response (Base -> Generic -> Cacheable -> Obj) | ||
// Obj response (Generic -> Cacheable -> Obj) | ||
@@ -34,5 +33,5 @@ exports = module.exports = internals.Obj = function (object, type, encoding) { | ||
this._payload = [SafeStringify(this.raw)]; | ||
this._payload = [JSON.stringify(this.raw)]; | ||
this._headers['Content-Type'] = type || this._headers['Content-Type'] || 'application/json'; | ||
this._flags.encoding = encoding || 'utf-8'; | ||
}; |
@@ -12,7 +12,7 @@ // Load modules | ||
// Redirection response (Base -> Generic -> Cacheable -> Text -> Redirection) | ||
// Redirection response (Generic -> Cacheable -> Text -> Redirection) | ||
exports = module.exports = internals.Redirection = function (uri, message, type) { | ||
exports = module.exports = internals.Redirection = function (uri, message, type, encoding) { | ||
Text.call(this, message || 'You are being redirected...', type); | ||
Text.call(this, message || 'You are being redirected...', type, encoding); | ||
this.variety = 'redirection'; | ||
@@ -23,4 +23,4 @@ this.varieties.redirection = true; | ||
this._code = 302; // Defaults to temporary/rewritable | ||
this._flags.location = uri; | ||
this._code = 302; // Defaults to temporary/rewritable | ||
this.uri(uri); | ||
@@ -42,3 +42,3 @@ return this; | ||
this.setTemporary(isTemporary !== false); // Defaults to true | ||
this._setTemporary(isTemporary !== false); // Defaults to true | ||
return this; | ||
@@ -50,3 +50,3 @@ }; | ||
this.setTemporary(isPermanent === false); // Defaults to true | ||
this._setTemporary(isPermanent === false); // Defaults to true | ||
return this; | ||
@@ -58,3 +58,3 @@ }; | ||
this.setRewritable(isRewritable !== false); // Defaults to true | ||
this._setRewritable(isRewritable !== false); // Defaults to true | ||
return this; | ||
@@ -64,3 +64,3 @@ }; | ||
internals.Redirection.prototype.isTemporary = function () { | ||
internals.Redirection.prototype._isTemporary = function () { | ||
@@ -71,3 +71,3 @@ return this._code === 302 || this._code === 307; | ||
internals.Redirection.prototype.isRewritable = function () { | ||
internals.Redirection.prototype._isRewritable = function () { | ||
@@ -78,6 +78,6 @@ return this._code === 301 || this._code === 302; | ||
internals.Redirection.prototype.setTemporary = function (isTemporary) { | ||
internals.Redirection.prototype._setTemporary = function (isTemporary) { | ||
if (isTemporary) { | ||
if (this.isRewritable()) { | ||
if (this._isRewritable()) { | ||
this._code = 302; | ||
@@ -90,3 +90,3 @@ } | ||
else { | ||
if (this.isRewritable()) { | ||
if (this._isRewritable()) { | ||
this._code = 301; | ||
@@ -101,6 +101,6 @@ } | ||
internals.Redirection.prototype.setRewritable = function (isRewritable) { | ||
internals.Redirection.prototype._setRewritable = function (isRewritable) { | ||
if (isRewritable) { | ||
if (this.isTemporary()) { | ||
if (this._isTemporary()) { | ||
this._code = 302; | ||
@@ -113,3 +113,3 @@ } | ||
else { | ||
if (this.isTemporary()) { | ||
if (this._isTemporary()) { | ||
this._code = 307; | ||
@@ -116,0 +116,0 @@ } |
// Load modules | ||
var Negotiator = require('negotiator'); | ||
var Stream = require('stream'); | ||
var Zlib = require('zlib'); | ||
var Negotiator = require('negotiator'); | ||
var Generic = require('./generic'); | ||
@@ -16,3 +16,3 @@ var Headers = require('./headers'); | ||
// Stream response (Base -> Generic -> Stream) | ||
// Stream response (Generic -> Stream) | ||
@@ -27,3 +27,4 @@ exports = module.exports = internals.Stream = function (stream) { | ||
this.stream = stream; | ||
this._passThrough = {}; | ||
this._setStream(stream); | ||
@@ -43,2 +44,37 @@ return this; | ||
internals.Stream.prototype._setStream = function (stream) { | ||
if (!stream) { | ||
this._stream = null; | ||
return; | ||
} | ||
// Check if stream is a node HTTP response (stream.*) or a (mikeal's) Request object (stream.response.*) | ||
if (stream.statusCode || | ||
(stream.response && stream.response.statusCode)) { | ||
this._passThrough.code = stream.statusCode || stream.response.statusCode; | ||
} | ||
if (stream.headers || | ||
(stream.response && stream.response.headers)) { | ||
this._passThrough.headers = stream.headers || stream.response.headers; | ||
} | ||
// Support pre node v0.10 streams API | ||
if (stream.pipe === Stream.prototype.pipe) { | ||
var oldStream = stream; | ||
oldStream.pause(); | ||
stream = new Stream.Readable().wrap(stream); | ||
oldStream.resume(); | ||
} | ||
this._stream = stream; | ||
}; | ||
internals.Stream.prototype._prepare = function (request, callback) { | ||
@@ -74,23 +110,22 @@ | ||
// Check if stream is a node HTTP response (stream.*) or a (mikeal's) Request object (stream.response.*) | ||
// Apply passthrough code and headers | ||
if (!request._route.proxy || | ||
request._route.proxy.settings.passThrough) { // Pass headers only if not proxy or proxy with pass-through set | ||
if (this._passThrough.headers && | ||
(!request._route.proxy || request._route.proxy.settings.passThrough)) { | ||
var responseHeaders = this.stream.response ? this.stream.response.headers : this.stream.headers; | ||
if (responseHeaders) { | ||
if (this._passThrough.headers) { | ||
var localCookies = Utils.clone(this._headers['Set-Cookie']); | ||
var localHeaders = this._headers; | ||
this._headers = Utils.clone(responseHeaders); | ||
this._headers = Utils.clone(this._passThrough.headers); | ||
Utils.merge(this._headers, localHeaders); | ||
if (localCookies) { | ||
var headerKeys = Object.keys(responseHeaders); | ||
var headerKeys = Object.keys(this._passThrough.headers); | ||
for (var i = 0, il = headerKeys.length; i < il; ++i) { | ||
if (headerKeys[i].toLowerCase() === 'set-cookie') { | ||
delete this._headers[headerKeys[i]]; | ||
this._headers['Set-Cookie'] = [].concat(responseHeaders[headerKeys[i]]).concat(localCookies); | ||
break; | ||
} | ||
if (headerKeys[i].toLowerCase() === 'set-cookie') { | ||
delete this._headers[headerKeys[i]]; | ||
this._headers['Set-Cookie'] = [].concat(this._passThrough.headers[headerKeys[i]]).concat(localCookies); | ||
break; | ||
} | ||
}; | ||
@@ -101,3 +136,5 @@ } | ||
this._code = this.stream.statusCode || ((this.stream.response && this.stream.response.code) ? this.stream.response.code : this._code); | ||
if (this._passThrough.code) { | ||
this._code = this._passThrough.code; | ||
} | ||
@@ -145,39 +182,14 @@ var encoder = null; | ||
isEnded = true; | ||
self._preview.end(); | ||
request.raw.res.end(); | ||
self._stream.destroy && self._stream.destroy(); | ||
callback(); | ||
}; | ||
request.raw.req.once('close', function () { | ||
request.raw.req.once('close', end); | ||
this._stream.once('error', end); | ||
(encoder || this._stream).once('end', end); | ||
if (self.stream.destroy) { | ||
self.stream.destroy.bind(self.stream); | ||
} | ||
end(); | ||
}); | ||
this.stream.on('error', function () { | ||
request.raw.req.destroy(); | ||
end(); | ||
}); | ||
if (encoder) { | ||
encoder.once('end', function () { | ||
end(); | ||
}); | ||
this.stream.resume(); | ||
this.stream.pipe(encoder).pipe(request.raw.res); | ||
} | ||
else { | ||
this.stream.once('end', function () { | ||
end(); | ||
}); | ||
this.stream.resume(); | ||
this.stream.pipe(request.raw.res); | ||
} | ||
}; | ||
this._stream.pipe(this._preview); | ||
(encoder ? this._stream.pipe(encoder) : this._stream).pipe(request.raw.res, { end: false }); | ||
}; |
@@ -12,5 +12,5 @@ // Load modules | ||
// Text response (Base -> Generic -> Cacheable -> Text) | ||
// Text response (Generic -> Cacheable -> Text) | ||
exports = module.exports = internals.Text = function (text, type) { | ||
exports = module.exports = internals.Text = function (text, type, encoding) { | ||
@@ -21,3 +21,3 @@ Cacheable.call(this); | ||
this.message(text, type); | ||
this.message(text, type, encoding); | ||
@@ -32,3 +32,3 @@ return this; | ||
if (text) { | ||
if (text !== null) { | ||
this._payload = [text]; | ||
@@ -35,0 +35,0 @@ } |
@@ -12,3 +12,3 @@ // Load modules | ||
// View response (Base -> Generic -> Cacheable -> View) | ||
// View response (Generic -> Cacheable -> View) | ||
@@ -40,11 +40,16 @@ module.exports = internals.View = function (manager, template, context, options) { | ||
if (rendered instanceof Error) { | ||
return callback(rendered); | ||
return Utils.nextTick(callback)(rendered); | ||
} | ||
this._payload = [rendered]; | ||
this._headers['Content-Type'] = (this.view.manager.settings.engine && this.view.manager.settings.engine['Content-Type']) || 'text/html'; | ||
this._flags.encoding = (this.view.manager.settings.engine && this.view.manager.settings.engine.encoding) || 'utf-8'; | ||
this._payload = [rendered.result]; | ||
if (rendered.config.contentType) { | ||
this._headers['Content-Type'] = rendered.config.contentType; | ||
} | ||
if (rendered.config.encoding) { | ||
this._flags.encoding = rendered.config.encoding; | ||
} | ||
return Cacheable.prototype._prepare.call(this, request, callback); | ||
}; | ||
@@ -33,6 +33,7 @@ // Load modules | ||
Schema.route(options, this.settings, function (err) { | ||
var schemaError = Schema.routeOptions(options); | ||
Utils.assert(!schemaError, 'Invalid route options for', options.path, ':', schemaError); | ||
Utils.assert(!err, 'Route options are invalid: ' + err); | ||
}); | ||
schemaError = Schema.routeConfig(this.settings); | ||
Utils.assert(!schemaError, 'Invalid route config for', options.path, ':', schemaError); | ||
@@ -42,6 +43,7 @@ this.server = server; | ||
this.method = options.method.toLowerCase(); | ||
Utils.assert(this.method !== 'head', 'Cannot add HEAD route'); | ||
this.path = options.path; | ||
this.settings.method = this.method; // Expose method in settings | ||
Utils.assert(this.path.match(internals.Route.validatePathRegex), 'Invalid path: ' + this.path); | ||
Utils.assert(this.path.match(internals.Route.validatePathRegex), 'Invalid path:', this.path); | ||
Utils.assert(this.path.match(internals.Route.validatePathEncodedRegex) === null, 'Path cannot contain encoded non-reserved path characters'); | ||
@@ -57,6 +59,2 @@ | ||
Utils.assert(!this.settings.validate.payload || !this.settings.payload || this.settings.payload === 'parse', 'Route payload must be set to \'parse\' when payload validation enabled'); | ||
this.settings.payload = this.settings.payload || | ||
(this.settings.validate.payload || this.method === 'post' || this.method === 'put' ? 'parse' : 'stream'); | ||
Utils.assert(!this.settings.jsonp || typeof this.settings.jsonp === 'string', 'Bad route JSONP parameter name'); | ||
@@ -81,3 +79,3 @@ | ||
Utils.assert(mode === 'client' || mode === 'server', 'Unknown cache mode: ' + mode); | ||
Utils.assert(mode === 'client' || mode === 'server', 'Unknown cache mode:', mode); | ||
modes[mode] = true; | ||
@@ -90,3 +88,3 @@ }); | ||
this.settings.cache.privacy = this.settings.cache.privacy || 'default'; | ||
this.cache = (this.settings.cache.mode.server ? this.server.plugin._provisionCache(this.settings.cache, 'route', this.fingerprint, this.settings.cache.segment) : | ||
this.cache = (this.settings.cache.mode.server ? this.server.pack._provisionCache(this.settings.cache, 'route', this.fingerprint, this.settings.cache.segment) : | ||
new Catbox.Policy(this.settings.cache)); | ||
@@ -132,3 +130,3 @@ } | ||
pre.mode = pre.mode || 'serial'; | ||
Utils.assert(pre.mode === 'serial' || pre.mode === 'parallel', 'Unknown prerequisite mode: ' + pre.mode); | ||
Utils.assert(pre.mode === 'serial' || pre.mode === 'parallel', 'Unknown prerequisite mode:', pre.mode); | ||
@@ -135,0 +133,0 @@ if (typeof pre.method === 'string') { |
@@ -46,3 +46,3 @@ // Load modules | ||
var method = (request.method === 'head' ? 'get' : request.method); | ||
var vhost = (request.raw.req.headers.host && this.vhosts[request.raw.req.headers.host.split(':')[0].trim()]); | ||
var vhost = (request.info.host && this.vhosts[request.info.host.split(':')[0]]); | ||
@@ -49,0 +49,0 @@ var lookup = function (table, verb) { |
@@ -16,78 +16,8 @@ // Load modules | ||
// Validate route options | ||
exports.route = function (options, config, next) { | ||
var error = Joi.validate(options, internals.routeOptionsSchema); | ||
if (error) { | ||
return next(error.annotated()); | ||
} | ||
error = Joi.validate(config, internals.routeConfigSchema); | ||
return next(error ? error.annotated() : null); | ||
}; | ||
internals.routeOptionsSchema = { | ||
method: T.String().invalid('head').required(), | ||
path: T.String().required(), | ||
vhost: [T.String().optional(), T.Array().optional()], | ||
handler: [T.Object().optional(), T.Function().optional(), T.String().valid('notFound').optional()], | ||
config: T.Object().optional().nullOk() | ||
}; | ||
internals.routeConfigSchema = { | ||
handler: [T.Object(), T.Function(), T.String().valid('notFound').optional()], | ||
payload: T.String().valid('stream', 'raw', 'parse').nullOk(), | ||
response: T.Object({ | ||
schema: T.Object().nullOk().optional(), | ||
sample: T.Number().optional(), | ||
failAction: T.String().optional().valid('error', 'log', 'ignore') | ||
}).optional().nullOk().allow(true).allow(false), | ||
auth: [ | ||
T.Object({ | ||
mode: T.String().valid(['required', 'optional', 'try']).nullOk(), | ||
scope: T.String().nullOk(), | ||
tos: T.Number().nullOk(), | ||
entity: T.String().nullOk(), | ||
strategy: T.String().nullOk(), | ||
strategies: T.Array().nullOk(), | ||
payload: [T.String().nullOk(), T.Boolean()] | ||
}).optional().nullOk(), | ||
T.Boolean().allow(false).optional().nullOk(), | ||
T.String().optional().nullOk() | ||
], | ||
validate: T.Object({ | ||
payload: T.Object().optional().nullOk().allow(true).allow(false), | ||
query: T.Object().optional().nullOk().allow(true).allow(false), | ||
path: T.Object().optional().nullOk().allow(true).allow(false) | ||
}).optional().nullOk(), | ||
cache: T.Object({ | ||
mode: T.String().optional().valid(['server+client', 'client+server', 'client', 'server']), | ||
segment: T.String().optional(), | ||
privacy: T.String().optional().valid('default', 'public', 'private'), | ||
expiresIn: T.Number().optional().without('expiresAt'), | ||
expiresAt: T.String().optional().without('expiresIn'), | ||
staleIn: T.Number().optional().with('staleTimeout'), | ||
staleTimeout: T.Number().optional().with(), | ||
strict: T.Boolean().optional() | ||
}).optional().nullOk(), | ||
jsonp: T.String().optional(), | ||
pre: T.Array().optional().nullOk(), | ||
app: T.Object().optional().nullOk(), | ||
plugins: T.Object().optional().nullOk(), | ||
description: T.String().optional(), | ||
notes: [T.String().optional(), T.Array()], | ||
tags: [T.String().optional(), T.Array()] | ||
}; | ||
// Validate server options | ||
exports.server = function (options, next) { | ||
exports.server = function (options) { | ||
var error = Joi.validate(options, internals.serverSchema); | ||
return next(error ? error.annotated() : null); | ||
return (error ? error.annotated() : null); | ||
}; | ||
@@ -97,7 +27,14 @@ | ||
internals.serverSchema = { | ||
nickname: T.String().optional(), | ||
host: T.String().optional(), | ||
port: T.Number().optional(), | ||
uri: T.String().optional(), | ||
app: T.Object().nullOk(), | ||
auth: T.Object().nullOk().allow(false).allow(true), | ||
cache: [T.String().nullOk(), T.Object({ | ||
engine: T.String().required(), | ||
partition: T.String(), | ||
host: T.String(), | ||
port: T.Number(), | ||
username: T.String(), | ||
password: T.String(), | ||
poolSize: T.Number(), | ||
maxByteSize: T.Number() | ||
})], | ||
cors: T.Object({ | ||
@@ -110,4 +47,18 @@ origin: T.Array(), | ||
additionalMethods: T.Array(), | ||
exposedHeaders: T.Array(), | ||
additionalExposedHeaders: T.Array(), | ||
credentials: T.Boolean() | ||
}).nullOk().allow(false).allow(true), | ||
debug: T.Object({ | ||
request: T.Array() | ||
}).nullOk().allow(false), | ||
files: T.Object({ | ||
relativeTo: T.String() | ||
}).nullOk().allow(false).allow(true), | ||
labels: T.Array().nullOk(), | ||
location: T.String().emptyOk(), | ||
payload: T.Object({ | ||
maxBytes: T.Number() | ||
}).nullOk().allow(false).allow(true), | ||
plugins: T.Object().nullOk(), | ||
router: T.Object({ | ||
@@ -121,11 +72,6 @@ isCaseSensitive: T.Boolean(), | ||
failAction: T.String(), | ||
clearInvalid: T.Boolean() | ||
clearInvalid: T.Boolean(), | ||
strictHeader: T.Boolean() | ||
}).nullOk() | ||
}).nullOk().allow(false).allow(true), | ||
payload: T.Object({ | ||
maxBytes: T.Number() | ||
}).nullOk().allow(false).allow(true), | ||
files: T.Object({ | ||
relativeTo: T.String() | ||
}).nullOk().allow(false).allow(true), | ||
timeout: T.Object({ | ||
@@ -136,32 +82,117 @@ socket: T.Number().nullOk().allow(false).allow(true), | ||
}).nullOk().allow(false).allow(true), | ||
debug: T.Object({ | ||
request: T.Array() | ||
}).nullOk().allow(false), | ||
tls: T.Object().nullOk().allow(false).allow(true).optional(), | ||
tls: T.Object().nullOk(), | ||
views: T.Object({ | ||
path: T.String().optional().nullOk(), | ||
engines: T.Object().optional().nullOk(), | ||
engine: T.Object().optional().nullOk(), | ||
compileOptions: T.Object().optional().nullOk(), | ||
layout: T.Boolean().optional().nullOk(), | ||
layoutKeyword: T.String().optional().nullOk(), | ||
encoding: T.String().optional().nullOk(), | ||
cache: T.Object().optional().nullOk().allow(true).allow(false), | ||
allowAbsolutePaths: T.Boolean().nullOk().optional(), | ||
allowInsecureAccess: T.Boolean().nullOk().optional(), | ||
partials: T.Object().optional().nullOk() | ||
}).nullOk().allow(false).allow(true).optional(), | ||
cache: [T.String().nullOk().optional(), T.Object({ | ||
engine: T.String().required(), | ||
partition: T.String(), | ||
host: T.String(), | ||
port: T.Number(), | ||
username: T.String(), | ||
password: T.String(), | ||
poolSize: T.Number(), | ||
maxByteSize: T.Number() | ||
}).optional()], | ||
app: T.Object().optional().nullOk(), | ||
plugins: T.Object().optional().nullOk(), | ||
labels: T.Array().optional().nullOk() | ||
engines: T.Object().required(), | ||
defaultExtension: T.String(), | ||
path: T.String(), | ||
basePath: T.String(), | ||
compileOptions: T.Object(), | ||
runtimeOptions: T.Object(), | ||
layout: T.Boolean(), | ||
layoutKeyword: T.String(), | ||
encoding: T.String(), | ||
isCached: T.Boolean(), | ||
allowAbsolutePaths: T.Boolean(), | ||
allowInsecureAccess: T.Boolean(), | ||
partialsPath: T.String(), | ||
contentType: T.String() | ||
}).nullOk() | ||
}; | ||
// Validate route options | ||
exports.routeOptions = function (options) { | ||
error = Joi.validate(options, internals.routeOptionsSchema); | ||
return (error ? error.annotated() : null); | ||
}; | ||
internals.routeOptionsSchema = { | ||
method: T.String().required(), | ||
path: T.String().required(), | ||
vhost: [T.String(), T.Array()], | ||
handler: [T.Object(), T.Function(), T.String().valid('notFound')], | ||
config: T.Object().nullOk() | ||
}; | ||
// Validate route config | ||
exports.routeConfig = function (config) { | ||
error = Joi.validate(config, internals.routeConfigSchema); | ||
return (error ? error.annotated() : null); | ||
}; | ||
internals.routeConfigSchema = { | ||
handler: [T.Object(), T.Function(), T.String().valid('notFound')], | ||
payload: T.String().valid('stream', 'raw', 'parse').nullOk(), | ||
auth: [ | ||
T.Object({ | ||
mode: T.String().valid(['required', 'optional', 'try']).nullOk(), | ||
scope: T.String().nullOk(), | ||
tos: T.String().allow(false).nullOk(), | ||
entity: T.String().nullOk(), | ||
strategy: T.String().nullOk(), | ||
strategies: T.Array().nullOk(), | ||
payload: [T.String().nullOk(), T.Boolean()] | ||
}).nullOk(), | ||
T.Boolean().allow(false).nullOk(), | ||
T.String().nullOk() | ||
], | ||
validate: T.Object({ | ||
payload: T.Object().nullOk().allow(true).allow(false), | ||
query: T.Object().nullOk().allow(true).allow(false), | ||
path: T.Object().nullOk().allow(true).allow(false), | ||
response: T.Object({ | ||
schema: T.Object().nullOk(), | ||
sample: T.Number().min(0).max(100), | ||
failAction: T.String().valid('error', 'log') | ||
}).nullOk().allow(true).allow(false) | ||
}).nullOk(), | ||
cache: T.Object({ | ||
mode: T.String().valid(['server+client', 'client+server', 'client', 'server']), | ||
segment: T.String(), | ||
privacy: T.String().valid('default', 'public', 'private'), | ||
expiresIn: T.Number().xor('expiresAt'), | ||
expiresAt: T.String(), | ||
staleIn: T.Number().with('staleTimeout'), | ||
staleTimeout: T.Number()//.with('staleIn') | ||
}).nullOk(), | ||
jsonp: T.String(), | ||
pre: T.Array().nullOk(), | ||
app: T.Object().nullOk(), | ||
plugins: T.Object().nullOk(), | ||
description: T.String(), | ||
notes: [T.String(), T.Array()], | ||
tags: [T.String(), T.Array()] | ||
}; | ||
exports.view = function (options) { | ||
var error = Joi.validate(options, internals.viewSchema); | ||
return (error ? error.annotated() : null); | ||
}; | ||
internals.viewSchema = { | ||
module: [T.Object({ | ||
compile: T.Function().required() | ||
}).allowOtherKeys().required(), T.String().required()], | ||
path: T.String().emptyOk(), | ||
basePath: T.String().emptyOk(), | ||
compileOptions: T.Object(), | ||
runtimeOptions: T.Object(), | ||
layout: T.Boolean(), | ||
layoutKeyword: T.String(), | ||
encoding: T.String(), | ||
isCached: T.Boolean(), | ||
allowAbsolutePaths: T.Boolean(), | ||
allowInsecureAccess: T.Boolean(), | ||
partialsPath: T.String().emptyOk(), | ||
contentType: T.String() | ||
}; |
@@ -46,3 +46,3 @@ // Load modules | ||
var args = {}; | ||
for (a = 0, al = arguments.length; a < al; ++a) { | ||
for (var a = 0, al = arguments.length; a < al; ++a) { | ||
if (arguments[a] === undefined) { | ||
@@ -59,4 +59,4 @@ continue; | ||
var key = argMap[type]; | ||
Utils.assert(key, 'Bad server constructor arguments: no match for arg type ' + type); | ||
Utils.assert(!args[key], 'Bad server constructor arguments: duplicated arg type: ' + type); | ||
Utils.assert(key, 'Bad server constructor arguments: no match for arg type:', type); | ||
Utils.assert(!args[key], 'Bad server constructor arguments: duplicated arg type:', type); | ||
args[key] = arguments[a]; | ||
@@ -66,18 +66,15 @@ } | ||
this.settings = Utils.applyToDefaults(Defaults.server, args.options || {}); | ||
Schema.server(this.settings, function (err) { | ||
var schemaError = Schema.server(this.settings); | ||
Utils.assert(!schemaError, 'Invalid server options:', schemaError); | ||
Utils.assert(!err, 'Invalid server options: ' + err); | ||
}); | ||
// Set basic configuration | ||
this.settings.host = args.host ? args.host.toLowerCase() : '0.0.0.0'; | ||
this.settings.port = typeof args.port !== 'undefined' ? args.port : (this.settings.tls ? 443 : 80); | ||
if (this.settings.port) { | ||
this.settings.nickname = this.settings.host + ':' + this.settings.port; | ||
this.settings.uri = (this.settings.tls ? 'https://' : 'http://') + this.settings.host + ':' + this.settings.port; | ||
} | ||
this._host = args.host ? args.host.toLowerCase() : ''; | ||
this._port = typeof args.port !== 'undefined' ? args.port : (this.settings.tls ? 443 : 80); | ||
Utils.assert(!this.settings.location || this.settings.location.charAt(this.settings.location.length - 1) !== '/', 'Location setting must not contain a trailing \'/\''); | ||
Utils.assert(this.settings.timeout.server === null || this.settings.timeout.socket === null || this.settings.timeout.server < this.settings.timeout.socket, 'Server timeout must be shorter than socket timeout'); | ||
Utils.assert(this.settings.timeout.client === null || this.settings.timeout.socket === null || this.settings.timeout.client < this.settings.timeout.socket, 'Client timeout must be shorter than socket timeout'); | ||
var socketTimeout = (this.settings.timeout.socket === undefined ? 2 * 60 * 1000 : this.settings.timeout.socket); | ||
Utils.assert(!this.settings.timeout.server || !socketTimeout || this.settings.timeout.server < socketTimeout, 'Server timeout must be shorter than socket timeout'); | ||
Utils.assert(!this.settings.timeout.client || !socketTimeout || this.settings.timeout.client < socketTimeout, 'Client timeout must be shorter than socket timeout'); | ||
@@ -93,7 +90,7 @@ // Server facilities | ||
if (args.pack) { | ||
this.plugin = args.pack; | ||
this.pack = args.pack; | ||
} | ||
else { | ||
this.plugin = new Pack({ cache: this.settings.cache }); | ||
this.plugin._server(this); | ||
this.pack = new Pack({ cache: this.settings.cache }); | ||
this.pack._server(this); | ||
} | ||
@@ -111,2 +108,3 @@ | ||
this.settings.cors._methods = (this.settings.cors.methods || []).concat(this.settings.cors.additionalMethods || []).join(', '); | ||
this.settings.cors._exposedHeaders = (this.settings.cors.exposedHeaders || []).concat(this.settings.cors.additionalExposedHeaders || []).join(', '); | ||
} | ||
@@ -134,2 +132,14 @@ | ||
} | ||
// Server information | ||
this.info = { | ||
host: this._host || '0.0.0.0', | ||
port: this._port || 0, | ||
protocol: (this.settings.tls ? 'https' : 'http') | ||
}; | ||
if (this.info.port) { | ||
this.info.uri = this.info.protocol + '://' + this.info.host + ':' + this.info.port; | ||
} | ||
@@ -152,5 +162,5 @@ return this; | ||
if (req.socket && | ||
self.settings.timeout.socket !== null) { | ||
self.settings.timeout.socket !== undefined) { | ||
req.socket.setTimeout(self.settings.timeout.socket); | ||
req.socket.setTimeout(self.settings.timeout.socket || 0); | ||
} | ||
@@ -184,3 +194,3 @@ | ||
this.plugin.start(callback); | ||
this.pack.start(callback); | ||
}; | ||
@@ -196,13 +206,15 @@ | ||
if (this._started) { | ||
return callback(); | ||
return Utils.nextTick(callback)(); | ||
} | ||
this._started = true; | ||
this._connections = {}; | ||
this.listener.once('listening', function () { | ||
var address = self.listener.address(); // Update the port and uri with what was actually bound | ||
self.settings.port = address.port; | ||
self.settings.host = self.settings.host || address.address; | ||
self.settings.nickname = self.settings.host + ':' + self.settings.port; | ||
self.settings.uri = (self.settings.tls ? 'https://' : 'http://') + self.settings.host + ':' + self.settings.port; | ||
// Update the host, port, and uri with active values | ||
var address = self.listener.address(); | ||
self.info.host = self._host || address.address || '0.0.0.0'; | ||
self.info.port = address.port; | ||
self.info.uri = self.info.protocol + '://' + self.info.host + ':' + self.info.port; | ||
@@ -212,3 +224,14 @@ return callback(); | ||
this.listener.listen(this.settings.port, this.settings.host); | ||
this.listener.on('connection', function(connection) { | ||
var key = connection.remoteAddress + ':' + connection.remotePort; | ||
self._connections[key] = connection; | ||
connection.once('close', function() { | ||
delete self._connections[key]; | ||
}); | ||
}); | ||
this.listener.listen(this._port, this._host); | ||
}; | ||
@@ -221,3 +244,3 @@ | ||
this.plugin.stop(options, callback); | ||
this.pack.stop(options, callback); | ||
}; | ||
@@ -232,10 +255,22 @@ | ||
callback = callback || function () { }; | ||
options.timeout = options.timeout || 5000; // Default timeout to 5 seconds | ||
if (!this._started) { | ||
return callback(); | ||
return Utils.nextTick(callback)(); | ||
} | ||
self._started = false; | ||
var timeoutId = setTimeout(function () { | ||
Object.keys(self._connections).forEach(function (key) { | ||
var connection = self._connections[key]; | ||
return connection && connection.destroy(); | ||
}); | ||
}, options.timeout); | ||
self.listener.close(function () { | ||
self._started = false; | ||
clearTimeout(timeoutId); | ||
callback(); | ||
@@ -297,3 +332,3 @@ }); | ||
Utils.assert(name && typeof name === 'string', 'Invalid name'); | ||
Utils.assert(!this._stateDefinitions[name], 'State already defined: ' + name); | ||
Utils.assert(!this._stateDefinitions[name], 'State already defined:', name); | ||
Utils.assert(!options || !options.encoding || ['base64json', 'base64', 'form', 'iron', 'none'].indexOf(options.encoding) !== -1, 'Bad encoding'); | ||
@@ -311,2 +346,9 @@ | ||
internals.Server.prototype.views = function (options) { | ||
Utils.assert(!this._views, 'Cannot set server views manager more than once'); | ||
this._views = new Views(options); | ||
}; | ||
internals.Server.prototype.inject = function (options, callback) { | ||
@@ -317,3 +359,4 @@ | ||
var onEnd = function (res) { | ||
var needle = this._dispatch(requestOptions); | ||
Shot.inject(needle, options, function (res) { | ||
@@ -328,7 +371,4 @@ if (res.raw.res.hapi) { | ||
callback(res); | ||
}; | ||
var needle = this._dispatch(requestOptions); | ||
Shot.inject(needle, options, onEnd); | ||
return callback(res); | ||
}); | ||
}; | ||
@@ -343,3 +383,3 @@ | ||
Utils.assert(typeof name === 'string', 'name must be a string'); | ||
Utils.assert(name.match(/^\w+$/), 'Invalid name: ' + name); | ||
Utils.assert(name.match(/^\w+$/), 'Invalid name:', name); | ||
Utils.assert(!this.helpers[name], 'Helper function name already exists'); | ||
@@ -357,3 +397,3 @@ Utils.assert(!options || typeof options === 'object', 'options must be an object'); | ||
Utils.assert(!settings.cache.mode, 'Cache mode not allowed in helper configuration (always server side)'); | ||
cache = this.plugin._provisionCache(settings.cache, 'helper', name, settings.cache.segment); | ||
cache = this.pack._provisionCache(settings.cache, 'helper', name, settings.cache.segment); | ||
} | ||
@@ -367,7 +407,7 @@ | ||
var lastArgPos = args.length - 1; | ||
var next = args[lastArgPos]; | ||
var helperNext = args[lastArgPos]; | ||
// Wrap method for Cache.Stale interface 'function (callback) { callback(err, value); }' | ||
// Wrap method for Cache.Stale interface 'function (next) { next(err, value); }' | ||
var generateFunc = function (callback) { | ||
var generateFunc = function (next) { | ||
@@ -377,6 +417,6 @@ args[lastArgPos] = function (result) { | ||
if (result instanceof Error) { | ||
return callback(result); | ||
return next(result); | ||
} | ||
return callback(null, result); | ||
return next(null, result); | ||
}; | ||
@@ -390,7 +430,7 @@ | ||
next(err || result); | ||
helperNext(err || result); | ||
}); | ||
} | ||
var key = settings.generateKey(args); | ||
var key = settings.generateKey.apply(null, args); | ||
if (key === null) { // Value can be '' | ||
@@ -402,3 +442,3 @@ self._log(['helper', 'key', 'error'], { name: name, args: args }); | ||
return next(err || value); | ||
return helperNext(err || value); | ||
}); | ||
@@ -411,7 +451,7 @@ }; | ||
internals.generateKey = function (args) { | ||
internals.generateKey = function () { | ||
var key = ''; | ||
for (var i = 0, il = args.length - 1; i < il; ++i) { // 'args.length - 1' to skip 'next' | ||
var arg = args[i]; | ||
for (var i = 0, il = arguments.length - 1; i < il; ++i) { // 'arguments.length - 1' to skip 'next' | ||
var arg = arguments[i]; | ||
if (typeof arg !== 'string' && | ||
@@ -418,0 +458,0 @@ typeof arg !== 'number' && |
115
lib/state.js
@@ -18,2 +18,9 @@ // Load modules | ||
// Header format | ||
// 1: name 2: quoted value 3: value | ||
internals.strictRx = /\s*([^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)\s*=\s*(?:(?:"([^\x00-\x20\"\,\;\\\x7F]*)")|([^\x00-\x20\"\,\;\\\x7F]*))(?:(?:;|(?:\s*\,)\s*)|$)/g; | ||
internals.looseRx = /\s*([^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)\s*=\s*(?:(?:"([^\"]*)")|([^\;]*))(?:(?:;|(?:\s*\,)\s*)|$)/g; | ||
// Read and parse body | ||
@@ -42,4 +49,5 @@ | ||
// 1: name 2: quoted value 3: value | ||
var verify = cookies.replace(/\s*([^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)\s*=\s*(?:(?:"([^\x00-\x20\"\,\;\\\x7F]*)")|([^\x00-\x20\"\,\;\\\x7F]*))(?:(?:;|(?:\s*\,)\s*)|$)/g, function ($0, $1, $2, $3) { | ||
var state = {}; | ||
var formatRx = (request.server.settings.state.cookies.strictHeader ? internals.strictRx : internals.looseRx); | ||
var verify = cookies.replace(formatRx, function ($0, $1, $2, $3) { | ||
@@ -49,11 +57,11 @@ var name = $1; | ||
if (request.state[name]) { | ||
if (request.state[name] instanceof Array === false) { | ||
request.state[name] = [request.state[name]]; | ||
if (state[name]) { | ||
if (state[name] instanceof Array === false) { | ||
state[name] = [state[name]]; | ||
} | ||
request.state[name].push(value); | ||
state[name].push(value); | ||
} | ||
else { | ||
request.state[name] = value; | ||
state[name] = value; | ||
} | ||
@@ -72,30 +80,14 @@ | ||
parse(); | ||
parse(state); | ||
}; | ||
var shouldStop = function (error, name) { | ||
var parse = function (state) { | ||
if (request.server.settings.state.cookies.clearInvalid) { | ||
request.clearState(name); | ||
} | ||
var parsed = {}; | ||
// failAction: 'error', 'log', 'ignore' | ||
var names = Object.keys(state); | ||
Async.forEachSeries(names, function (name, nextName) { | ||
if (request.server.settings.state.cookies.failAction === 'error') { | ||
next(Boom.badRequest('Bad cookie ' + (name ? 'value: ' + name : 'header'))); | ||
return true; | ||
} | ||
var value = state[name]; | ||
if (request.server.settings.state.cookies.failAction === 'log') { | ||
request._log(['state', 'error'], error); | ||
} | ||
return false; | ||
}; | ||
var parse = function () { | ||
var names = Object.keys(request.state); | ||
Async.forEachSeries(names, function (name, nextName) { | ||
var definition = request.server._stateDefinitions[name]; | ||
@@ -105,7 +97,6 @@ if (!definition || | ||
parsed[name] = value; | ||
return nextName(); | ||
} | ||
var value = request.state[name]; | ||
// Single value | ||
@@ -134,3 +125,3 @@ | ||
request.state[name] = result; | ||
parsed[name] = result; | ||
return nextName(); | ||
@@ -165,4 +156,3 @@ }); | ||
arrayResult.push(arrayValue); // Keep bad value | ||
return nextArray(); | ||
return nextName(); | ||
} | ||
@@ -177,3 +167,3 @@ | ||
request.state[name] = arrayResult; | ||
parsed[name] = arrayResult; | ||
return nextName(); | ||
@@ -186,2 +176,3 @@ }); | ||
request.state = parsed; | ||
return next(); | ||
@@ -193,3 +184,3 @@ }); | ||
var unsign = function (name, value, definition, callback) { | ||
var unsign = function (name, value, definition, innerNext) { | ||
@@ -199,3 +190,3 @@ if (!definition || | ||
return callback(null, value); | ||
return innerNext(null, value); | ||
} | ||
@@ -205,3 +196,3 @@ | ||
if (pos === -1) { | ||
return callback(Boom.internal('Missing signature separator')); | ||
return innerNext(Boom.internal('Missing signature separator')); | ||
} | ||
@@ -213,3 +204,3 @@ | ||
if (!sig) { | ||
return callback(Boom.internal('Missing signature')); | ||
return innerNext(Boom.internal('Missing signature')); | ||
} | ||
@@ -219,3 +210,3 @@ | ||
if (sigParts.length !== 2) { | ||
return callback(Boom.internal('Bad signature format')); | ||
return innerNext(Boom.internal('Bad signature format')); | ||
} | ||
@@ -231,10 +222,10 @@ | ||
if (err) { | ||
return callback(err); | ||
return innerNext(err); | ||
} | ||
if (!Cryptiles.fixedTimeComparison(mac.digest, hmac)) { | ||
return callback(Boom.internal('Bad hmac value')); | ||
return innerNext(Boom.internal('Bad hmac value')); | ||
} | ||
return callback(null, unsigned); | ||
return innerNext(null, unsigned); | ||
}); | ||
@@ -245,3 +236,3 @@ }; | ||
var decode = function (value, definition, callback) { | ||
var decode = function (value, definition, innerNext) { | ||
@@ -254,6 +245,6 @@ // Encodings: 'base64json', 'base64', 'form', 'iron', 'none' | ||
if (err) { | ||
return callback(err); | ||
return innerNext(err); | ||
} | ||
return callback(null, unsealed); | ||
return innerNext(null, unsealed); | ||
}); | ||
@@ -281,8 +272,30 @@ | ||
catch (err) { | ||
return callback(err); | ||
return innerNext(err); | ||
} | ||
return callback(null, result); | ||
return innerNext(null, result); | ||
}; | ||
var shouldStop = function (error, name) { | ||
if (request.server.settings.state.cookies.clearInvalid) { | ||
request.clearState(name); | ||
} | ||
// failAction: 'error', 'log', 'ignore' | ||
if (request.server.settings.state.cookies.failAction === 'log' || | ||
request.server.settings.state.cookies.failAction === 'error') { | ||
request._log(['state', 'error'], error); | ||
} | ||
if (request.server.settings.state.cookies.failAction === 'error') { | ||
next(Boom.badRequest('Bad cookie ' + (name ? 'value: ' + name : 'header'))); | ||
return true; | ||
} | ||
return false; | ||
}; | ||
prepare(); | ||
@@ -306,3 +319,3 @@ }; | ||
return callback(null, []); | ||
return Utils.nextTick(callback)(null, []); | ||
} | ||
@@ -430,2 +443,4 @@ | ||
callback = Utils.nextTick(callback); | ||
// Encodings: 'base64json', 'base64', 'form', 'iron', 'none' | ||
@@ -486,3 +501,3 @@ | ||
return callback(null, value); | ||
return Utils.nextTick(callback)(null, value); | ||
} | ||
@@ -489,0 +504,0 @@ |
@@ -81,6 +81,7 @@ // Load modules | ||
if (request.route.response === null || | ||
request.route.response === undefined || | ||
request.route.response === true || // Value can be false | ||
request.route.response.sample === 0) { | ||
if (request.route.validate.response === null || | ||
request.route.validate.response === undefined || | ||
request.route.validate.response === true || // Value can be false | ||
request.route.validate.response.sample === 0 || | ||
request.route.validate.response.sample === false) { | ||
@@ -90,5 +91,5 @@ return next(); | ||
if (request.route.response.sample) { | ||
if (request.route.validate.response.sample) { | ||
var currentSample = Math.ceil((Math.random() * 100)); | ||
if (currentSample > request.route.response.sample) { | ||
if (currentSample > request.route.validate.response.sample) { | ||
return next(); | ||
@@ -108,12 +109,11 @@ } | ||
var error = Joi.validate(request._response.raw, request.route.response.schema); | ||
var error = Joi.validate(request._response.raw, request.route.validate.response.schema); | ||
// failAction: 'error', 'log', 'ignore' | ||
// failAction: 'error', 'log' | ||
if (!error || | ||
request.route.response.failAction === 'ignore') { | ||
if (!error) { | ||
return next(); | ||
} | ||
if (request.route.response.failAction === 'log') { | ||
if (request.route.validate.response.failAction === 'log') { | ||
request._log(['validation', 'error'], error.message); | ||
@@ -120,0 +120,0 @@ return next(); |
414
lib/views.js
@@ -5,5 +5,6 @@ // Load modules | ||
var Path = require('path'); | ||
var Boom = require('boom'); | ||
var Defaults = require('./defaults'); | ||
var Schema = require('./schema'); | ||
var Utils = require('./utils'); | ||
var Boom = require('boom'); | ||
// Additional engine modules required in constructor | ||
@@ -16,159 +17,166 @@ | ||
// Engines Manager | ||
internals.Engines = function (options) { | ||
// View Manager | ||
this.settings = options || {}; | ||
this._engines = {}; | ||
}; | ||
exports = module.exports = internals.Manager = function (options) { | ||
var self = this; | ||
internals.Engines.prototype.add = function (settings, key) { | ||
var extensions = Object.keys(options.engines); | ||
Utils.assert(extensions.length, 'Views manager requires at least one registered extension handler'); | ||
key = key || settings.extension || null; | ||
var defaults = Utils.applyToDefaults(Defaults.views, options); | ||
delete defaults.engines; | ||
delete defaults.defaultExtension; | ||
Utils.assert(settings.module, 'Engine.module must be defined'); | ||
Utils.assert(key, 'Engine.extension must be defined'); | ||
this._engines = {}; | ||
this._defaultExtension = options.defaultExtension || (extensions.length === 1 ? extensions[0] : ''); | ||
if (typeof settings.module === 'string') { | ||
try { | ||
this._engines[key] = require(settings.module); // Can only require modules from hapi's package.json | ||
// Load engines | ||
extensions.forEach(function (extension) { | ||
var config = options.engines[extension]; | ||
if (typeof config === 'string') { | ||
config = { module: config }; | ||
} | ||
catch (e) { | ||
Utils.assert(false, 'Engine.module (' + settings.module + ') must be installed'); | ||
} | ||
} | ||
else { | ||
this._engines[key] = settings.module; | ||
} | ||
this._engines[key].suffix = '.' + key; | ||
// Generalize engine support via map object | ||
config = Utils.applyToDefaults(defaults, config); | ||
var schemaError = Schema.view(config); | ||
Utils.assert(!schemaError, 'Invalid server options:', schemaError); | ||
var funcs = Object.keys(settings.map); | ||
for (var i = 0, il = funcs.length; i < il; ++i) { | ||
if (settings.map.hasOwnProperty(funcs[i])) { | ||
this._engines[key][funcs[i]] = settings.map[funcs[i]](this._engines[key]); | ||
var engine = { | ||
module: (typeof config.module === 'string' ? require(config.module) : config.module), | ||
config: config, | ||
suffix: '.' + extension | ||
}; | ||
Utils.assert(engine.module.compile, 'Invalid view engine module: missing compile()'); | ||
if (config.isCached) { | ||
engine.cache = {}; | ||
} | ||
} | ||
return this._engines[key]; | ||
}; | ||
// Load partials | ||
self._loadPartials(engine); | ||
internals.Engines.prototype.getExt = function (p) { | ||
// Set engine | ||
if (p.indexOf('.') >= 0) { | ||
p = Path.extname(p).slice(1); | ||
} | ||
self._engines[extension] = engine; | ||
}); | ||
return p; | ||
return this; | ||
}; | ||
internals.Engines.prototype.get = function (extension) { | ||
internals.Manager.prototype._loadPartials = function (engine) { | ||
return this._engines[this.getExt(extension)]; | ||
}; | ||
var self = this; | ||
if (!engine.config.partialsPath || | ||
!engine.module.hasOwnProperty('registerPartial')) { | ||
// View Manager | ||
return; | ||
} | ||
exports = module.exports = internals.Manager = function (options) { | ||
var load = function () { | ||
this.settings = this.mergeLeft(Defaults.views, options); | ||
var path = Path.join(engine.config.basePath || '', engine.config.partialsPath); | ||
var files = traverse(path); | ||
files.forEach(function (file) { | ||
Utils.assert(!this.settings.partials || this.settings.partials.path, 'Missing partials path'); | ||
var offset = path.slice(-1) === '/' ? 0 : 1; | ||
var name = file.slice(path.length + offset, -engine.suffix.length); | ||
var src = Fs.readFileSync(file).toString(engine.config.encoding); | ||
engine.module.registerPartial(name, src); | ||
}) | ||
}; | ||
this.engines = new internals.Engines(); // Multiple engine support (distinguished by extension) | ||
this.engineLookup = {}; // For quickly looking up engines by filename | ||
this._cache = {}; // Compiled templates cache | ||
var traverse = function (path) { | ||
this.loadEngines(); | ||
this.loadPartials(); | ||
var files = []; | ||
return this; | ||
}; | ||
Fs.readdirSync(path).forEach(function (file) { | ||
file = Path.join(path, file); | ||
var stat = Fs.statSync(file); | ||
if (stat.isDirectory()) { | ||
files = files.concat(traverse(file)); | ||
return; | ||
} | ||
internals.Manager.prototype.mergeLeft = function () { | ||
if (stat.isFile() && | ||
Path.basename(file)[0] !== '.' && | ||
Path.extname(file) === engine.suffix) { | ||
var args = Array.prototype.slice.call(arguments); | ||
var base = Utils.clone(args.shift()); | ||
files.push(file); | ||
} | ||
}); | ||
args.forEach(function (el, i, arr) { | ||
return files; | ||
}; | ||
for (var key in el) { | ||
base[key] = el[key]; | ||
} | ||
}); | ||
return base; | ||
load(); | ||
}; | ||
internals.Manager.prototype.loadEngines = function () { | ||
internals.Manager.prototype.render = function (filename, context, options) { | ||
this.settings.engines = this.settings.engines || {}; | ||
var self = this; | ||
// Support API backwards compatibility | ||
context = context || {}; | ||
options = options || {}; | ||
if (this.settings.engine) { | ||
this.settings.engines[this.settings.engine.extension || Defaults.views.engines.html.extension] = this.settings.engine; | ||
delete this.settings.engine; | ||
} | ||
var engine = null; | ||
// Load all engines | ||
var render = function () { | ||
var engines = Object.keys(this.settings.engines); | ||
for (var i = 0, il = engines.length; i < il; ++i) { | ||
var key = engines[i]; | ||
var engine = this.settings.engines[key]; | ||
if (!engine.extension) { | ||
engine.extension = key; | ||
var fileExtension = Path.extname(filename).slice(1); | ||
var extension = fileExtension || self._defaultExtension; | ||
if (!extension) { | ||
return Boom.internal('Unknown extension and no defaultExtension configured for view template: ' + filename); | ||
} | ||
// Merge Base | ||
var base = Utils.clone(Defaults.views.engines.html); | ||
var baseMethods = Object.keys(base); | ||
for (var j = 0, jl = baseMethods.length; j < jl; ++j) { | ||
var method = baseMethods[j]; | ||
if (!engine.hasOwnProperty(method)) { | ||
engine[method] = base[method]; | ||
} | ||
engine = self._engines[extension]; | ||
if (!engine) { | ||
return Boom.internal('No view engine found for file: ' + filename); | ||
} | ||
this.engines.add(engine); | ||
} | ||
}; | ||
var settings = Utils.applyToDefaults(engine.config, options); | ||
var compiled = compile(filename + (fileExtension ? '' : engine.suffix), engine, settings); | ||
if (compiled instanceof Error) { | ||
return compiled; | ||
} | ||
internals.Manager.prototype.execute = function (engine, compiled, ctx, options) { | ||
if (!engine.config.layout) { | ||
if (engine && engine.hasOwnProperty('execute') && typeof engine.execute === 'function') { | ||
return engine.execute(engine, compiled, ctx, options); | ||
} | ||
// No layout | ||
return compiled; | ||
}; | ||
try { | ||
return compiled(context, settings.runtimeOptions); | ||
} | ||
catch (err) { | ||
return Boom.internal(err.message, err); | ||
} | ||
} | ||
// With layout | ||
internals.Manager.prototype.render = function (filename, context, options) { | ||
if (context.hasOwnProperty(engine.config.layoutKeyword)) { | ||
var template = filename; | ||
if (typeof template === 'string') { | ||
template = this._get(template, options); | ||
if (template instanceof Error) { | ||
return template; | ||
return Boom.internal('settings.layoutKeyword conflict', { context: context, keyword: engine.config.layoutKeyword }); | ||
} | ||
} | ||
var engine = this.engines.get(this.engineLookup[filename]); | ||
var layout = compile('layout' + engine.suffix, engine, settings); | ||
if (layout instanceof Error) { | ||
return layout; | ||
} | ||
if (!this.settings.layout) { | ||
var layoutContext = Utils.clone(context); | ||
// No layout | ||
try { | ||
return this.execute(engine, template, context, options)(context, options); | ||
layoutContext[engine.config.layoutKeyword] = compiled(context, settings.runtimeOptions); | ||
return layout(layoutContext, settings.runtimeOptions); | ||
} | ||
@@ -178,190 +186,58 @@ catch (err) { | ||
} | ||
} | ||
}; | ||
// With layout | ||
var compile = function (template, engine, settings) { | ||
if (context && | ||
context.hasOwnProperty(this.settings.layoutKeyword)) { | ||
if (engine.cache && | ||
engine.cache[template]) { | ||
return Boom.internal('settings.layoutKeyword conflict', { context: context, keyword: this.settings.layoutKeyword }); | ||
} | ||
return engine.cache[template]; | ||
} | ||
var layout = this._get('layout', options); | ||
if (layout instanceof Error) { | ||
return layout; | ||
} | ||
// Validate path | ||
var layoutContext = Utils.clone(context); | ||
var isAbsolutePath = (template[0] === '/'); | ||
var isInsecurePath = template.match(/\.\.\//g); | ||
try { | ||
layoutContext[this.settings.layoutKeyword] = this.execute(engine, template, context, options)(context); | ||
return this.execute(engine, layout, layoutContext, options)(layoutContext); | ||
} | ||
catch (err) { | ||
return Boom.internal(err.message, err); | ||
} | ||
}; | ||
if (!settings.allowAbsolutePaths && | ||
isAbsolutePath) { | ||
return Boom.internal('Absolute paths are not allowed in views'); | ||
} | ||
internals.Manager.prototype._isCaching = function (filename, globalSettings, engineSettings, set) { | ||
if (!settings.allowInsecureAccess && | ||
isInsecurePath) { | ||
set = set || false; | ||
engineSettings = this.mergeLeft(Defaults.views.engines.html, engineSettings || {}); | ||
if (globalSettings.cache === false) { | ||
return false; // if global cache disabled, disable all caching behavior | ||
} | ||
if (engineSettings.cache === false) { | ||
return false; // if engine cache disabled, disable this request | ||
} | ||
if (set) { | ||
return true; | ||
} | ||
return this._cache.hasOwnProperty(filename); // Cache enabled only if key exists | ||
}; | ||
internals.Manager.prototype.getCache = function (filename) { | ||
return this._cache[filename]; | ||
}; | ||
internals.Manager.prototype.setCache = function (filename, compiled, options, engineSettings) { | ||
if (this._isCaching(filename, options, engineSettings, true)) { | ||
return this._cache[filename] = compiled; | ||
} | ||
}; | ||
internals.Manager.prototype._get = function (filename, options) { | ||
options = this.mergeLeft(this.settings, options); | ||
if (this._isCaching(filename, options)) { | ||
return this.getCache(filename); | ||
} | ||
// Normalize path | ||
var isAbsolutePath = (filename[0] === '/'); | ||
var isInsecurePath = filename.match(/\.\.\//g) !== null; | ||
if (!options.allowAbsolutePaths && | ||
isAbsolutePath) { | ||
return Boom.internal('Absolute paths are not allowed in views'); | ||
} | ||
if (!options.allowInsecureAccess && | ||
isInsecurePath) { | ||
return Boom.internal('View paths cannot lookup templates outside root path (path includes one or more \'../\')'); | ||
} | ||
// Resolve path and extension | ||
var fullPath = filename; | ||
if (!isAbsolutePath) { | ||
fullPath = Path.join(options.basePath, options.path, fullPath); | ||
} | ||
var folder = Path.dirname(fullPath); | ||
var files = Fs.readdirSync(folder); | ||
var re = new RegExp(Path.basename(filename) + '([.][a-zA-Z0-9]+)?$'); | ||
for (var j = 0, jl = files.length; j < jl; ++j) { | ||
var match = re.exec(files[j]); | ||
if (match) { | ||
fullPath = Path.join(folder, files[j]); | ||
break; | ||
return Boom.internal('View paths cannot lookup templates outside root path (path includes one or more \'../\')'); | ||
} | ||
} | ||
// Pass the filename to Jade via this copy of compileOptions | ||
// Resolve path and extension | ||
options.compileOptions.filename = fullPath; | ||
var fullPath = (isAbsolutePath ? template : Path.join(settings.basePath || '', settings.path || '', template)); | ||
// Read file | ||
settings.compileOptions.template = fullPath; // Pass the template to Jade via this copy of compileOptions | ||
try { | ||
var source = Fs.readFileSync(fullPath).toString(this.settings.encoding); | ||
} | ||
catch (e) { | ||
return Boom.internal('View file not found: ' + fullPath); | ||
} | ||
// Read file | ||
var engine = this.engines.get(fullPath); | ||
var compiled = engine.compile(source, options.compileOptions); | ||
this.engineLookup[filename] = engine.suffix.slice(1); | ||
this.setCache(filename, compiled, options, this.settings.engines[engine.suffix.slice(1)]); | ||
return compiled; | ||
}; | ||
internals.Manager.prototype.loadPartials = function () { | ||
var self = this; | ||
if (!this.settings.partials) { | ||
return; | ||
} | ||
var path = Path.join(this.settings.basePath, this.settings.partials.path); | ||
var load = function (root, currentPath, files) { | ||
var current = Path.join(root, currentPath); | ||
if (!Fs.statSync(current).isDirectory()) { | ||
try { | ||
var engine = self.engines.get(current); | ||
if (current.slice(-engine.suffix.length) === engine.suffix) { | ||
files.push(current); | ||
} | ||
} | ||
catch (e) { | ||
// Skip file | ||
} | ||
return register(files); | ||
try { | ||
var source = Fs.readFileSync(fullPath).toString(settings.encoding); | ||
} | ||
catch (e) { | ||
return Boom.internal('View file not found: ' + fullPath); | ||
} | ||
var directory = Fs.readdirSync(current); | ||
for (var i = 0, il = directory.length; i < il; ++i) { | ||
var currentFile = directory[i]; | ||
var currentFileFullPath = Path.join(root, currentFile); | ||
if (Fs.statSync(currentFileFullPath).isDirectory()) { | ||
load(currentFileFullPath, '/', files); | ||
} | ||
else { | ||
load(root, currentFile, files); | ||
} | ||
var compiled = engine.module.compile(source, settings.compileOptions); | ||
if (engine.cache) { | ||
engine.cache[template] = compiled; | ||
} | ||
return register(files); | ||
return compiled; | ||
}; | ||
var register = function (files) { | ||
var result = render(); | ||
if (result instanceof Error) { | ||
return result; | ||
} | ||
for (var i = 0, il = files.length; i < il; ++i) { | ||
var file = files[i]; | ||
var engine = self.engines.get(file); | ||
if (!engine.hasOwnProperty('registerPartial')) { | ||
continue; | ||
} | ||
var offset = path.slice(-1) === '/' ? 0 : 1; | ||
var name = file.slice(path.length + offset, -engine.suffix.length); | ||
var src = Fs.readFileSync(file).toString(self.settings.encoding); | ||
engine.registerPartial(name, src); | ||
} | ||
}; | ||
load(path, '/', []); | ||
return { result: result, config: engine.config } | ||
}; | ||
@@ -377,7 +253,7 @@ | ||
payload: request.payload, | ||
querystring: request.querystring | ||
query: request.query | ||
}; | ||
request.reply.view(viewFilePath, context).send(); | ||
request.reply.view(viewFilePath, context); | ||
}; | ||
}; |
@@ -5,3 +5,3 @@ { | ||
"homepage": "http://hapijs.com", | ||
"version": "0.16.0", | ||
"version": "1.0.0", | ||
"author": "Eran Hammer <eran@hueniverse.com> (http://hueniverse.com)", | ||
@@ -30,27 +30,26 @@ "contributors": [ | ||
"engines": { | ||
"node": "0.8.x" | ||
"node": "0.10.x" | ||
}, | ||
"dependencies": { | ||
"hoek": "0.7.x", | ||
"boom": "0.3.x", | ||
"joi": "0.2.x", | ||
"hawk": "0.11.x", | ||
"shot": "0.1.x", | ||
"oz": "0.1.x", | ||
"async": "0.1.x", | ||
"request": "2.16.x", | ||
"hoek": "0.8.x", | ||
"boom": "0.4.x", | ||
"joi": "0.3.x", | ||
"catbox": "0.5.x", | ||
"hawk": "0.13.x", | ||
"shot": "0.2.x", | ||
"cryptiles": "0.2.x", | ||
"iron": "0.3.x", | ||
"async": "0.2.x", | ||
"request": "2.20.x", | ||
"formidable": "1.0.x", | ||
"mime": "1.2.x", | ||
"catbox": "0.4.x", | ||
"json-stringify-safe": "3.0.x", | ||
"cryptiles": "0.1.x", | ||
"iron": "0.2.x", | ||
"lru-cache": "2.2.x", | ||
"optimist": "0.3.x", | ||
"negotiator": "0.2.x" | ||
"lru-cache": "2.3.x", | ||
"optimist": "0.4.x", | ||
"negotiator": "0.2.x", | ||
"semver": "1.1.x" | ||
}, | ||
"devDependencies": { | ||
"lab": "0.0.x", | ||
"lab": "0.1.x", | ||
"handlebars": "1.0.x", | ||
"jade": "0.28.x", | ||
"jade": "0.30.x", | ||
"complexity-report": "0.x.x" | ||
@@ -57,0 +56,0 @@ }, |
165
README.md
<a href="https://github.com/spumko"><img src="https://raw.github.com/spumko/spumko/master/images/from.png" align="right" /></a> | ||
<img src="https://raw.github.com/spumko/hapi/master/images/hapi.png" /> | ||
A rich framework for building restful API services. **hapi** is a configuration-centric framework in which | ||
authentication requirements, input validation, data caching and pre-fetching, developer documentation, | ||
and other essential facilities are provided out-of-the-box and enabled using simple JSON configuration | ||
objects. **hapi** enables developers to focus on writing reusable business logic instead of spending time | ||
with everything else. | ||
A rich framework for building web applications and services. **hapi** is a simple to use configuration-centric | ||
framework with built-in support for input validation, caching, authentication, and other essential facilities. | ||
**hapi** enables developers to focus on writing reusable application logic instead of spending time building | ||
infrastructure. The framework supports a powerful plugin architecture for pain-free and scalable extensibility. | ||
For the latest updates and release information follow [@hapijs](https://twitter.com/hapijs) on twitter. | ||
Current version: **0.16.0** | ||
Current version: **1.0.0** | ||
Node version: **0.10** required | ||
[](http://travis-ci.org/spumko/hapi) | ||
<img src="https://raw.github.com/olivierlacan/shields/master/coveralls/coveralls_100.png" /> | ||
### [API Reference](/docs/Reference.md) | ||
### [Tutorials](/docs/Tutorials.md) | ||
### [Plugins](/docs/Plugins.md) | ||
### [Breaking Changes](https://github.com/spumko/hapi/issues?labels=breaking+changes&state=closed) | ||
## Getting started | ||
To demonstrate a basic example we will be creating a "hello world" service with a single API endpoint. | ||
### Hello World Server | ||
Start by creating a _package.json_ by running | ||
Start by creating a _package.json_: | ||
``` | ||
@@ -37,3 +25,3 @@ npm init | ||
Now install **hapi** and have it saved to your _package.json_ dependencies by running | ||
Install **hapi** and have it saved to your _package.json_ dependencies: | ||
``` | ||
@@ -43,3 +31,3 @@ npm install hapi --save | ||
Next create an _'index.js'_ file and add the following contents to it: | ||
Create an _'index.js'_ file and with the following contents: | ||
```javascript | ||
@@ -51,10 +39,2 @@ var Hapi = require('hapi'); | ||
// Define the route | ||
var hello = { | ||
handler: function (request) { | ||
request.reply({ greeting: 'hello world' }); | ||
} | ||
}; | ||
// Add the route | ||
@@ -64,115 +44,8 @@ server.route({ | ||
path: '/hello', | ||
config: hello | ||
}); | ||
// Start the server | ||
server.start(); | ||
``` | ||
Start the server with `node .` and navigate to the website at 'http://localhost:8000/hello' in a browser and you will see the following output: | ||
```json | ||
{"greeting":"hello world"} | ||
``` | ||
### Hello World Server + Validation | ||
To demonstrate one of the more powerful features in **hapi** we will change the 'hello' route to only respond whenever a _'name'_ is present on the querystring. Change the _'index.js'_ so that the _'hello'_ config object looks like the following: | ||
```javascript | ||
var hello = { | ||
handler: function (request) { | ||
handler: function () { | ||
request.reply({ greeting: 'hello ' + request.query.name }); | ||
}, | ||
validate: { | ||
query: { | ||
name: Hapi.Types.String().required() | ||
} | ||
this.reply('hello world'); | ||
} | ||
}; | ||
``` | ||
When you start the server with `node .` and navigate to 'http://localhost:8000/hello' you will get a 400 response with an error explaining that 'name' is required. When the 'name' is omitted from the querystring the handler will not be called. However, if you do provide a 'name' it will be echoed out in the response. If you request 'http://localhost:8000/hello?name=John' then you will get the following response: | ||
```json | ||
{"greeting":"hello John"} | ||
``` | ||
To learn more about the various validation options you can read the [validation section](docs/Reference.md#query-validation) in the reference. | ||
### Hello Static Server | ||
The **hapi** route handler can be used to easily serve files, directories, render templates, and even proxy requests. In this example the _'directory'_ handler will be used to create a static site serving files in the _'public'_ folder. Remove the `hello` variable and make the `server.route` command look like the following: | ||
```javascript | ||
server.route({ | ||
method: 'GET', | ||
path: '/{path*}', | ||
handler: { | ||
directory: { path: './public', listing: false, index: true } | ||
} | ||
}); | ||
``` | ||
Create a folder named _'public'_ and add a _'index.html'_ file in the folder with the following contents: | ||
```html | ||
<html> | ||
<head><title>Hello Static</title></head> | ||
<body> | ||
Hello Static | ||
</body> | ||
</html> | ||
``` | ||
Now when you request 'http://localhost:8000' you will see the html page rendered. You can add other files in this folder and they will be served. This is a good solution for serving static assets like images and css files. | ||
### Hello Templates Server | ||
To demonstrate how to use **hapi** to render templates we will be creating a template and rendering it using the [handlebars](http://handlebarsjs.com/) engine. Begin by installing handlebars by running the following npm command: | ||
```bash | ||
npm install handlebars | ||
``` | ||
Next create a directory named _'templates'_ that will contain the template files. In this directory create a _'index.html'_ with the following contents: | ||
```html | ||
<html> | ||
<head><title>{{greeting}}</title></head> | ||
<body> | ||
{{greeting}} | ||
</body> | ||
</html> | ||
``` | ||
The next step is going to be to tell the **hapi** server to use templates and the handlebars engine. After this, the route handler will be updated to render the template using an object that contains a _'greeting'_ property we want displayed. Change the _'index.js'_ file so that it looks like the following: | ||
```javascript | ||
var Hapi = require('hapi'); | ||
var options = { | ||
views: { | ||
path: './templates', | ||
engine: { | ||
module: 'handlebars' | ||
} | ||
} | ||
}; | ||
// Create a server with a host, port, and options | ||
var server = Hapi.createServer('localhost', 8000, options); | ||
// Define the route | ||
var hello = { | ||
handler: function (request) { | ||
// Render the view with the custom greeting | ||
request.reply.view('index.html', { greeting: 'hello world' }).send(); | ||
} | ||
}; | ||
// Add the route | ||
server.route({ | ||
method: 'GET', | ||
path: '/', | ||
config: hello | ||
}); | ||
// Start the server | ||
@@ -182,8 +55,14 @@ server.start(); | ||
When you run the server with `node .` and view the homepage you will see the custom greeting message rendered. More information on using templates with **hapi** can be found in the [views](docs/Reference.md#views) section of the [API Reference](docs/Reference.md). | ||
Launch the application (`node .`) and open 'http://localhost:8000/hello' in a browser. | ||
### Community | ||
For discussion about hapi join the [#hapi channel](http://webchat.freenode.net/?channels=hapi) on irc.freenode.net. | ||
## More information | ||
### [Contributors](https://github.com/spumko/hapi/contributors) | ||
- For the **latest updates** follow [@hapijs](https://twitter.com/hapijs). | ||
- For more **information, tutorials, and references** on the currently published version, visit [**hapijs.com**](http://hapijs.com) | ||
- Information about the **work-in-progress** in the master branch: | ||
- [API reference](/docs/Reference.md) | ||
- [Upcoming breaking changes](https://github.com/spumko/hapi/issues?labels=breaking+changes) | ||
- For **discussions** join the [#hapi channel](http://webchat.freenode.net/?channels=hapi) on irc.freenode.net | ||
- Any **issues or questions** (no matter how basic), open an issue. | ||
@@ -5,3 +5,2 @@ // Load modules | ||
var Lab = require('lab'); | ||
var Oz = require('oz'); | ||
var Hawk = require('hawk'); | ||
@@ -36,31 +35,19 @@ var Stream = require('stream'); | ||
var hashPassword = function (password) { | ||
var loadUser = function (username, password, callback) { | ||
var hash = Crypto.createHash('sha1'); | ||
hash.update(password, 'utf8'); | ||
return hash.digest('base64'); | ||
}; | ||
var loadUser = function (id, callback) { | ||
if (id === 'john') { | ||
return callback(null, { | ||
if (username === 'john') { | ||
return callback(null, password === '12345', { | ||
user: 'john', | ||
scope: [], | ||
tos: 100 | ||
}, hashPassword('12345')); | ||
tos: '1.0.0' | ||
}); | ||
} | ||
else if (id === 'jane') { | ||
else if (username === 'jane') { | ||
return callback(Hapi.error.internal('boom')); | ||
} | ||
else if (id === 'invalid1') { | ||
return callback(null, 'bad'); | ||
else if (username === 'invalid1') { | ||
return callback(null, true, 'bad'); | ||
} | ||
else if (id === 'invalid2') { | ||
return callback(null, {}, null); | ||
} | ||
else { | ||
return callback(null, null); | ||
} | ||
return callback(null, false); | ||
}; | ||
@@ -71,4 +58,3 @@ | ||
scheme: 'basic', | ||
loadUserFunc: loadUser, | ||
hashPasswordFunc: hashPassword, | ||
validateFunc: loadUser, | ||
defaultMode: 'required' | ||
@@ -99,3 +85,3 @@ } | ||
{ method: 'POST', path: '/basicScope', handler: basicHandler, config: { auth: { scope: 'x' } } }, | ||
{ method: 'POST', path: '/basicTos', handler: basicHandler, config: { auth: { tos: 200 } } }, | ||
{ method: 'POST', path: '/basicTos', handler: basicHandler, config: { auth: { tos: '1.1.x' } } }, | ||
{ method: 'POST', path: '/double', handler: doubleHandler } | ||
@@ -163,2 +149,15 @@ ]); | ||
it('returns an error on bad header format', function (done) { | ||
var request = { method: 'POST', url: '/basic', headers: { authorization: 'basic' } }; | ||
server.inject(request, function (res) { | ||
expect(res.result).to.exist; | ||
expect(res.result.code).to.equal(400); | ||
expect(res.result.isMissing).to.equal(undefined); | ||
done(); | ||
}); | ||
}); | ||
it('returns an error on bad header internal syntax', function (done) { | ||
@@ -177,5 +176,5 @@ | ||
it('returns an error on bad scheme', function (done) { | ||
it('returns an error on missing username', function (done) { | ||
var request = { method: 'POST', url: '/basic', headers: { authorization: 'something' } }; | ||
var request = { method: 'POST', url: '/basic', headers: { authorization: basicHeader('', '') } }; | ||
@@ -185,3 +184,3 @@ server.inject(request, function (res) { | ||
expect(res.result).to.exist; | ||
expect(res.result.code).to.equal(401); | ||
expect(res.result.code).to.equal(400); | ||
done(); | ||
@@ -227,14 +226,2 @@ }); | ||
it('returns an error on missing password error', function (done) { | ||
var request = { method: 'POST', url: '/basic', headers: { authorization: basicHeader('invalid2', '12345') } }; | ||
server.inject(request, function (res) { | ||
expect(res.result).to.exist; | ||
expect(res.result.code).to.equal(500); | ||
done(); | ||
}); | ||
}); | ||
it('returns an error on insufficient tos', function (done) { | ||
@@ -278,5 +265,4 @@ | ||
}); | ||
var options = { method: 'GET', url: '/noauth' }; | ||
server.inject(options, function (res) { | ||
server.inject('/noauth', function (res) { | ||
@@ -294,4 +280,3 @@ expect(res.result).to.exist; | ||
scheme: 'basic', | ||
loadUserFunc: loadUser, | ||
hashPasswordFunc: hashPassword | ||
validateFunc: loadUser | ||
} | ||
@@ -318,4 +303,3 @@ }; | ||
var invalidOptions = { method: 'GET', url: '/noauth' }; | ||
server.inject(invalidOptions, function (res) { | ||
server.inject('/noauth', function (res) { | ||
@@ -335,7 +319,7 @@ expect(res.result).to.exist; | ||
scheme: 'basic', | ||
loadUserFunc: loadUser | ||
validateFunc: loadUser | ||
}, | ||
'b': { | ||
scheme: 'basic', | ||
loadUserFunc: loadUser | ||
validateFunc: loadUser | ||
} | ||
@@ -401,181 +385,2 @@ } | ||
describe('Oz', function () { | ||
var loadApp = function (id, callback) { | ||
var app = { | ||
id: id, | ||
secret: '8dunq9823udn', | ||
scope: ['y'] | ||
}; | ||
return callback(app); | ||
}; | ||
var loadGrant = function (id, callback) { | ||
var result = { | ||
id: id, | ||
app: '123', | ||
user: '456', | ||
exp: Date.now() + 5000, | ||
scope: ['y'] | ||
}; | ||
return callback(result); | ||
}; | ||
var config = { | ||
auth: { | ||
scheme: 'oz', | ||
encryptionPassword: '47dqyboq387yd5qo', | ||
loadAppFunc: loadApp, | ||
loadGrantFunc: loadGrant | ||
} | ||
}; | ||
var server = new Hapi.Server(config); | ||
var ozHandler = function (request) { | ||
request.reply('Success'); | ||
}; | ||
server.route([ | ||
{ method: 'POST', path: '/oz', handler: ozHandler, config: { auth: 'default' } }, | ||
{ method: 'POST', path: '/ozOptional', handler: ozHandler, config: { auth: { mode: 'optional' } } }, | ||
{ method: 'POST', path: '/ozScope', handler: ozHandler, config: { auth: { scope: 'x' } } }, | ||
{ method: 'POST', path: '/ozTos', handler: ozHandler, config: { auth: { tos: 200 } } } | ||
]); | ||
var ozHeader = function (path, callback) { | ||
var app = { | ||
id: '123' | ||
}; | ||
var grant = { | ||
id: 's81u29n1812', | ||
user: '456', | ||
exp: Date.now() + 5000, | ||
scope: ['y'] | ||
}; | ||
Oz.ticket.issue(app, grant, config.auth.encryptionPassword, {}, function (err, envelope) { | ||
var request = { | ||
method: 'POST', | ||
resource: path, | ||
host: 'example.com', | ||
port: 8080 | ||
}; | ||
return callback(Oz.Request.generateHeader(request, envelope)); | ||
}); | ||
}; | ||
it('returns a reply on successful auth', function (done) { | ||
ozHeader('/oz', function (header) { | ||
var request = { method: 'POST', url: '/oz', headers: { host: 'example.com:8080', authorization: header } }; | ||
server.inject(request, function (res) { | ||
expect(res.result).to.exist; | ||
expect(res.result).to.equal('Success'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('returns a reply on successful app endpoint request', function (done) { | ||
var request = { method: 'POST', url: '/oz/app', headers: { host: 'example.com:8080', authorization: basicHeader('123', '8dunq9823udn') } }; | ||
server.inject(request, function (res) { | ||
expect(res.payload).to.exist; | ||
var envelope = JSON.parse(res.payload); | ||
expect(envelope.app).to.equal('123'); | ||
done(); | ||
}); | ||
}); | ||
it('returns a reply on failed optional auth', function (done) { | ||
var request = { method: 'POST', url: '/ozOptional', headers: { host: 'example.com:8080' } }; | ||
server.inject(request, function (res) { | ||
expect(res.result).to.exist; | ||
expect(res.result).to.equal('Success'); | ||
done(); | ||
}); | ||
}); | ||
it('returns an error on insufficient tos', function (done) { | ||
ozHeader('/ozTos', function (header) { | ||
var request = { method: 'POST', url: '/ozTos', headers: { host: 'example.com:8080', authorization: header } }; | ||
server.inject(request, function (res) { | ||
expect(res.result).to.exist; | ||
expect(res.result.code).to.equal(403); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('returns an error on insufficient scope', function (done) { | ||
ozHeader('/ozScope', function (header) { | ||
var request = { method: 'POST', url: '/ozScope', headers: { host: 'example.com:8080', authorization: header } }; | ||
server.inject(request, function (res) { | ||
expect(res.result).to.exist; | ||
expect(res.result.code).to.equal(403); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('cannot add a route that has payload validation required', function (done) { | ||
var fn = function () { | ||
server.route({ method: 'POST', path: '/ozPayload', handler: ozHandler, config: { auth: { mode: 'required', payload: 'required' }, payload: 'raw' } }); | ||
}; | ||
expect(fn).to.throw(Error); | ||
done(); | ||
}); | ||
it('cannot add a route that has payload validation as optional', function (done) { | ||
var fn = function () { | ||
server.route({ method: 'POST', path: '/ozPayload', handler: ozHandler, config: { auth: { mode: 'required', payload: 'optional' }, payload: 'raw' } }); | ||
}; | ||
expect(fn).to.throw(Error); | ||
done(); | ||
}); | ||
it('can add a route that has payload validation as none', function (done) { | ||
var fn = function () { | ||
server.route({ method: 'POST', path: '/ozPayload', handler: ozHandler, config: { auth: { mode: 'required', payload: false }, payload: 'raw' } }); | ||
}; | ||
expect(fn).to.not.throw(Error); | ||
done(); | ||
}); | ||
}); | ||
describe('Hawk', function () { | ||
@@ -616,3 +421,3 @@ | ||
if (credentials[id] && credentials[id].cred) { | ||
return Hawk.client.header('http://0.0.0.0:8080' + path, 'POST', { credentials: credentials[id].cred }).field; | ||
return Hawk.client.header('http://example.com:8080' + path, 'POST', { credentials: credentials[id].cred }); | ||
} | ||
@@ -635,14 +440,8 @@ else { | ||
request.reply.payload('Success').send(); | ||
request.reply('Success'); | ||
}; | ||
var hawkChangeHandler = function (request) { | ||
request.auth.credentials.algorithm = 'ha'; | ||
request.reply.payload('Success').send(); | ||
}; | ||
var hawkErrorHandler = function (request) { | ||
request.reply.payload(new Error()).send(); | ||
request.reply(new Error()); | ||
}; | ||
@@ -652,9 +451,21 @@ | ||
var stream = new Stream(); | ||
stream.readable = true; | ||
stream.resume = function () { | ||
var TestStream = function () { | ||
Stream.Readable.call(this); | ||
}; | ||
Hapi.utils.inherits(TestStream, Stream.Readable); | ||
TestStream.prototype._read = function (size) { | ||
var self = this; | ||
if (this.isDone) { | ||
return; | ||
} | ||
this.isDone = true; | ||
setTimeout(function () { | ||
stream.emit('data', 'hi'); | ||
self.push('hi'); | ||
}, 2); | ||
@@ -664,7 +475,8 @@ | ||
stream.emit('end', ''); | ||
self.push(null); | ||
}, 5); | ||
}; | ||
request.reply.stream(stream).send(); | ||
var stream = new TestStream(); | ||
request.reply(stream); | ||
}; | ||
@@ -675,3 +487,2 @@ | ||
{ method: 'POST', path: '/hawkValidate', handler: hawkHandler, config: { auth: 'default', validate: { query: { } } } }, | ||
{ method: 'POST', path: '/hawkchange', handler: hawkChangeHandler, config: { auth: 'default' } }, | ||
{ method: 'POST', path: '/hawkError', handler: hawkErrorHandler, config: { auth: 'default' } }, | ||
@@ -681,3 +492,3 @@ { method: 'POST', path: '/hawkStream', handler: hawkStreamHandler, config: { auth: 'default' } }, | ||
{ method: 'POST', path: '/hawkScope', handler: hawkHandler, config: { auth: { scope: 'x' } } }, | ||
{ method: 'POST', path: '/hawkTos', handler: hawkHandler, config: { auth: { tos: 200 } } }, | ||
{ method: 'POST', path: '/hawkTos', handler: hawkHandler, config: { auth: { tos: '2.0.0' } } }, | ||
{ method: 'POST', path: '/hawkPayload', handler: hawkHandler, config: { auth: { mode: 'required', payload: 'required' }, payload: 'raw' } }, | ||
@@ -691,3 +502,3 @@ { method: 'POST', path: '/hawkPayloadOptional', handler: hawkHandler, config: { auth: { mode: 'required', payload: 'optional' }, payload: 'raw' } }, | ||
var request = { method: 'POST', url: '/hawk', headers: { authorization: hawkHeader('john', '/hawk'), host: '0.0.0.0:8080' } }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawk', headers: { authorization: hawkHeader('john', '/hawk').field } }; | ||
@@ -704,3 +515,3 @@ server.inject(request, function (res) { | ||
var request = { method: 'POST', url: '/hawkOptional', headers: { host: 'example.com:8080' } }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawkOptional' }; | ||
@@ -716,3 +527,4 @@ server.inject(request, function (res) { | ||
var request = { method: 'POST', url: '/hawkStream', headers: { authorization: hawkHeader('john', '/hawkStream'), host: '0.0.0.0:8080' } }; | ||
var authHeader = hawkHeader('john', '/hawkStream'); | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawkStream', headers: { authorization: authHeader.field } }; | ||
@@ -724,15 +536,2 @@ server.inject(request, function (res) { | ||
var attributes = Hawk.utils.parseAuthorizationHeader(res.raw.req.headers.authorization); | ||
var artifacts = { | ||
method: res.raw.req.method, | ||
host: res.raw.req.headers.host.split(':')[0], | ||
port: res.raw.req.headers.host.split(':')[1], | ||
resource: res.raw.req.url, | ||
ts: attributes.ts, | ||
nonce: attributes.nonce, | ||
ext: attributes.ext, | ||
mac: attributes.mac | ||
}; | ||
var options = { | ||
@@ -744,3 +543,3 @@ payload: res.payload | ||
var header = Hawk.server.header(cred, artifacts, options); | ||
var header = Hawk.server.header(cred, authHeader.artifacts, options); | ||
var trailerAuth = res.raw.res._trailer.split(':')[1]; | ||
@@ -758,3 +557,4 @@ trailerAuth = trailerAuth.substr(1, trailerAuth.lastIndexOf('"')); | ||
var request = { method: 'POST', url: '/hawk', headers: { authorization: hawkHeader('john', '/hawk'), host: '0.0.0.0:8080' } }; | ||
var authHeader = hawkHeader('john', '/hawk'); | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawk', headers: { authorization: authHeader.field } }; | ||
@@ -766,15 +566,2 @@ server.inject(request, function (res) { | ||
var attributes = Hawk.utils.parseAuthorizationHeader(res.raw.req.headers.authorization); | ||
var artifacts = { | ||
method: res.raw.req.method, | ||
host: res.raw.req.headers.host.split(':')[0], | ||
port: res.raw.req.headers.host.split(':')[1], | ||
resource: res.raw.req.url, | ||
ts: attributes.ts, | ||
nonce: attributes.nonce, | ||
ext: attributes.ext, | ||
mac: attributes.mac | ||
}; | ||
var options = { | ||
@@ -787,3 +574,3 @@ payload: res.payload, | ||
var header = Hawk.server.header(cred, artifacts, options); | ||
var header = Hawk.server.header(cred, authHeader.artifacts, options); | ||
expect(header).to.equal(res.headers['server-authorization']); | ||
@@ -798,3 +585,4 @@ | ||
var request = { method: 'POST', url: '/hawkValidate?a=1', headers: { authorization: hawkHeader('john', '/hawkValidate?a=1'), host: '0.0.0.0:8080' } }; | ||
var authHeader = hawkHeader('john', '/hawkValidate?a=1'); | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawkValidate?a=1', headers: { authorization: authHeader.field } }; | ||
@@ -807,15 +595,2 @@ server.inject(request, function (res) { | ||
var attributes = Hawk.utils.parseAuthorizationHeader(res.raw.req.headers.authorization); | ||
var artifacts = { | ||
method: res.raw.req.method, | ||
host: res.raw.req.headers.host.split(':')[0], | ||
port: res.raw.req.headers.host.split(':')[1], | ||
resource: res.raw.req.url, | ||
ts: attributes.ts, | ||
nonce: attributes.nonce, | ||
ext: attributes.ext, | ||
mac: attributes.mac | ||
}; | ||
var options = { | ||
@@ -828,4 +603,4 @@ payload: res.payload, | ||
artifacts.credentials = cred; | ||
var header = Hawk.server.header(cred, artifacts, options); | ||
authHeader.artifacts.credentials = cred; | ||
var header = Hawk.server.header(cred, authHeader.artifacts, options); | ||
expect(header).to.equal(res.headers['server-authorization']); | ||
@@ -838,16 +613,5 @@ | ||
it('returns an error when the hawk auth response header can\'t be created', function (done) { | ||
var request = { method: 'POST', url: '/hawkchange', headers: { authorization: hawkHeader('joan', '/hawkchange'), host: '0.0.0.0:8080' } }; | ||
server.inject(request, function (res) { | ||
expect(res.statusCode).to.equal(500); | ||
done(); | ||
}); | ||
}); | ||
it('doesn\'t include authorization header in response when the response is an error', function (done) { | ||
var request = { method: 'POST', url: '/hawkError', headers: { authorization: hawkHeader('john', '/hawkError'), host: '0.0.0.0:8080' } }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawkError', headers: { authorization: hawkHeader('john', '/hawkError').field } }; | ||
@@ -864,3 +628,3 @@ server.inject(request, function (res) { | ||
var request = { method: 'POST', url: '/hawk', headers: { authorization: hawkHeader('john', 'abcd'), host: '0.0.0.0:8080' } }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawk', headers: { authorization: hawkHeader('john', 'abcd').field } }; | ||
@@ -877,3 +641,3 @@ server.inject(request, function (res) { | ||
var request = { method: 'POST', url: '/hawk', headers: { authorization: 'junk', host: '0.0.0.0:8080' } }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawk', headers: { authorization: 'junk' } }; | ||
@@ -890,3 +654,3 @@ server.inject(request, function (res) { | ||
var request = { method: 'POST', url: '/hawk', headers: { authorization: 'junk something', host: '0.0.0.0:8080' } }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawk', headers: { authorization: 'junk something' } }; | ||
@@ -903,3 +667,3 @@ server.inject(request, function (res) { | ||
var request = { method: 'POST', url: '/hawkTos', headers: { authorization: hawkHeader('john', '/hawkTos'), host: '0.0.0.0:8080' } }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawkTos', headers: { authorization: hawkHeader('john', '/hawkTos').field } }; | ||
@@ -915,3 +679,3 @@ server.inject(request, function (res) { | ||
var request = { method: 'POST', url: '/hawkScope', headers: { authorization: hawkHeader('john', '/hawkScope'), host: '0.0.0.0:8080' } }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawkScope', headers: { authorization: hawkHeader('john', '/hawkScope').field } }; | ||
@@ -927,3 +691,3 @@ server.inject(request, function (res) { | ||
var request = { method: 'POST', url: '/hawk', headers: { authorization: hawkHeader('john', '/hawk'), custom: '0.0.0.0:8080' } }; | ||
var request = { method: 'POST', url: '/hawk', headers: { authorization: hawkHeader('john', '/hawk').field, custom: 'example.com:8080' } }; | ||
@@ -952,4 +716,4 @@ var config = { | ||
var payload = 'application text formatted payload'; | ||
var authHeader = Hawk.client.header('http://0.0.0.0:8080/hawkPayload', 'POST', { credentials: credentials.john.cred, payload: payload, contentType: 'application/text' }); | ||
var request = { method: 'POST', url: '/hawkPayload', headers: { authorization: authHeader.field, host: '0.0.0.0:8080', 'content-type': 'application/text' }, payload: payload }; | ||
var authHeader = Hawk.client.header('http://example.com:8080/hawkPayload', 'POST', { credentials: credentials.john.cred, payload: payload, contentType: 'application/text' }); | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawkPayload', headers: { authorization: authHeader.field, 'content-type': 'application/text' }, payload: payload }; | ||
@@ -967,5 +731,5 @@ server.inject(request, function (res) { | ||
var payload = 'Here is my payload'; | ||
var authHeader = Hawk.client.header('http://0.0.0.0:8080/hawkPayload', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
var authHeader = Hawk.client.header('http://example.com:8080/hawkPayload', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
payload += 'HACKED'; | ||
var request = { method: 'POST', url: '/hawkPayload', headers: { authorization: authHeader.field, host: '0.0.0.0:8080' }, payload: payload }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawkPayload', headers: { authorization: authHeader.field }, payload: payload }; | ||
@@ -983,5 +747,5 @@ server.inject(request, function (res) { | ||
var payload = 'Here is my payload'; | ||
var authHeader = Hawk.client.header('http://0.0.0.0:8080/hawkPayloadOptional', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
var authHeader = Hawk.client.header('http://example.com:8080/hawkPayloadOptional', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
payload += 'HACKED'; | ||
var request = { method: 'POST', url: '/hawkPayloadOptional', headers: { authorization: authHeader.field, host: '0.0.0.0:8080' }, payload: payload }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawkPayloadOptional', headers: { authorization: authHeader.field }, payload: payload }; | ||
@@ -999,4 +763,4 @@ server.inject(request, function (res) { | ||
var payload = 'Here is my payload'; | ||
var authHeader = Hawk.client.header('http://0.0.0.0:8080/hawkPayloadOptional', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
var request = { method: 'POST', url: '/hawkPayloadOptional', headers: { authorization: authHeader.field, host: '0.0.0.0:8080' }, payload: payload }; | ||
var authHeader = Hawk.client.header('http://example.com:8080/hawkPayloadOptional', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawkPayloadOptional', headers: { authorization: authHeader.field }, payload: payload }; | ||
@@ -1014,4 +778,4 @@ server.inject(request, function (res) { | ||
var payload = 'Here is my payload'; | ||
var authHeader = Hawk.client.header('http://0.0.0.0:8080/hawkPayloadOptional', 'POST', { credentials: credentials.john.cred }); | ||
var request = { method: 'POST', url: '/hawkPayloadOptional', headers: { authorization: authHeader.field, host: '0.0.0.0:8080' }, payload: payload }; | ||
var authHeader = Hawk.client.header('http://example.com:8080/hawkPayloadOptional', 'POST', { credentials: credentials.john.cred }); | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawkPayloadOptional', headers: { authorization: authHeader.field }, payload: payload }; | ||
@@ -1029,4 +793,4 @@ server.inject(request, function (res) { | ||
var payload = 'Here is my payload'; | ||
var authHeader = Hawk.client.header('http://0.0.0.0:8080/hawkPayloadNone', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
var request = { method: 'POST', url: '/hawkPayloadNone', headers: { authorization: authHeader.field, host: '0.0.0.0:8080' }, payload: payload }; | ||
var authHeader = Hawk.client.header('http://example.com:8080/hawkPayloadNone', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawkPayloadNone', headers: { authorization: authHeader.field }, payload: payload }; | ||
@@ -1044,5 +808,5 @@ server.inject(request, function (res) { | ||
var payload = 'Here is my payload'; | ||
var authHeader = Hawk.client.header('http://0.0.0.0:8080/hawkPayloadNone', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
var authHeader = Hawk.client.header('http://example.com:8080/hawkPayloadNone', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
payload += 'HACKED'; | ||
var request = { method: 'POST', url: '/hawkPayloadNone', headers: { authorization: authHeader.field, host: '0.0.0.0:8080' }, payload: payload }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawkPayloadNone', headers: { authorization: authHeader.field }, payload: payload }; | ||
@@ -1060,4 +824,4 @@ server.inject(request, function (res) { | ||
var payload = 'Here is my payload'; | ||
var authHeader = Hawk.client.header('http://0.0.0.0:8080/hawkOptionalPayload', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
var request = { method: 'POST', url: '/hawkOptionalPayload', headers: { authorization: authHeader.field, host: '0.0.0.0:8080' }, payload: payload }; | ||
var authHeader = Hawk.client.header('http://example.com:8080/hawkOptionalPayload', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawkOptionalPayload', headers: { authorization: authHeader.field }, payload: payload }; | ||
@@ -1075,5 +839,5 @@ server.inject(request, function (res) { | ||
var payload = 'Here is my payload'; | ||
var authHeader = Hawk.client.header('http://0.0.0.0:8080/hawkOptionalPayload', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
var authHeader = Hawk.client.header('http://example.com:8080/hawkOptionalPayload', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
payload += 'HACKED'; | ||
var request = { method: 'POST', url: '/hawkOptionalPayload', headers: { authorization: authHeader.field, host: '0.0.0.0:8080' }, payload: payload }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/hawkOptionalPayload', headers: { authorization: authHeader.field }, payload: payload }; | ||
@@ -1117,3 +881,3 @@ server.inject(request, function (res) { | ||
if (credentials[id] && credentials[id].cred) { | ||
return Hawk.uri.getBewit('http://0.0.0.0:8080' + path, { credentials: credentials[id].cred, ttlSec: 60 }); | ||
return Hawk.uri.getBewit('http://example.com:8080' + path, { credentials: credentials[id].cred, ttlSec: 60 }); | ||
} | ||
@@ -1143,3 +907,3 @@ else { | ||
{ method: 'GET', path: '/bewitScope', handler: bewitHandler, config: { auth: { scope: 'x' } } }, | ||
{ method: 'GET', path: '/bewitTos', handler: bewitHandler, config: { auth: { tos: 200 } } } | ||
{ method: 'GET', path: '/bewitTos', handler: bewitHandler, config: { auth: { tos: '2.0.0' } } } | ||
]); | ||
@@ -1150,6 +914,4 @@ | ||
var bewit = getBewit('john', '/bewit'); | ||
var request = { method: 'GET', url: '/bewit?bewit=' + bewit, headers: { host: '0.0.0.0:8080' }}; | ||
server.inject('http://example.com:8080/bewit?bewit=' + bewit, function (res) { | ||
server.inject(request, function (res) { | ||
expect(res.result).to.equal('Success'); | ||
@@ -1163,6 +925,4 @@ done(); | ||
var bewit = getBewit('john', '/abc'); | ||
var request = { method: 'GET', url: '/bewitOptional?bewit=' + bewit, headers: { host: 'example.com:8080' } }; | ||
server.inject('http://example.com:8080/bewitOptional?bewit=' + bewit, function (res) { | ||
server.inject(request, function (res) { | ||
expect(res.result.code).to.equal(401); | ||
@@ -1176,6 +936,4 @@ done(); | ||
var bewit = getBewit('john', '/abc'); | ||
var request = { method: 'GET', url: '/bewit?bewit=' + bewit, headers: { host: '0.0.0.0:8080' } }; | ||
server.inject('http://example.com:8080/bewit?bewit=' + bewit, function (res) { | ||
server.inject(request, function (res) { | ||
expect(res.result.code).to.equal(401); | ||
@@ -1188,6 +946,4 @@ done(); | ||
var request = { method: 'GET', url: '/bewit?bewit=junk', headers: { host: '0.0.0.0:8080' } }; | ||
server.inject('http://example.com:8080/bewit?bewit=junk', function (res) { | ||
server.inject(request, function (res) { | ||
expect(res.result.code).to.equal(400); | ||
@@ -1201,6 +957,4 @@ done(); | ||
var bewit = getBewit('john', '/bewitTos'); | ||
var request = { method: 'GET', url: '/bewitTos?bewit=' + bewit, headers: { host: '0.0.0.0:8080' } }; | ||
server.inject('http://example.com:8080/bewitTos?bewit=' + bewit, function (res) { | ||
server.inject(request, function (res) { | ||
expect(res.result.code).to.equal(403); | ||
@@ -1214,6 +968,4 @@ done(); | ||
var bewit = getBewit('john', '/bewitScope'); | ||
var request = { method: 'GET', url: '/bewitScope?bewit=' + bewit, headers: { host: '0.0.0.0:8080' } }; | ||
server.inject('http://example.com:8080/bewitScope?bewit=' + bewit, function (res) { | ||
server.inject(request, function (res) { | ||
expect(res.result.code).to.equal(403); | ||
@@ -1227,3 +979,3 @@ done(); | ||
var bewit = getBewit('john', '/bewit'); | ||
var request = { method: 'GET', url: '/bewit?bewit=' + bewit, headers: { custom: '0.0.0.0:8080' } }; | ||
var request = { method: 'GET', url: '/bewit?bewit=' + bewit, headers: { custom: 'example.com:8080' } }; | ||
@@ -1342,20 +1094,19 @@ var config = { | ||
var loadUser = function (id, callback) { | ||
var loadUser = function (username, password, callback) { | ||
if (id === 'john') { | ||
return callback(null, { | ||
if (username === 'john') { | ||
return callback(null, password === '12345', { | ||
user: 'john', | ||
scope: [], | ||
tos: 100 | ||
}, '12345'); | ||
tos: '1.0.0' | ||
}); | ||
} | ||
else if (id === 'jane') { | ||
else if (username === 'jane') { | ||
return callback(Hapi.error.internal('boom')); | ||
} | ||
else if (id === 'invalid') { | ||
return callback(null, {}); | ||
else if (username === 'invalid1') { | ||
return callback(null, true, 'bad'); | ||
} | ||
else { | ||
return callback(null, null); | ||
} | ||
return callback(null, false); | ||
}; | ||
@@ -1366,3 +1117,3 @@ | ||
if (credentials[id] && credentials[id].cred) { | ||
return Hawk.client.header('http://0.0.0.0:8080' + path, 'POST', { credentials: credentials[id].cred }).field; | ||
return Hawk.client.header('http://example.com:8080' + path, 'POST', { credentials: credentials[id].cred }).field; | ||
} | ||
@@ -1386,3 +1137,3 @@ else { | ||
scheme: 'basic', | ||
loadUserFunc: loadUser | ||
validateFunc: loadUser | ||
} | ||
@@ -1403,3 +1154,3 @@ } | ||
{ method: 'POST', path: '/multipleScope', handler: handler, config: { auth: { scope: 'x' } } }, | ||
{ method: 'POST', path: '/multipleTos', handler: handler, config: { auth: { tos: 200 } } }, | ||
{ method: 'POST', path: '/multipleTos', handler: handler, config: { auth: { tos: '2.0.0' } } }, | ||
{ method: 'POST', path: '/multiplePayload', handler: handler, config: { auth: { strategies: ['basic', 'hawk'], payload: 'optional' }, payload: 'raw' } } | ||
@@ -1410,3 +1161,3 @@ ]); | ||
var request = { method: 'POST', url: '/multiple', headers: { authorization: basicHeader('john', '12345'), host: '0.0.0.0:8080' } }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/multiple', headers: { authorization: basicHeader('john', '12345') } }; | ||
@@ -1422,3 +1173,3 @@ server.inject(request, function (res) { | ||
var request = { method: 'POST', url: '/multiple', headers: { authorization: hawkHeader('john', '/multiple'), host: '0.0.0.0:8080' } }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/multiple', headers: { authorization: hawkHeader('john', '/multiple') } }; | ||
@@ -1434,3 +1185,3 @@ server.inject(request, function (res) { | ||
var request = { method: 'POST', url: '/multiple', headers: { authorization: 'Basic fail', host: '0.0.0.0:8080' } }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/multiple', headers: { authorization: 'Basic fail' } }; | ||
@@ -1446,3 +1197,3 @@ server.inject(request, function (res) { | ||
var request = { method: 'POST', url: '/multiple', headers: { host: '0.0.0.0:8080' } }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/multiple'}; | ||
@@ -1458,3 +1209,3 @@ server.inject(request, function (res) { | ||
var request = { method: 'POST', url: '/multiple', headers: { host: '0.0.0.0:8080' } }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/multiple' }; | ||
@@ -1471,3 +1222,3 @@ server.inject(request, function (res) { | ||
var request = { method: 'POST', url: '/multiple', headers: { authorization: 'Basic fail; Hawk fail', host: '0.0.0.0:8080' } }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/multiple', headers: { authorization: 'Basic fail; Hawk fail' } }; | ||
@@ -1483,3 +1234,3 @@ server.inject(request, function (res) { | ||
var request = { method: 'POST', url: '/multiple', headers: { authorization: 'Basic fail; ' + hawkHeader('john', '/multiple'), host: '0.0.0.0:8080' } }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/multiple', headers: { authorization: 'Basic fail; ' + hawkHeader('john', '/multiple') } }; | ||
@@ -1495,3 +1246,3 @@ server.inject(request, function (res) { | ||
var request = { method: 'POST', url: '/multiple', headers: { authorization: hawkHeader('john', 'abcd'), host: '0.0.0.0:8080' } }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/multiple', headers: { authorization: hawkHeader('john', 'abcd') } }; | ||
@@ -1520,5 +1271,5 @@ server.inject(request, function (res) { | ||
var payload = 'Here is my payload'; | ||
var authHeader = Hawk.client.header('http://0.0.0.0:8080/multiplePayload', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
var authHeader = Hawk.client.header('http://example.com:8080/multiplePayload', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
payload += 'HACKED'; | ||
var request = { method: 'POST', url: '/multiplePayload', headers: { authorization: authHeader.field, host: '0.0.0.0:8080' }, payload: payload }; | ||
var request = { method: 'POST', url: 'http://example.com:8080/multiplePayload', headers: { authorization: authHeader.field }, payload: payload }; | ||
@@ -1536,4 +1287,4 @@ server.inject(request, function (res) { | ||
var payload = 'Here is my payload'; | ||
var authHeader = Hawk.client.header('http://0.0.0.0:8080/multiplePayload', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
var request = { method: 'POST', url: '/multiplePayload', headers: { authorization: authHeader.field, host: '0.0.0.0:8080' }, payload: payload }; | ||
var authHeader = Hawk.client.header('http://example.com:8080/multiplePayload', 'POST', { credentials: credentials.john.cred, payload: payload }); | ||
var request = { method: 'POST', url: 'http://example.com:8080/multiplePayload', headers: { authorization: authHeader.field }, payload: payload }; | ||
@@ -1562,3 +1313,3 @@ server.inject(request, function (res) { | ||
return callback(session.user === 'valid' ? null : new Error('bad user'), override); | ||
return callback(null, session.user === 'valid', override); | ||
} | ||
@@ -1600,3 +1351,3 @@ }; | ||
server.inject({ method: 'GET', url: '/login/valid' }, function (res) { | ||
server.inject('/login/valid', function (res) { | ||
@@ -1620,3 +1371,3 @@ expect(res.result).to.equal('valid'); | ||
server.inject({ method: 'GET', url: '/login/valid' }, function (res) { | ||
server.inject('/login/valid', function (res) { | ||
@@ -1641,3 +1392,3 @@ expect(res.result).to.equal('valid'); | ||
server.inject({ method: 'GET', url: '/login/invalid' }, function (res) { | ||
server.inject('/login/invalid', function (res) { | ||
@@ -1659,2 +1410,52 @@ expect(res.result).to.equal('invalid'); | ||
it('authenticates a request', function (done) { | ||
var config = { | ||
scheme: 'cookie', | ||
password: 'password', | ||
ttl: 60 * 1000, | ||
cookie: 'special', | ||
clearInvalid: true | ||
}; | ||
var server = new Hapi.Server({ auth: config }); | ||
server.route({ | ||
method: 'GET', path: '/login/{user}', | ||
config: { | ||
auth: { mode: 'try' }, | ||
handler: function () { | ||
this.auth.session.set({ user: this.params.user }); | ||
return this.reply(this.params.user); | ||
} | ||
} | ||
}); | ||
server.route({ | ||
method: 'GET', path: '/resource', handler: function () { | ||
expect(this.auth.credentials.user).to.equal('steve'); | ||
return this.reply('resource'); | ||
}, | ||
config: { auth: 'default' } | ||
}); | ||
server.inject('/login/steve', function (res) { | ||
expect(res.result).to.equal('steve'); | ||
var header = res.headers['set-cookie']; | ||
expect(header.length).to.equal(1); | ||
expect(header[0]).to.contain('Max-Age=60'); | ||
var cookie = header[0].match(/(?:[^\x00-\x20\(\)<>@\,;\:\\"\/\[\]\?\=\{\}\x7F]+)\s*=\s*(?:([^\x00-\x20\"\,\;\\\x7F]*))/); | ||
server.inject({ method: 'GET', url: '/resource', headers: { cookie: 'special=' + cookie[1] } }, function (res) { | ||
expect(res.statusCode).to.equal(200); | ||
expect(res.result).to.equal('resource'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('redirection', function (done) { | ||
@@ -1669,7 +1470,3 @@ | ||
redirectTo: 'http://example.com/login', | ||
appendNext: true, | ||
validateFunc: function (session, callback) { | ||
return callback(); | ||
} | ||
appendNext: true | ||
}; | ||
@@ -1686,3 +1483,3 @@ | ||
server.inject({ method: 'GET', url: '/' }, function (res) { | ||
server.inject('/', function (res) { | ||
@@ -1703,7 +1500,3 @@ expect(res.result).to.equal('You are being redirected...'); | ||
redirectTo: 'http://example.com/login?mode=1', | ||
appendNext: true, | ||
validateFunc: function (session, callback) { | ||
return callback(); | ||
} | ||
appendNext: true | ||
}; | ||
@@ -1720,3 +1513,3 @@ | ||
server.inject({ method: 'GET', url: '/' }, function (res) { | ||
server.inject('/', function (res) { | ||
@@ -1737,7 +1530,3 @@ expect(res.result).to.equal('You are being redirected...'); | ||
redirectTo: 'http://example.com/login?mode=1', | ||
appendNext: false, | ||
validateFunc: function (session, callback) { | ||
return callback(); | ||
} | ||
appendNext: false | ||
}; | ||
@@ -1754,3 +1543,3 @@ | ||
server.inject({ method: 'GET', url: '/' }, function (res) { | ||
server.inject('/', function (res) { | ||
@@ -1771,7 +1560,3 @@ expect(res.result).to.equal('You are being redirected...'); | ||
redirectTo: 'http://example.com/login', | ||
appendNext: true, | ||
validateFunc: function (session, callback) { | ||
return callback(); | ||
} | ||
appendNext: true | ||
}; | ||
@@ -1788,3 +1573,3 @@ | ||
server.inject({ method: 'GET', url: '/' }, function (res) { | ||
server.inject('/', function (res) { | ||
@@ -1791,0 +1576,0 @@ expect(res.result).to.equal('try'); |
@@ -24,3 +24,3 @@ // Load modules | ||
var _server = null; | ||
var server = null; | ||
@@ -45,3 +45,3 @@ var profileHandler = function (request) { | ||
var cacheable = new Hapi.Response.Text('hello'); | ||
var cacheable = new Hapi.response.Text('hello'); | ||
cacheable._code = 200; | ||
@@ -54,3 +54,3 @@ | ||
request.reply(new Stream); | ||
request.reply(new Stream()); | ||
}; | ||
@@ -66,23 +66,7 @@ | ||
var notCacheableHandler = function (request) { | ||
var response = new Hapi.Response.Raw(request) | ||
.type('text/plain') | ||
.bytes(13) | ||
.ttl(1000); | ||
response.begin(function (err) { | ||
response.write('!hola ') | ||
.write('amigos!'); | ||
request.reply(response); | ||
}); | ||
}; | ||
function setupServer(done) { | ||
_server = new Hapi.Server('0.0.0.0', 0); | ||
server = new Hapi.Server('0.0.0.0', 0, { debug: false }); | ||
_server.route([ | ||
server.route([ | ||
{ method: 'GET', path: '/profile', config: { handler: profileHandler, cache: { expiresIn: 120000, privacy: 'private' } } }, | ||
@@ -93,5 +77,4 @@ { method: 'GET', path: '/item', config: { handler: activeItemHandler, cache: { expiresIn: 120000 } } }, | ||
{ method: 'GET', path: '/bad', config: { handler: badHandler, cache: { mode: 'client+server', expiresIn: 120000 } } }, | ||
{ method: 'GET', path: '/cache', config: { handler: cacheItemHandler, cache: { mode: 'client+server', expiresIn: 120000, strict: true } } }, | ||
{ method: 'GET', path: '/error', config: { handler: errorHandler, cache: { mode: 'client+server', expiresIn: 120000, strict: true } } }, | ||
{ method: 'GET', path: '/notcacheablenostrict', config: { handler: notCacheableHandler, cache: { mode: 'client+server', expiresIn: 120000, strict: false } } }, | ||
{ method: 'GET', path: '/cache', config: { handler: cacheItemHandler, cache: { mode: 'client+server', expiresIn: 120000 } } }, | ||
{ method: 'GET', path: '/error', config: { handler: errorHandler, cache: { mode: 'client+server', expiresIn: 120000 } } }, | ||
{ method: 'GET', path: '/clientserver', config: { handler: profileHandler, cache: { mode: 'client+server', expiresIn: 120000 } } }, | ||
@@ -101,13 +84,5 @@ { method: 'GET', path: '/serverclient', config: { handler: profileHandler, cache: { mode: 'server+client', expiresIn: 120000 } } } | ||
_server.start(done); | ||
server.start(done); | ||
} | ||
var makeRequest = function (path, callback) { | ||
_server.inject({ | ||
method: 'get', | ||
url: path | ||
}, callback); | ||
}; | ||
before(setupServer); | ||
@@ -117,5 +92,5 @@ | ||
makeRequest('/profile', function (rawRes) { | ||
server.inject('/profile', function (res) { | ||
expect(rawRes.headers['cache-control']).to.equal('max-age=120, must-revalidate, private'); | ||
expect(res.headers['cache-control']).to.equal('max-age=120, must-revalidate, private'); | ||
done(); | ||
@@ -127,5 +102,5 @@ }); | ||
makeRequest('/profile', function (rawRes) { | ||
server.inject('/profile', function (res) { | ||
expect(rawRes.headers['cache-control']).to.equal('max-age=120, must-revalidate, private'); | ||
expect(res.headers['cache-control']).to.equal('max-age=120, must-revalidate, private'); | ||
done(); | ||
@@ -137,5 +112,5 @@ }); | ||
makeRequest('/item2', function (rawRes) { | ||
server.inject('/item2', function (res) { | ||
expect(rawRes.headers['cache-control']).to.not.equal('max-age=120, must-revalidate'); | ||
expect(res.headers['cache-control']).to.not.equal('max-age=120, must-revalidate'); | ||
done(); | ||
@@ -145,11 +120,9 @@ }); | ||
it('throws error when returning a stream in a cached endpoint handler', function (done) { | ||
it('returns 500 when returning a stream in a cached endpoint handler', function (done) { | ||
function test() { | ||
server.inject('/bad', function (res) { | ||
makeRequest('/bad', function (rawRes) { }); | ||
} | ||
expect(test).to.throw(Error); | ||
done(); | ||
expect(res.statusCode).to.equal(500); | ||
done(); | ||
}); | ||
}); | ||
@@ -159,5 +132,5 @@ | ||
makeRequest('/error', function () { | ||
server.inject('/error', function () { | ||
_server.plugin._cache.get({ segment: '/error', id: '/error' }, function (err, cached) { | ||
server.pack._cache.get({ segment: '/error', id: '/error' }, function (err, cached) { | ||
@@ -172,3 +145,3 @@ expect(cached).to.not.exist; | ||
makeRequest('/nocache', function (res) { | ||
server.inject('/nocache', function (res) { | ||
@@ -182,5 +155,5 @@ expect(res.headers['cache-control']).to.equal('no-cache'); | ||
makeRequest('/cache', function () { | ||
server.inject('/cache', function () { | ||
_server.plugin._cache.get({ segment: '/cache', id: '/cache' }, function (err, cached) { | ||
server.pack._cache.get({ segment: '/cache', id: '/cache' }, function (err, cached) { | ||
@@ -193,33 +166,7 @@ expect(cached).to.exist; | ||
it('doesn\'t throw an error when requesting a non-strict route that is not cacheable', function (done) { | ||
makeRequest('/notcacheablenostrict', function (res) { | ||
expect(res.statusCode).to.equal(200); | ||
done(); | ||
}); | ||
}); | ||
it('throws an error when requesting a strict cached route that is not cacheable', function (done) { | ||
var server = new Hapi.Server('0.0.0.0', 18885); | ||
server.route({ method: 'GET', path: '/notcacheable', config: { handler: notCacheableHandler, cache: { mode: 'client+server', expiresIn: 120000, strict: true } } }); | ||
var fn = function () { | ||
server.inject({ | ||
method: 'get', | ||
url: 'http://127.0.0.1:18885/notcacheable' | ||
}); | ||
}; | ||
expect(fn).to.throw(Error, 'Attempted to cache non-cacheable item'); | ||
done(); | ||
}); | ||
it('caches server+client the same as client+server', function (done) { | ||
makeRequest('/serverclient', function (res1) { | ||
server.inject('/serverclient', function (res1) { | ||
_server.plugin._cache.get({ segment: '/serverclient', id: '/serverclient' }, function (err1, cached1) { | ||
server.pack._cache.get({ segment: '/serverclient', id: '/serverclient' }, function (err1, cached1) { | ||
@@ -230,5 +177,5 @@ expect(cached1).to.exist; | ||
makeRequest('/clientserver', function (res2) { | ||
server.inject('/clientserver', function (res2) { | ||
_server.plugin._cache.get({ segment: '/clientserver', id: '/clientserver' }, function (err2, cached2) { | ||
server.pack._cache.get({ segment: '/clientserver', id: '/clientserver' }, function (err2, cached2) { | ||
@@ -235,0 +182,0 @@ expect(cached2).to.exist; |
@@ -30,32 +30,23 @@ // Load modules | ||
var directHandler = function (request) { | ||
var streamHandler = function (request) { | ||
var response = new Hapi.Response.Raw(request) | ||
.created('me') | ||
.type('text/plain') | ||
.bytes(13) | ||
.ttl(1000); | ||
var TestStream = function () { | ||
response.begin(function (err) { | ||
Stream.Readable.call(this); | ||
}; | ||
response.write('!hola ') | ||
.write('amigos!'); | ||
Hapi.utils.inherits(TestStream, Stream.Readable); | ||
setTimeout(function () { | ||
TestStream.prototype._read = function (size) { | ||
request.reply(response); | ||
}, 55); | ||
}); | ||
}; | ||
var self = this; | ||
var streamHandler = function (request) { | ||
if (this.isDone) { | ||
return; | ||
} | ||
this.isDone = true; | ||
var s = new Stream(); | ||
s.readable = true; | ||
s.resume = function () { | ||
setTimeout(function () { | ||
s.emit('data', 'Hello'); | ||
self.push('Hello'); | ||
}, 60); | ||
@@ -65,7 +56,7 @@ | ||
s.emit('end'); | ||
self.push(null); | ||
}, 70); | ||
}; | ||
request.reply.stream(s).send(); | ||
request.reply(new TestStream()); | ||
}; | ||
@@ -78,3 +69,2 @@ | ||
{ method: 'POST', path: '/fast', config: { handler: fastHandler } }, | ||
{ method: 'GET', path: '/direct', config: { handler: directHandler } }, | ||
{ method: 'GET', path: '/stream', config: { handler: streamHandler } } | ||
@@ -93,3 +83,3 @@ ]); | ||
hostname: '127.0.0.1', | ||
port: _server.settings.port, | ||
port: _server.info.port, | ||
path: '/fast', | ||
@@ -122,3 +112,3 @@ method: 'POST' | ||
hostname: '127.0.0.1', | ||
port: _server.settings.port, | ||
port: _server.info.port, | ||
path: '/fast', | ||
@@ -138,21 +128,2 @@ method: 'POST' | ||
it('doesn\'t return a client error message when response is direct type', function (done) { | ||
var options = { | ||
hostname: '127.0.0.1', | ||
port: _server.settings.port, | ||
path: '/direct', | ||
method: 'GET' | ||
}; | ||
var req = Http.request(options, function (res) { | ||
expect(res.statusCode).to.equal(201); | ||
done(); | ||
}); | ||
req.end(); | ||
}); | ||
it('doesn\'t return a client error message when response is taking a long time to send', function (done) { | ||
@@ -163,3 +134,3 @@ | ||
hostname: '127.0.0.1', | ||
port: _server.settings.port, | ||
port: _server.info.port, | ||
path: '/stream', | ||
@@ -202,3 +173,3 @@ method: 'GET' | ||
hostname: '127.0.0.1', | ||
port: _server.settings.port, | ||
port: _server.info.port, | ||
path: '/fast', | ||
@@ -205,0 +176,0 @@ method: 'POST' |
@@ -27,3 +27,8 @@ // Load modules | ||
pack: { | ||
cache: 'memory' | ||
cache: { | ||
engine: 'memory' | ||
}, | ||
app: { | ||
my: 'special-value' | ||
} | ||
}, | ||
@@ -46,6 +51,3 @@ servers: [ | ||
plugins: { | ||
'../test/integration/pack/--test1': {} | ||
}, | ||
permissions: { | ||
ext: true | ||
'../test/integration/pack/--test1': [ { ext: true }, {} ] | ||
} | ||
@@ -65,3 +67,3 @@ }; | ||
expect(res.result).to.equal('testing123'); | ||
expect(res.result).to.equal('testing123special-value'); | ||
done(); | ||
@@ -78,3 +80,5 @@ }); | ||
pack: { | ||
cache: 'memory' | ||
cache: { | ||
engine: 'memory' | ||
} | ||
}, | ||
@@ -81,0 +85,0 @@ servers: [ |
@@ -122,3 +122,3 @@ // Load modules | ||
Request.post({ url: server.settings.uri, headers: { 'accept-encoding': 'gzip' }, body: rawBody }, function (err, res, body) { | ||
Request.post({ url: server.info.uri, headers: { 'accept-encoding': 'gzip' }, body: rawBody }, function (err, res, body) { | ||
@@ -140,3 +140,3 @@ expect(body).to.equal(zippedBody.toString()); | ||
Request.post({ url: server.settings.uri, headers: { 'accept-encoding': '*' }, body: rawBody }, function (err, res, body) { | ||
Request.post({ url: server.info.uri, headers: { 'accept-encoding': '*' }, body: rawBody }, function (err, res, body) { | ||
@@ -158,3 +158,3 @@ expect(body).to.equal(zippedBody.toString()); | ||
Request.post({ url: server.settings.uri, headers: { 'accept-encoding': 'deflate' }, body: rawBody }, function (err, res, body) { | ||
Request.post({ url: server.info.uri, headers: { 'accept-encoding': 'deflate' }, body: rawBody }, function (err, res, body) { | ||
@@ -176,3 +176,3 @@ expect(body).to.equal(zippedBody.toString()); | ||
Request.post({ url: server.settings.uri, headers: { 'accept-encoding': 'gzip,q=1; deflate,q=.5' }, body: rawBody }, function (err, res, body) { | ||
Request.post({ url: server.info.uri, headers: { 'accept-encoding': 'gzip,q=1; deflate,q=.5' }, body: rawBody }, function (err, res, body) { | ||
@@ -194,3 +194,3 @@ expect(body).to.equal(zippedBody.toString()); | ||
Request.post({ url: server.settings.uri, headers: { 'accept-encoding': 'deflate,q=1; gzip,q=.5' }, body: rawBody }, function (err, res, body) { | ||
Request.post({ url: server.info.uri, headers: { 'accept-encoding': 'deflate,q=1; gzip,q=.5' }, body: rawBody }, function (err, res, body) { | ||
@@ -212,3 +212,3 @@ expect(body).to.equal(zippedBody.toString()); | ||
Request.post({ url: server.settings.uri, headers: { 'accept-encoding': 'deflate, gzip' }, body: rawBody }, function (err, res, body) { | ||
Request.post({ url: server.info.uri, headers: { 'accept-encoding': 'deflate, gzip' }, body: rawBody }, function (err, res, body) { | ||
@@ -228,3 +228,3 @@ expect(body).to.equal(zippedBody.toString()); | ||
Request.post({ url: server.settings.uri, headers: { }, body: rawBody }, function (err, res, body) { | ||
Request.post({ url: server.info.uri, headers: { }, body: rawBody }, function (err, res, body) { | ||
@@ -231,0 +231,0 @@ expect(body).to.equal(rawBody); |
// Load modules | ||
var Domain = require('domain'); | ||
var Lab = require('lab'); | ||
@@ -90,3 +91,3 @@ var Hapi = require('../..'); | ||
expect(pack._servers[0].plugin.list.test.version).to.equal('5.0.0'); | ||
expect(pack._servers[0].pack.list.test.version).to.equal('5.0.0'); | ||
@@ -132,3 +133,3 @@ done(); | ||
var server = new Hapi.Server(); | ||
server.plugin.allow({ route: true }).register(plugin, { something: true }, function (err) { | ||
server.pack.allow({ route: true }).register(plugin, { something: true }, function (err) { | ||
@@ -140,2 +141,22 @@ expect(err).to.not.exist; | ||
it('registers plugin via server plugin interface (inline permission)', function (done) { | ||
var plugin = { | ||
name: 'test', | ||
version: '2.0.0', | ||
register: function (pack, options, next) { | ||
expect(options.something).to.be.true; | ||
next(); | ||
} | ||
}; | ||
var server = new Hapi.Server(); | ||
server.pack.register(plugin, [{ route: true }, { something: true }], function (err) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
it('throws when pack server contains cache configuration', function (done) { | ||
@@ -159,3 +180,3 @@ | ||
pack.allow({ route: true }).require('./pack/--test1', {}, function (err) { | ||
pack.require('./pack/--test1', [{ route: true }, {}], function (err) { | ||
@@ -201,3 +222,3 @@ expect(err).to.not.exist; | ||
var server = new Hapi.Server(); | ||
server.plugin.register(plugin, function (err) { | ||
server.pack.register(plugin, function (err) { | ||
@@ -209,3 +230,3 @@ expect(err).to.not.exist; | ||
server.plugin.register(plugin, function (err) { }); | ||
server.pack.register(plugin, function (err) { }); | ||
}).to.throw(); | ||
@@ -220,6 +241,14 @@ | ||
var server = new Hapi.Server({ labels: 'test' }); | ||
server.plugin.require(['./pack/--test1', './pack/--test2'], function (err) { | ||
var log = null; | ||
server.pack.events.once('log', function (event, tags) { | ||
log = [event, tags]; | ||
}); | ||
server.pack.require(['./pack/--test1', './pack/--test2'], function (err) { | ||
expect(err).to.not.exist; | ||
expect(routesList(server)).to.deep.equal(['/test1', '/test2']); | ||
expect(routesList(server)).to.deep.equal(['/test1', '/test2', '/test2/path']); | ||
expect(log[1].test).to.equal(true); | ||
expect(log[0].data).to.equal('abc'); | ||
done(); | ||
@@ -232,6 +261,6 @@ }); | ||
var server = new Hapi.Server({ labels: 'test' }); | ||
server.plugin.require({ './pack/--test1': {}, './pack/--test2': {} }, function (err) { | ||
server.pack.require({ './pack/--test1': [{ route: true }, {}], './pack/--test2': {} }, function (err) { | ||
expect(err).to.not.exist; | ||
expect(routesList(server)).to.deep.equal(['/test1', '/test2']); | ||
expect(routesList(server)).to.deep.equal(['/test1', '/test2', '/test2/path']); | ||
done(); | ||
@@ -241,6 +270,20 @@ }); | ||
it('exposes the plugin path', function (done) { | ||
var server = new Hapi.Server({ labels: 'test' }); | ||
server.pack.require('./pack/--test2', function (err) { | ||
expect(err).to.not.exist; | ||
server.inject('/test2/path', function (res) { | ||
expect(res.result).to.equal(process.cwd() + '/test/integration/pack/--test2'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('requires plugin with views', function (done) { | ||
var server = new Hapi.Server(); | ||
server.plugin.require({ './pack/--views': { message: 'viewing it' } }, function (err) { | ||
server.pack.require({ './pack/--views': { message: 'viewing it' } }, function (err) { | ||
@@ -342,3 +385,3 @@ expect(err).to.not.exist; | ||
var server = new Hapi.Server(); | ||
server.plugin.allow({ ext: true }).register(plugin, function (err) { | ||
server.pack.allow({ route: true }).register(plugin, [{ ext: true }], function (err) { | ||
@@ -365,3 +408,3 @@ expect(err).to.not.exist; | ||
return this.reply(this.plugins.deps); | ||
return this.reply(this.app.deps); | ||
}; | ||
@@ -400,4 +443,4 @@ | ||
server.plugin.allow({ ext: true }).require('./pack/--deps1', function (err) { }); | ||
}).to.throw('Plugin \'--deps1\' missing dependencies: --deps2'); | ||
server.pack.allow({ ext: true }).require('./pack/--deps1', [{ ext: true }], function (err) { }); | ||
}).to.throw('Plugin --deps1 missing dependencies: --deps2'); | ||
done(); | ||
@@ -421,4 +464,4 @@ }); | ||
server.plugin.allow({ ext: true }).register(plugin, function (err) { }); | ||
}).to.throw('Plugin \'test\' missing dependencies: none'); | ||
server.pack.allow({ ext: true }).register(plugin, function (err) { }); | ||
}).to.throw('Plugin test missing dependencies: none'); | ||
done(); | ||
@@ -430,7 +473,14 @@ }); | ||
var server = new Hapi.Server(); | ||
expect(function () { | ||
server.plugin.allow({ ext: true }).require(['./pack/--deps1', './pack/--deps3'], function (err) { }); | ||
}).to.throw('Plugin \'--deps1\' missing dependencies: --deps2'); | ||
done(); | ||
var domain = Domain.create(); | ||
domain.on('error', function (err) { | ||
expect(err.message).to.equal('Plugin --deps1 missing dependencies: --deps2'); | ||
done(); | ||
}); | ||
domain.run(function () { | ||
server.pack.allow({ ext: true }).require(['./pack/--deps1', './pack/--deps3'], function (err) { }); | ||
}); | ||
}); | ||
@@ -463,3 +513,3 @@ | ||
var server = new Hapi.Server(0); | ||
server.plugin.register(plugin, function (err) { | ||
server.pack.register(plugin, function (err) { | ||
@@ -496,3 +546,3 @@ expect(err).to.not.exist; | ||
server.plugin.require('./pack/--auth', function (err) { | ||
server.pack.require('./pack/--auth', function (err) { | ||
@@ -499,0 +549,0 @@ expect(err).to.not.exist; |
@@ -10,9 +10,9 @@ // Declare internals | ||
var loadUser = function (id, callback) { | ||
var loadUser = function (username, password, callback) { | ||
if (id === 'john') { | ||
return callback(null, { user: 'john' }, '12345'); | ||
if (username === 'john') { | ||
return callback(null, password === '12345', { user: 'john' }); | ||
} | ||
return callback(null, null); | ||
return callback(null, false); | ||
}; | ||
@@ -22,3 +22,3 @@ | ||
scheme: 'basic', | ||
loadUserFunc: loadUser, | ||
validateFunc: loadUser, | ||
defaultMode: true | ||
@@ -25,0 +25,0 @@ }); |
@@ -0,0 +0,0 @@ { |
@@ -14,4 +14,4 @@ // Declare internals | ||
request.plugins.deps = request.plugins.deps || '|'; | ||
request.plugins.deps += '1|' | ||
request.app.deps = request.app.deps || '|'; | ||
request.app.deps += '1|' | ||
cont(); | ||
@@ -18,0 +18,0 @@ }, { after: '--deps3' }); |
@@ -12,4 +12,4 @@ // Declare internals | ||
request.plugins.deps = request.plugins.deps || '|'; | ||
request.plugins.deps += '2|' | ||
request.app.deps = request.app.deps || '|'; | ||
request.app.deps += '2|' | ||
cont(); | ||
@@ -16,0 +16,0 @@ }, { after: '--deps3', before: '--deps1' }); |
@@ -12,4 +12,4 @@ // Declare internals | ||
request.plugins.deps = request.plugins.deps || '|'; | ||
request.plugins.deps += '3|' | ||
request.app.deps = request.app.deps || '|'; | ||
request.app.deps += '3|' | ||
cont(); | ||
@@ -16,0 +16,0 @@ }); |
@@ -0,0 +0,0 @@ // Plugin registration |
@@ -10,3 +10,3 @@ // Declare internals | ||
pack.select('test').route({ path: '/test1', method: 'GET', handler: function () { this.reply('testing123'); } }); | ||
pack.select('test').route({ path: '/test1', method: 'GET', handler: function () { this.reply('testing123' + ((pack.app && pack.app.my) || '')); } }); | ||
pack.api(internals.math); | ||
@@ -13,0 +13,0 @@ pack.api('glue', internals.text.glue); |
@@ -11,4 +11,6 @@ // Declare internals | ||
pack.route({ path: '/test2', method: 'GET', handler: function () { this.reply('testing123'); } }); | ||
pack.route({ path: '/test2/path', method: 'GET', handler: function () { this.reply(pack.path); } }); | ||
pack.log('test', 'abc'); | ||
return next(); | ||
}; | ||
@@ -10,3 +10,6 @@ // Declare internals | ||
pack.views({ path: './templates' }); | ||
pack.views({ | ||
engines: { 'html': 'handlebars' }, | ||
path: './templates' | ||
}); | ||
pack.route([ | ||
@@ -16,3 +19,3 @@ { | ||
return this.reply.view('test', { message: options.message }).send(); | ||
return this.reply.view('test', { message: options.message }); | ||
} | ||
@@ -19,0 +22,0 @@ }, |
@@ -69,3 +69,3 @@ // Load modules | ||
var multipartPayload = '{"x":"1","y":"2","z":"3"}'; | ||
var payload = '{"x":"1","y":"2","z":"3"}'; | ||
@@ -75,4 +75,4 @@ var handler = function (request) { | ||
expect(request.payload).to.not.exist; | ||
expect(request.rawBody).to.equal(multipartPayload); | ||
request.reply(request.rawBody); | ||
expect(request.rawPayload.toString()).to.equal(payload); | ||
request.reply(request.rawPayload); | ||
}; | ||
@@ -83,6 +83,6 @@ | ||
server.inject({ method: 'POST', url: '/', payload: multipartPayload }, function (res) { | ||
server.inject({ method: 'POST', url: '/', payload: payload }, function (res) { | ||
expect(res.result).to.exist; | ||
expect(res.result).to.equal(multipartPayload); | ||
expect(res.result).to.equal(payload); | ||
done(); | ||
@@ -94,3 +94,3 @@ }); | ||
var multipartPayload = '{"x":"1","y":"2","z":"3"}'; | ||
var payload = '{"x":"1","y":"2","z":"3"}'; | ||
@@ -101,3 +101,3 @@ var handler = function (request) { | ||
expect(request.payload.z).to.equal('3'); | ||
expect(request.rawBody).to.equal(multipartPayload); | ||
expect(request.rawPayload.toString()).to.equal(payload); | ||
request.reply(request.payload); | ||
@@ -109,3 +109,3 @@ }; | ||
server.inject({ method: 'POST', url: '/', payload: multipartPayload }, function (res) { | ||
server.inject({ method: 'POST', url: '/', payload: payload }, function (res) { | ||
@@ -129,4 +129,3 @@ expect(res.result).to.exist; | ||
var s = new Stream(); | ||
s.readable = true; | ||
var s = new Stream.PassThrough(); | ||
@@ -136,4 +135,4 @@ server.start(function () { | ||
var options = { | ||
hostname: '127.0.0.1', | ||
port: server.settings.port, | ||
hostname: 'localhost', | ||
port: server.info.port, | ||
path: '/', | ||
@@ -145,3 +144,3 @@ method: 'POST' | ||
s.emit('data', 'Hello'); | ||
s.write('Hello'); | ||
}, 5); | ||
@@ -183,4 +182,4 @@ | ||
var options = { | ||
hostname: '127.0.0.1', | ||
port: server.settings.port, | ||
hostname: 'localhost', | ||
port: server.info.port, | ||
path: '/', | ||
@@ -214,3 +213,3 @@ method: 'POST', | ||
var multipartPayload = '{"x":"1","y":"2","z":"3"}'; | ||
var payload = '{"x":"1","y":"2","z":"3"}'; | ||
@@ -220,4 +219,4 @@ var handler = function (request) { | ||
expect(request.payload).to.not.exist; | ||
expect(request.rawBody).to.equal(multipartPayload); | ||
request.reply(request.rawBody); | ||
expect(request.rawPayload.toString()).to.equal(payload); | ||
request.reply(request.rawPayload); | ||
}; | ||
@@ -228,6 +227,6 @@ | ||
server.inject({ method: 'POST', url: '/', payload: multipartPayload, headers: { 'Content-Length': '5' } }, function (res) { | ||
server.inject({ method: 'POST', url: '/', payload: payload, headers: { 'Content-Length': '5' } }, function (res) { | ||
expect(res.result).to.exist; | ||
expect(res.result).to.equal(multipartPayload); | ||
expect(res.result).to.equal(payload); | ||
done(); | ||
@@ -239,3 +238,3 @@ }); | ||
var multipartPayload = '{"x":"1","y":"2","z":"3"}'; | ||
var payload = '{"x":"1","y":"2","z":"3"}'; | ||
@@ -245,4 +244,4 @@ var handler = function (request) { | ||
expect(request.payload).to.not.exist; | ||
expect(request.rawBody).to.equal(multipartPayload); | ||
request.reply(request.rawBody); | ||
expect(request.rawPayload.toString()).to.equal(payload); | ||
request.reply(request.rawPayload); | ||
}; | ||
@@ -253,6 +252,6 @@ | ||
server.inject({ method: 'POST', url: '/', payload: multipartPayload, headers: { 'Content-Length': '500' } }, function (res) { | ||
server.inject({ method: 'POST', url: '/', payload: payload, headers: { 'Content-Length': '500' } }, function (res) { | ||
expect(res.result).to.exist; | ||
expect(res.result).to.equal(multipartPayload); | ||
expect(res.result).to.equal(payload); | ||
done(); | ||
@@ -271,3 +270,3 @@ }); | ||
var server = new Hapi.Server('127.0.0.1', 0); | ||
var server = new Hapi.Server('localhost', 0); | ||
server.route({ method: 'POST', path: '/', config: { handler: handler, payload: 'stream' } }); | ||
@@ -283,4 +282,4 @@ | ||
var options = { | ||
hostname: '127.0.0.1', | ||
port: server.settings.port, | ||
hostname: 'localhost', | ||
port: server.info.port, | ||
path: '/', | ||
@@ -290,8 +289,7 @@ method: 'POST' | ||
var s = new Stream(); | ||
s.readable = true; | ||
var s = new Stream.PassThrough(); | ||
var iv = setInterval(function () { | ||
s.emit('data', 'Hello'); | ||
s.write('Hello'); | ||
}, 5); | ||
@@ -323,8 +321,8 @@ | ||
expect(request.payload.key).to.equal('value'); | ||
request.reply('Success'); | ||
request.reply(request.payload.key); | ||
}; | ||
var server = new Hapi.Server('127.0.0.1', 0, { timeout: { client: 50 } }); | ||
var server = new Hapi.Server('localhost', 0, { timeout: { client: 50 } }); | ||
server.route({ method: 'POST', path: '/', config: { handler: handler, payload: 'parse' } }); | ||
server.route({ method: '*', path: '/any', handler: handler }); | ||
@@ -336,8 +334,34 @@ before(function (done) { | ||
var TestStream = function () { | ||
Stream.Readable.call(this); | ||
}; | ||
Hapi.utils.inherits(TestStream, Stream.Readable); | ||
TestStream.prototype._read = function (size) { | ||
if (this.isDone) { | ||
return; | ||
} | ||
this.isDone = true; | ||
this.push('{ "key": "value" }'); | ||
this.push(null); | ||
}; | ||
it('sets parse mode when route methos is * and request is POST or PUT', function (done) { | ||
server.inject({ url: '/any', method: 'POST', headers: { 'Content-Type': 'application/json' }, payload: '{ "key": "09876" }' }, function (res) { | ||
expect(res.statusCode).to.equal(200); | ||
expect(res.result).to.equal('09876'); | ||
done(); | ||
}); | ||
}); | ||
it('sets the request payload with the streaming data', function (done) { | ||
var options = { | ||
hostname: '127.0.0.1', | ||
port: server.settings.port, | ||
path: '/', | ||
uri: 'http://localhost:' + server.info.port + '/?x=1', | ||
method: 'POST', | ||
@@ -349,18 +373,11 @@ headers: { | ||
var s = new Stream(); | ||
s.readable = true; | ||
var req = Request(options, function (err, res, body) { | ||
var req = Http.request(options, function (res) { | ||
expect(res.statusCode).to.equal(200); | ||
res.on('data', function (data) { | ||
expect(data.toString()).to.equal('Success'); | ||
done(); | ||
}); | ||
expect(body).to.equal('value'); | ||
done(); | ||
}); | ||
var s = new TestStream(); | ||
s.pipe(req); | ||
s.emit('data', '{ "key": "value" }'); | ||
req.end(); | ||
}); | ||
@@ -371,5 +388,5 @@ | ||
var options = { | ||
hostname: '127.0.0.1', | ||
port: server.settings.port, | ||
path: '/', | ||
hostname: 'localhost', | ||
port: server.info.port, | ||
path: '/?x=2', | ||
method: 'POST', | ||
@@ -391,8 +408,7 @@ headers: { | ||
it('returns a 400 status code when the request content-length is smaller than payload', function (done) { | ||
it('resets connection when the request content-length is smaller than payload', function (done) { | ||
var options = { | ||
hostname: '127.0.0.1', | ||
port: server.settings.port, | ||
path: '/', | ||
uri: 'http://localhost:' + server.info.port + '/?x=3', | ||
body: '{ "key": "value" }', | ||
method: 'POST', | ||
@@ -405,9 +421,7 @@ headers: { | ||
var req = Http.request(options, function (res) { | ||
Request(options, function (err, res, body) { | ||
expect(res.statusCode).to.equal(400); | ||
expect(err.message).to.equal('socket hang up'); | ||
done(); | ||
}); | ||
req.end('{ "key": "value" }'); | ||
}); | ||
@@ -418,5 +432,5 @@ | ||
var options = { | ||
hostname: '127.0.0.1', | ||
port: server.settings.port, | ||
path: '/', | ||
hostname: 'localhost', | ||
port: server.info.port, | ||
path: '/?x=4', | ||
method: 'POST', | ||
@@ -441,5 +455,5 @@ headers: { | ||
var options = { | ||
hostname: '127.0.0.1', | ||
port: server.settings.port, | ||
path: '/', | ||
hostname: 'localhost', | ||
port: server.info.port, | ||
path: '/?x=5', | ||
method: 'POST', | ||
@@ -460,52 +474,2 @@ headers: { | ||
}); | ||
it('returns a 400 status code when the request payload is streaming data with content-length being too small', function (done) { | ||
var s = new Stream(); | ||
s.readable = true; | ||
var options = { | ||
uri: 'http://127.0.0.1:' + server.settings.port + '/', | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
'Content-Length': '1' | ||
} | ||
}; | ||
var req = Request(options, function (err, res) { | ||
expect(res.statusCode).to.equal(400); | ||
done(); | ||
}); | ||
s.pipe(req); | ||
s.emit('data', '{ "key": "value" }'); | ||
req.end(); | ||
}); | ||
it('returns a 200 status code when the request payload is streaming data with content-length being smaller than payload but payload truncates to a valid value ', function (done) { | ||
var s = new Stream(); | ||
s.readable = true; | ||
var options = { | ||
uri: 'http://127.0.0.1:' + server.settings.port + '/', | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
'Content-Length': '18' | ||
} | ||
}; | ||
var req = Request(options, function (err, res) { | ||
expect(res.statusCode).to.equal(200); | ||
done(); | ||
}); | ||
s.pipe(req); | ||
s.emit('data', '{ "key": "value" } garbage here'); | ||
req.end(); | ||
}); | ||
}); | ||
@@ -517,7 +481,7 @@ | ||
var multipartPayload = '7d8d78347h8347d58w347hd58w374d58w37h5d8w37hd4'; | ||
var payload = '7d8d78347h8347d58w347hd58w374d58w37h5d8w37hd4'; | ||
var handler = function (request) { | ||
var handler = function () { | ||
expect(request).to.not.exist; // Must not be called | ||
throw new Error('never called'); | ||
}; | ||
@@ -528,3 +492,3 @@ | ||
server.inject({ method: 'POST', url: '/', payload: multipartPayload, headers: { 'content-encoding': 'gzip' } }, function (res) { | ||
server.inject({ method: 'POST', url: '/', payload: payload, headers: { 'content-encoding': 'gzip' } }, function (res) { | ||
@@ -539,9 +503,9 @@ expect(res.result).to.exist; | ||
var multipartPayload = '{"hi":"hello"}'; | ||
var payload = '{"hi":"hello"}'; | ||
Zlib.gzip(multipartPayload, function (err, result) { | ||
Zlib.gzip(payload, function (err, result) { | ||
var handler = function (request) { | ||
var handler = function () { | ||
request.reply('Success'); | ||
this.reply('Success'); | ||
}; | ||
@@ -628,7 +592,14 @@ | ||
var file = Fs.readFileSync(Path.join(__dirname, '../../images/hapi.png')); | ||
var path = Path.join(__dirname, '../../images/hapi.png'); | ||
var stats = Fs.statSync(path); | ||
var fileStream = Fs.createReadStream(path); | ||
var fileContents = Fs.readFileSync(path, { encoding: 'binary' }); | ||
var fileHandler = function (request) { | ||
expect(request.raw.req.headers['content-type']).to.contain('multipart/form-data'); | ||
expect(request.payload['my_file']).to.contain('Photoshop'); | ||
expect(request.payload['my_file'].size).to.equal(stats.size); | ||
var tmpContents = Fs.readFileSync(request.payload['my_file'].path, { encoding: 'binary' }); | ||
expect(fileContents).to.deep.equal(tmpContents); | ||
done(); | ||
@@ -641,5 +612,5 @@ }; | ||
var r = Request.post(server.settings.uri + '/file'); | ||
var r = Request.post(server.info.uri + '/file'); | ||
var form = r.form(); | ||
form.append('my_file', file); | ||
form.append('my_file', fileStream); | ||
}); | ||
@@ -646,0 +617,0 @@ }); |
@@ -43,8 +43,8 @@ // Load modules | ||
this.reply.payload({ | ||
var profile = { | ||
'id': 'fa0dbda9b1b', | ||
'name': 'John Doe' | ||
}) | ||
.state('test', '123') | ||
.send(); | ||
}; | ||
this.reply(profile).state('test', '123'); | ||
}; | ||
@@ -62,6 +62,6 @@ | ||
this.reply.payload({ | ||
this.reply({ | ||
'id': '55cf687663', | ||
'name': 'Item' | ||
}).created('http://example.com').send(); | ||
}).created('http://example.com'); | ||
}; | ||
@@ -99,7 +99,6 @@ | ||
this.reply.payload({ status: 'success' }) | ||
this.reply({ status: 'success' }) | ||
.header('Custom1', 'custom header value 1') | ||
.header('X-Custom2', 'custom header value 2') | ||
.header('access-control-allow-headers', 'Invalid, List, Of, Values') | ||
.send(); | ||
.header('access-control-allow-headers', 'Invalid, List, Of, Values'); | ||
}; | ||
@@ -114,7 +113,7 @@ | ||
this.reply(new Hapi.Response.File(__dirname + '/../../package.json')); | ||
this.reply(new Hapi.response.File(__dirname + '/../../package.json')); | ||
}; | ||
var backendServer = new Hapi.Server(0); | ||
backendServer.route([ | ||
var upstream = new Hapi.Server(0); | ||
upstream.route([ | ||
{ method: 'GET', path: '/profile', handler: profile }, | ||
@@ -136,8 +135,8 @@ { method: 'GET', path: '/item', handler: activeItem }, | ||
return callback(null, backendServer.settings.uri + request.path + (request.url.search || '')); | ||
return callback(null, upstream.info.uri + request.path + (request.url.search || '')); | ||
}; | ||
backendServer.start(function () { | ||
upstream.start(function () { | ||
var backendPort = backendServer.settings.port; | ||
var backendPort = upstream.info.port; | ||
var routeCache = { expiresIn: 500, mode: 'server+client' }; | ||
@@ -175,7 +174,2 @@ | ||
var next = function (err, res) { | ||
return callback(res); | ||
}; | ||
options = options || {}; | ||
@@ -185,9 +179,14 @@ options.path = options.path || '/'; | ||
Request({ | ||
var req = { | ||
method: options.method, | ||
url: server.settings.uri + options.path, | ||
url: server.info.uri + options.path, | ||
form: options.form, | ||
headers: options.headers, | ||
jar: false | ||
}, next); | ||
}; | ||
Request(req, function (err, res) { | ||
callback(res); | ||
}); | ||
} | ||
@@ -231,2 +230,3 @@ | ||
expect(res.headers['access-control-allow-headers']).to.equal('Authorization, Content-Type, If-None-Match'); | ||
expect(res.headers['access-control-expose-headers']).to.equal('WWW-Authenticate, Server-Authorization'); | ||
done(); | ||
@@ -341,3 +341,3 @@ }); | ||
var route = server._router.route({ method: 'get', path: '/proxyerror', raw: { req: { headers: {} } } }); | ||
var route = server._router.route({ method: 'get', path: '/proxyerror', info: {}, raw: { req: { headers: {} } } }); | ||
route.proxy.httpClient = requestStub; | ||
@@ -373,3 +373,3 @@ | ||
Fs.createReadStream(__dirname + '/proxy.js').pipe(Request.post(server.settings.uri + '/file', function (err, rawRes, body) { | ||
Fs.createReadStream(__dirname + '/proxy.js').pipe(Request.post(server.info.uri + '/file', function (err, rawRes, body) { | ||
@@ -376,0 +376,0 @@ expect(rawRes.statusCode).to.equal(200); |
// Load modules | ||
var Lab = require('lab'); | ||
var Stream = require('stream'); | ||
var Request = require('request'); | ||
@@ -35,3 +36,3 @@ var Hapi = require('../..'); | ||
request.removeTail(t1); | ||
t1(); | ||
t1(); // Ignored | ||
@@ -57,3 +58,3 @@ setTimeout(t2, 10); | ||
else if (request.path === '/unknown/close') { | ||
request.reply.payload('unknown-close').send(); | ||
request.reply('unknown-close'); | ||
} | ||
@@ -68,5 +69,39 @@ else { | ||
request.reply('success'); | ||
request.raw.res.emit('error', new Error('fail')); | ||
setImmediate(function () { | ||
request.raw.res.emit('error', new Error('fail')); | ||
}); | ||
}; | ||
var streamErrorHandler = function (request) { | ||
var TestStream = function () { | ||
Stream.Readable.call(this); | ||
}; | ||
Hapi.utils.inherits(TestStream, Stream.Readable); | ||
TestStream.prototype._read = function (size) { | ||
var self = this; | ||
if (this.isDone) { | ||
return; | ||
} | ||
this.isDone = true; | ||
self.push('success'); | ||
self.push(null); | ||
setImmediate(function () { | ||
self.emit('error', new Error()); | ||
}); | ||
}; | ||
var stream = new TestStream(); | ||
request.reply(stream); | ||
}; | ||
var server = new Hapi.Server('0.0.0.0', 0, { cors: true }); | ||
@@ -78,3 +113,4 @@ server.ext('onPostHandler', postHandler); | ||
{ method: 'GET', path: '/ext', config: { handler: plainHandler } }, | ||
{ method: 'GET', path: '/response', config: { handler: responseErrorHandler } } | ||
{ method: 'GET', path: '/response', config: { handler: responseErrorHandler } }, | ||
{ method: 'GET', path: '/stream', config: { handler: streamErrorHandler } } | ||
]); | ||
@@ -84,18 +120,5 @@ | ||
var makeRequest = function (method, path, callback) { | ||
var next = function (res) { | ||
return callback(res); | ||
}; | ||
server.inject({ | ||
method: method, | ||
url: path | ||
}, next); | ||
}; | ||
it('returns custom error response', function (done) { | ||
makeRequest('GET', '/custom', function (res) { | ||
server.inject('/custom', function (res) { | ||
@@ -109,3 +132,3 @@ expect(res.headers['content-type']).to.equal('text/plain; charset=utf-8'); | ||
makeRequest('OPTIONS', '/custom', function (res) { | ||
server.inject({ method: 'OPTIONS', url: '/custom' }, function (res) { | ||
@@ -127,3 +150,3 @@ expect(res.headers['access-control-allow-origin']).to.equal('*'); | ||
makeRequest('GET', '/tail', function (res) { | ||
server.inject('/tail', function (res) { | ||
@@ -136,3 +159,3 @@ result = res.result; | ||
makeRequest('GET', '/ext', function (res) { | ||
server.inject('/ext', function (res) { | ||
@@ -146,3 +169,3 @@ expect(res.result.code).to.equal(400); | ||
makeRequest('GET', '/unknown/reply', function (res) { | ||
server.inject('/unknown/reply', function (res) { | ||
@@ -157,3 +180,3 @@ expect(res.statusCode).to.equal(200); | ||
makeRequest('GET', '/unknown/close', function (res) { | ||
server.inject('/unknown/close', function (res) { | ||
@@ -168,3 +191,3 @@ expect(res.statusCode).to.equal(200); | ||
makeRequest('GET', '/response', function (res) { | ||
server.inject('/response', function (res) { | ||
@@ -176,2 +199,11 @@ expect(res.result).to.equal('success'); | ||
it('handles stream errors on the response after the response has been piped', function (done) { | ||
server.inject('/stream', function (res) { | ||
expect(res.result).to.equal('success'); | ||
done(); | ||
}); | ||
}); | ||
it('returns 500 on handler exception (same tick)', function (done) { | ||
@@ -188,3 +220,3 @@ | ||
server.inject({ method: 'GET', url: '/domain' }, function (res) { | ||
server.inject('/domain', function (res) { | ||
@@ -202,20 +234,49 @@ expect(res.statusCode).to.equal(500); | ||
setTimeout(function () { | ||
setImmediate(function () { | ||
var x = a.b.c; | ||
}, 1); | ||
var x = not.here; | ||
}); | ||
}; | ||
server.route({ method: 'GET', path: '/domain', handler: handler }); | ||
server.on('internalError', function (request, err) { | ||
expect(err.trace[0]).to.equal('ReferenceError: not is not defined'); | ||
done(); | ||
}); | ||
var orig = console.error; | ||
console.error = function (stack) { | ||
var prints = 0; | ||
console.error = function (msg) { | ||
expect(stack).to.contain('Cannot read property \'c\' of undefined'); | ||
console.error = orig; | ||
++prints; | ||
if (prints === 1) { | ||
expect(msg).to.equal('Unmonitored error: hapi, uncaught, handler, error'); | ||
} | ||
else { | ||
console.error = orig; | ||
} | ||
}; | ||
server.inject({ method: 'GET', url: '/domain' }, function (res) { | ||
server.inject('/domain', function (res) { | ||
expect(res.statusCode).to.equal(500); | ||
}); | ||
}); | ||
it('ignores second call to onReply()', function (done) { | ||
var server = new Hapi.Server(); | ||
var handler = function () { | ||
this.reply('123').hold().send(); | ||
}; | ||
server.route({ method: 'GET', path: '/domain', handler: handler }); | ||
server.inject('/domain', function (res) { | ||
expect(res.result).to.equal('123'); | ||
done(); | ||
@@ -225,3 +286,3 @@ }); | ||
it('ignores second call to reply()', function (done) { | ||
it('sends reply after handler timeout', function (done) { | ||
@@ -232,5 +293,7 @@ var server = new Hapi.Server(); | ||
var reply = this.reply; | ||
reply('123'); | ||
reply('456'); | ||
var response = this.reply('123').hold(); | ||
setTimeout(function () | ||
{ | ||
response.send(); | ||
}, 10); | ||
}; | ||
@@ -240,3 +303,3 @@ | ||
server.inject({ method: 'GET', url: '/domain' }, function (res) { | ||
server.inject('/domain', function (res) { | ||
@@ -248,3 +311,2 @@ expect(res.result).to.equal('123'); | ||
it('returns 500 on ext method exception (same tick)', function (done) { | ||
@@ -265,3 +327,3 @@ | ||
server.inject({ method: 'GET', url: '/domain' }, function (res) { | ||
server.inject('/domain', function (res) { | ||
@@ -282,3 +344,3 @@ expect(res.statusCode).to.equal(500); | ||
expect(reply.send).to.not.exist; | ||
expect(this.reply.send).to.exist; | ||
expect(this.reply.redirect).to.exist; | ||
reply('ok'); | ||
@@ -289,3 +351,3 @@ }; | ||
server.inject({ method: 'GET', url: '/' }, function (res) { | ||
server.inject('/', function (res) { | ||
@@ -311,3 +373,3 @@ expect(res.result).to.equal('ok'); | ||
Request(server.settings.uri + '/address', function (err, res, body) { | ||
Request(server.info.uri + '/address', function (err, res, body) { | ||
@@ -332,3 +394,3 @@ expect(body).to.equal('ok'); | ||
server.inject({ method: 'GET', url: '/', headers: { referrer: 'http://site.com' } }, function (res) { | ||
server.inject({ url: '/', headers: { referrer: 'http://site.com' } }, function (res) { | ||
@@ -335,0 +397,0 @@ expect(res.result).to.equal('ok'); |
@@ -29,3 +29,3 @@ // Load modules | ||
request.reply.payload('Moved').created(request.server.settings.uri + '/item/' + request.payload.name).send(); | ||
request.reply('Moved').created(request.server.info.uri + '/item/' + request.payload.name); | ||
}; | ||
@@ -133,3 +133,3 @@ | ||
request.reply.payload('Success').send(); | ||
request.reply('Success'); | ||
}; | ||
@@ -176,3 +176,3 @@ | ||
request.reply.payload('Success').setState('<script></script>', 'seomthing').send(); | ||
request.reply('Success').setState('<script></script>', 'seomthing'); | ||
}}); | ||
@@ -194,3 +194,3 @@ | ||
request.reply.payload('Success').setState('seomthing', '<script></script>').send(); | ||
request.reply('Success').setState('seomthing', '<script></script>'); | ||
}}); | ||
@@ -215,3 +215,3 @@ | ||
config: { | ||
validate: { path: { name: Hapi.Types.Function() } } | ||
validate: { path: { name: Hapi.types.Function() } } | ||
} | ||
@@ -237,3 +237,3 @@ }); | ||
config: { | ||
validate: { payload: { name: Hapi.Types.String().max(4).min(1).required() } } | ||
validate: { payload: { name: Hapi.types.String().max(4).min(1).required() } } | ||
} | ||
@@ -261,3 +261,3 @@ }); | ||
config: { | ||
validate: { query: { name: Hapi.Types.String().alphanum().required() } } | ||
validate: { query: { name: Hapi.types.String().alphanum().required() } } | ||
} | ||
@@ -264,0 +264,0 @@ }); |
@@ -32,16 +32,60 @@ // Load modules | ||
socket1.connect(server.settings.port, server.settings.host, function () { | ||
socket1.connect(server.info.port, server.settings.host, function () { | ||
socket2.connect(server.settings.port, server.settings.host, function () { | ||
socket2.connect(server.info.port, server.settings.host, function () { | ||
expect(server.listener.connections).to.be.greaterThan(0); | ||
server.listener.getConnections(function (err, count) { | ||
server.stop(function () { | ||
expect(count).to.be.greaterThan(0); | ||
expect(server.listener.connections).to.equal(0); | ||
done(); | ||
server.stop(function () { | ||
server.listener.getConnections(function (err, count) { | ||
expect(count).to.equal(0); | ||
done(); | ||
}); | ||
}); | ||
socket1.end(); | ||
socket2.end(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
socket1.end(); | ||
socket2.end(); | ||
it('won\'t destroy connections until after the timeout', function (done) { | ||
var server = Hapi.createServer(0); | ||
server.start(function () { | ||
var socket1 = new Net.Socket(); | ||
var socket2 = new Net.Socket(); | ||
socket1.once('error', function (err) { | ||
expect(err.errno).to.equal('ECONNRESET'); | ||
}); | ||
socket2.once('error', function (err) { | ||
expect(err.errno).to.equal('ECONNRESET'); | ||
}); | ||
socket1.connect(server.info.port, server.settings.host, function () { | ||
socket2.connect(server.info.port, server.settings.host, function () { | ||
server.listener.getConnections(function (err, count) { | ||
expect(count).to.be.greaterThan(0); | ||
var timer = new Hapi.utils.Timer(); | ||
server.stop({ timeout: 20 }, function () { | ||
expect(timer.elapsed()).to.be.at.least(19); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -51,2 +95,50 @@ }); | ||
}); | ||
it('won\'t destroy connections if they close by themselves', function (done) { | ||
var server = Hapi.createServer(0); | ||
server.start(function () { | ||
var socket1 = new Net.Socket(); | ||
var socket2 = new Net.Socket(); | ||
socket1.once('error', function (err) { | ||
expect(err.errno).to.equal('ECONNRESET'); | ||
}); | ||
socket2.once('error', function (err) { | ||
expect(err.errno).to.equal('ECONNRESET'); | ||
}); | ||
socket1.connect(server.info.port, server.settings.host, function () { | ||
socket2.connect(server.info.port, server.settings.host, function () { | ||
server.listener.getConnections(function (err, count) { | ||
expect(count).to.be.greaterThan(0); | ||
var timer = new Hapi.utils.Timer(); | ||
server.stop(function () { | ||
server.listener.getConnections(function (err, count) { | ||
expect(count).to.equal(0); | ||
expect(timer.elapsed()).to.be.at.least(10); | ||
done(); | ||
}); | ||
}); | ||
setTimeout(function () { | ||
socket1.end(); | ||
socket2.end(); | ||
}, 10); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -35,3 +35,3 @@ // Load modules | ||
reply.bind(request, new Hapi.Response.Text('Cached')); | ||
reply.bind(request, new Hapi.response.Text('Cached')); | ||
}, 70); | ||
@@ -48,22 +48,15 @@ }; | ||
var exactHandler = function (request) { | ||
setTimeout(function () { | ||
request.reply('Exact'); | ||
}, 50); | ||
}; | ||
var respondingHandler = function (request) { | ||
var s = new Stream(); | ||
s.readable = true; | ||
s.resume = function () { }; | ||
request.reply.stream(s).send(); | ||
var s = new Stream.PassThrough(); | ||
request.reply(s); | ||
for (var i = 10000; i > 0; --i) { | ||
s.emit('data', i.toString()); | ||
s.write(i.toString()); | ||
} | ||
s.emit('end'); | ||
setTimeout(function () { | ||
s.emit('end'); | ||
}, 40); | ||
}; | ||
@@ -78,23 +71,34 @@ | ||
var s = new Stream(); | ||
s.readable = true; | ||
var TestStream = function () { | ||
s.resume = function () { | ||
Stream.Readable.call(this); | ||
}; | ||
Hapi.utils.inherits(TestStream, Stream.Readable); | ||
TestStream.prototype._read = function (size) { | ||
var self = this; | ||
if (this.isDone) { | ||
return; | ||
} | ||
this.isDone = true; | ||
setTimeout(function () { | ||
s.emit('data', 'Hello'); | ||
}, 60); | ||
self.push('Hello'); | ||
}, 30); | ||
setTimeout(function () { | ||
s.emit('end'); | ||
}, 70); | ||
self.push(null); | ||
}, 60); | ||
}; | ||
request.reply.stream(s).send(); | ||
request.reply(new TestStream()); | ||
}; | ||
var _server = new Hapi.Server('127.0.0.1', 0, { timeout: { server: 50 } }); | ||
_server.route([ | ||
var server = new Hapi.Server('127.0.0.1', 0, { timeout: { server: 50 } }); | ||
server.route([ | ||
{ method: 'GET', path: '/timeout', config: { handler: timeoutHandler } }, | ||
@@ -104,3 +108,2 @@ { method: 'GET', path: '/timeoutcache', config: { handler: cachedTimeoutHandler } }, | ||
{ method: 'GET', path: '/fast', config: { handler: fastHandler } }, | ||
{ method: 'GET', path: '/exact', config: { handler: exactHandler } }, | ||
{ method: 'GET', path: '/stream', config: { handler: streamHandler } }, | ||
@@ -110,8 +113,5 @@ { method: 'GET', path: '/responding', config: { handler: respondingHandler } } | ||
var _shortTimeoutServer = new Hapi.Server('127.0.0.1', 0, { timeout: { server: 1 } }); | ||
_shortTimeoutServer.route({ method: 'GET', path: '/timeout', config: { handler: timeoutHandler } }); | ||
before(function (done) { | ||
_server.start(done); | ||
server.start(done); | ||
}); | ||
@@ -123,3 +123,3 @@ | ||
_server.inject({ method: 'GET', url: '/timeout' }, function (res) { | ||
server.inject('/timeout', function (res) { | ||
@@ -132,10 +132,10 @@ expect(res.statusCode).to.equal(503); | ||
it('returns server error message when response takes as long as server timeout', function (done) { | ||
it('returns server error message when server timeout happens during request execution (and handler yields)', function (done) { | ||
var timer = new Hapi.utils.Timer(); | ||
var serverShort = new Hapi.Server({ timeout: { server: 2 } }); | ||
serverShort.route({ method: 'GET', path: '/', config: { handler: slowHandler } }); | ||
_server.inject({ method: 'GET', url: '/exact' }, function (res) { | ||
serverShort.inject('/', function (res) { | ||
expect(res.statusCode).to.equal(503); | ||
expect(timer.elapsed()).to.be.at.least(49); | ||
done(); | ||
@@ -147,9 +147,10 @@ }); | ||
_shortTimeoutServer.ext('onRequest', function (request, next) { | ||
var timer = new Hapi.utils.Timer(); | ||
while (timer.elapsed() < 3); | ||
next(); | ||
var serverExt = new Hapi.Server({ timeout: { server: 2 } }); | ||
serverExt.route({ method: 'GET', path: '/', config: { handler: function () { } } }); | ||
serverExt.ext('onRequest', function (request, next) { | ||
setTimeout(next, 10); | ||
}); | ||
_shortTimeoutServer.inject({ method: 'GET', url: '/timeout' }, function (res) { | ||
serverExt.inject('/', function (res) { | ||
@@ -164,3 +165,3 @@ expect(res.statusCode).to.equal(503); | ||
var timer = new Hapi.utils.Timer(); | ||
_server.inject({ method: 'GET', url: '/slow' }, function (res) { | ||
server.inject('/slow', function (res) { | ||
@@ -179,3 +180,3 @@ expect(timer.elapsed()).to.be.at.least(29); | ||
hostname: '127.0.0.1', | ||
port: _server.settings.port, | ||
port: server.info.port, | ||
path: '/responding', | ||
@@ -187,3 +188,3 @@ method: 'GET' | ||
expect(timer.elapsed()).to.be.at.least(91); | ||
expect(timer.elapsed()).to.be.at.least(70); | ||
expect(res.statusCode).to.equal(200); | ||
@@ -200,3 +201,3 @@ done(); | ||
hostname: '127.0.0.1', | ||
port: _server.settings.port, | ||
port: server.info.port, | ||
path: '/stream', | ||
@@ -206,6 +207,4 @@ method: 'GET' | ||
var timer = new Hapi.utils.Timer(); | ||
var req = Http.request(options, function (res) { | ||
expect(timer.elapsed()).to.be.at.least(59); | ||
expect(res.statusCode).to.equal(200); | ||
@@ -219,3 +218,3 @@ done(); | ||
_server.inject({ method: 'GET', url: '/fast' }, function (res) { | ||
server.inject('/fast', function (res) { | ||
@@ -230,3 +229,3 @@ expect(res.statusCode).to.equal(200); | ||
var timer = new Hapi.utils.Timer(); | ||
_server.inject({ method: 'GET', url: '/timeoutcache' }, function (res1) { | ||
server.inject('/timeoutcache', function (res1) { | ||
@@ -236,3 +235,3 @@ expect(timer.elapsed()).to.be.at.least(49); | ||
_server.inject({ method: 'GET', url: '/timeoutcache' }, function (res2) { | ||
server.inject('/timeoutcache', function (res2) { | ||
@@ -258,8 +257,8 @@ expect(timer.elapsed()).to.be.at.least(98); | ||
reply.bind(request, new Hapi.Response.Text('Cached')); | ||
reply.bind(request, new Hapi.response.Text('Cached')); | ||
}, 70); | ||
}; | ||
var _server = new Hapi.Server('127.0.0.1', 0, { timeout: { server: 50, client: 50 } }); | ||
_server.route([ | ||
var server = new Hapi.Server('127.0.0.1', 0, { timeout: { server: 50, client: 50 } }); | ||
server.route([ | ||
{ method: 'POST', path: '/timeout', config: { handler: timeoutHandler } }, | ||
@@ -271,3 +270,3 @@ { method: 'POST', path: '/timeoutcache', config: { handler: cachedTimeoutHandler } } | ||
_server.start(done); | ||
server.start(done); | ||
}); | ||
@@ -280,3 +279,3 @@ | ||
hostname: '127.0.0.1', | ||
port: _server.settings.port, | ||
port: server.info.port, | ||
path: '/timeout', | ||
@@ -309,3 +308,3 @@ method: 'POST' | ||
hostname: '127.0.0.1', | ||
port: _server.settings.port, | ||
port: server.info.port, | ||
path: '/timeoutcache', | ||
@@ -365,3 +364,3 @@ method: 'POST' | ||
port = server.settings.port; | ||
port = server.info.port; | ||
done(); | ||
@@ -368,0 +367,0 @@ }); |
// Load modules | ||
var Lab = require('lab'); | ||
var Oz = require('oz'); | ||
var Hapi = require('../../..'); | ||
@@ -62,24 +61,2 @@ var Auth = require('../../../lib/auth'); | ||
it('doesn\'t throw an error when constructed with all required parameters', function (done) { | ||
var fn = function () { | ||
var server = { | ||
settings: {}, | ||
route: function () { } | ||
}; | ||
var auth = new Auth(server); | ||
auth.addBatch({ | ||
scheme: 'oz', | ||
encryptionPassword: 'test', | ||
loadAppFunc: function () { }, | ||
loadGrantFunc: function () { } | ||
}); | ||
}; | ||
expect(fn).to.not.throw(Error); | ||
done(); | ||
}); | ||
it('doesn\'t throws an error if no strategies are defined', function (done) { | ||
@@ -107,3 +84,3 @@ | ||
scheme: 'basic', | ||
loadUserFunc: function () { } | ||
validateFunc: function () { } | ||
} | ||
@@ -156,5 +133,5 @@ }; | ||
scheme: 'basic', | ||
loadUserFunc: function (username, callback) { | ||
validateFunc: function (username, password, callback) { | ||
return callback(null, { user: 'steve' }, 'password'); | ||
return callback(null, password === 'password', { user: 'steve' }); | ||
} | ||
@@ -191,5 +168,5 @@ } | ||
scheme: 'basic', | ||
loadUserFunc: function (username, callback) { | ||
validateFunc: function (username, password, callback) { | ||
return callback(null, { id: 'steve', password: 'password' }); | ||
return callback(null, password === 'password', { id: 'steve', password: 'password' }); | ||
} | ||
@@ -484,14 +461,5 @@ } | ||
var oz = { | ||
scheme: 'oz', | ||
encryptionPassword: 'test', | ||
loadAppFunc: function () { }, | ||
loadGrantFunc: function () { } | ||
}; | ||
test(oz); | ||
var basic = { | ||
scheme: 'basic', | ||
loadUserFunc: function () { } | ||
validateFunc: function () { } | ||
}; | ||
@@ -498,0 +466,0 @@ |
@@ -47,3 +47,3 @@ // Load modules | ||
req: { | ||
resume: function () { } | ||
read: function () { } | ||
} | ||
@@ -67,3 +67,3 @@ } | ||
req: { | ||
resume: function () { } | ||
read: function () { } | ||
} | ||
@@ -70,0 +70,0 @@ } |
@@ -25,3 +25,3 @@ // Load modules | ||
var server = new Hapi.server(); | ||
var server = new Hapi.Server(); | ||
@@ -150,2 +150,23 @@ var _req = null; | ||
}); | ||
it('doesn\'t throw an error when logging without data and debug is configured', function (done) { | ||
var debugServer = new Hapi.Server({ debug: { request: ['uncaught'] } }); | ||
var request = new Request(debugServer, _req, _res); | ||
var fn = function () { | ||
request.log('uncaught', null, Date.now()); | ||
}; | ||
var orig = console.error; | ||
console.error = function (msg) { | ||
expect(msg).to.equal('Unmonitored error: uncaught'); | ||
console.error = orig; | ||
}; | ||
expect(fn).to.not.throw(Error); | ||
done(); | ||
}); | ||
}); | ||
@@ -173,3 +194,3 @@ | ||
var normServer = new Hapi.server({ router: { normalizeRequestPath: true } }); | ||
var normServer = new Hapi.Server({ router: { normalizeRequestPath: true } }); | ||
@@ -176,0 +197,0 @@ var request = new Request(normServer, _req, _res); |
@@ -5,2 +5,3 @@ // Load modules | ||
var Hapi = require('../../..'); | ||
var Directory = require('../../../lib/response/directory'); | ||
@@ -30,3 +31,3 @@ | ||
var dir = new Hapi.response.Directory(['no_such_path'], {}); | ||
var dir = new Directory(['no_such_path'], {}); | ||
dir._generateListing('no_such_path', null, function (response) { | ||
@@ -33,0 +34,0 @@ |
@@ -29,6 +29,7 @@ // Load modules | ||
var manager = new Views({ | ||
engines: { 'html': 'handlebars' }, | ||
path: __dirname + '/../templates/invalid' | ||
}); | ||
var view = new Hapi.response.View(manager, 'test', { message: "Ohai" }); | ||
var view = new Hapi.response.View(manager, 'test', { message: 'Ohai' }); | ||
view._prepare({}, function (response) { | ||
@@ -35,0 +36,0 @@ |
@@ -35,7 +35,4 @@ // Load modules | ||
Schema.server(settings, function (err) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
expect(Schema.server(settings)).to.exist; | ||
done(); | ||
}); | ||
@@ -48,7 +45,4 @@ | ||
Schema.server(server.settings, function (err) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
expect(Schema.server(server.settings)).to.exist; | ||
done(); | ||
}); | ||
@@ -61,7 +55,4 @@ | ||
Schema.server(server.settings, function (err) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
expect(Schema.server(server.settings)).to.exist; | ||
done(); | ||
}); | ||
@@ -73,7 +64,4 @@ | ||
Schema.server(server.settings, function (err) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
expect(Schema.server(server.settings)).to.not.exist; | ||
done(); | ||
}); | ||
@@ -93,13 +81,9 @@ | ||
describe('#route', function () { | ||
describe('#routeOptions', function () { | ||
it('fails when unknown properties exist', function (done) { | ||
var settings = { method: 'GET', path: '/', handler: function() {}, unknown: true }; | ||
Schema.route(settings, {}, function (err) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
var options = { method: 'GET', path: '/', handler: function() {}, unknown: true }; | ||
expect(Schema.routeOptions(options)).to.exist; | ||
done(); | ||
}); | ||
@@ -109,32 +93,12 @@ | ||
var settings = { method: 'GET' }; | ||
Schema.route(settings, null, function (err) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
var options = { method: 'GET' }; | ||
expect(Schema.routeOptions(options)).to.exist; | ||
done(); | ||
}); | ||
it('fails when config payload has invalid value', function (done) { | ||
var settings = { method: 'GET', path: '/', handler: function () { } }; | ||
var config = { payload: 'something' }; | ||
Schema.route(settings, config, function (err) { | ||
expect(err).to.exist; | ||
done(); | ||
}); | ||
}); | ||
it('succeeds when required fields are present', function (done) { | ||
var settings = { method: 'GET', path: '/', handler: function() {} }; | ||
Schema.route(settings, {}, function (err) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
var options = { method: 'GET', path: '/', handler: function () { } }; | ||
expect(Schema.routeOptions(options)).to.not.exist; | ||
done(); | ||
}); | ||
@@ -144,33 +108,31 @@ | ||
var settings = { method: 'GET', path: '/', handler: function() {}, config: { description: 'here is my description' } }; | ||
var options = { method: 'GET', path: '/', handler: function () { }, config: { description: 'here is my description' } }; | ||
Schema.route(settings, {}, function (err) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
expect(Schema.routeOptions(options)).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
it('succeeds when route config has tags', function (done) { | ||
describe('#routeConfig', function () { | ||
var settings = { method: 'GET', path: '/', handler: function() {}, config: { tags: ['tag1', 'tag2'] } }; | ||
it('fails when config payload has invalid value', function (done) { | ||
Schema.route(settings, {}, function (err) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
var config = { payload: 'something' }; | ||
expect(Schema.routeConfig(config)).to.exist; | ||
done(); | ||
}); | ||
it('succeeds when route config has notes', function (done) { | ||
it('succeeds when route config has a description', function (done) { | ||
var settings = { method: 'GET', path: '/', handler: function() {}, config: { notes: 'here is a note' } }; | ||
var config = { description: 'here is my description' }; | ||
expect(Schema.routeConfig(config)).to.not.exist; | ||
done(); | ||
}); | ||
Schema.route(settings, {}, function (err) { | ||
expect(err).to.not.exist; | ||
done(); | ||
}); | ||
it('succeeds validating cache config', function (done) { | ||
var config = { handler: internals.item, cache: { expiresIn: 20000, staleIn: 10000, staleTimeout: 500 } }; | ||
expect(Schema.routeConfig(config)).to.not.exist; | ||
done(); | ||
}); | ||
}); | ||
}); |
@@ -24,2 +24,7 @@ // Load modules | ||
var tlsOptions = { | ||
key: '-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBANysie374iGH54SVcmM4vb+CjN4nVVCmL6af9XOUxTqq/50CBn+Z\nZol0XDG+OK55HTOht4CsQrAXey69ZTxgUMcCAwEAAQJAX5t5XtxkiraA/hZpqsdo\nnlKHibBs7DY0KvLeuybXlKS3ar/0Uz0OSJ1oLx3d0KDSmcdAIrfnyFuBNuBzb3/J\nEQIhAPX/dh9azhztRppR+9j8CxDg4ixJ4iZbHdK0pfnY9oIFAiEA5aV8edK31dkF\nfBXoqlOvIeuNc6WBZrYjUNspH8M+BVsCIQDZF3U6/nve81bXYXqMZwGtB4kR5LH7\nf3W2OU4wS9RfsQIhAJkNB76xX3AYqX0fpOcPyuLSeH2gynNH5JWY2vmeSBGNAiAm\nLon4E3M/IrVVvpxGRFOazKlgIsQFGAaoylDrRFYgBA==\n-----END RSA PRIVATE KEY-----\n', | ||
cert: '-----BEGIN CERTIFICATE-----\nMIIB0TCCAXugAwIBAgIJANGtTMK5HBUIMA0GCSqGSIb3DQEBBQUAMEQxCzAJBgNV\nBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UECgwJaGFwaSB0ZXN0MRQwEgYDVQQD\nDAtleGFtcGxlLmNvbTAeFw0xMzA0MDQxNDQ4MDJaFw0yMzA0MDIxNDQ4MDJaMEQx\nCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UECgwJaGFwaSB0ZXN0MRQw\nEgYDVQQDDAtleGFtcGxlLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDcrInt\n++Ihh+eElXJjOL2/gozeJ1VQpi+mn/VzlMU6qv+dAgZ/mWaJdFwxvjiueR0zobeA\nrEKwF3suvWU8YFDHAgMBAAGjUDBOMB0GA1UdDgQWBBQBOiF6iL2PI4E6PBj071Dh\nAiQOGjAfBgNVHSMEGDAWgBQBOiF6iL2PI4E6PBj071DhAiQOGjAMBgNVHRMEBTAD\nAQH/MA0GCSqGSIb3DQEBBQUAA0EAw8Y2rpM8SUQXjgaJJmFXrfEvnl/he7q83K9W\n9Sr/QLHpCFxunWVd8c0wz+b8P/F9uW2V4wUf5NWj1UdHMCd6wQ==\n-----END CERTIFICATE-----\n' | ||
}; | ||
it('throws an error constructed without new', function (done) { | ||
@@ -38,3 +43,3 @@ | ||
var server = new Hapi.Server(); | ||
expect(server.settings.port).to.be.equal(80); | ||
expect(server.info.port).to.be.equal(80); | ||
done(); | ||
@@ -45,4 +50,4 @@ }); | ||
var server = new Hapi.Server({ tls: {} }); | ||
expect(server.settings.port).to.be.equal(443); | ||
var server = new Hapi.Server({ tls: tlsOptions }); | ||
expect(server.info.port).to.be.equal(443); | ||
done(); | ||
@@ -54,3 +59,3 @@ }); | ||
var server = new Hapi.Server('0.0.0.0', null); | ||
expect(server.settings.port).to.be.equal(80); | ||
expect(server.info.port).to.be.equal(80); | ||
done(); | ||
@@ -62,3 +67,3 @@ }); | ||
var server = new Hapi.Server('0.0.0.0', 0); | ||
expect(server.settings.port).to.be.equal(0); | ||
expect(server.info.port).to.be.equal(0); | ||
done(); | ||
@@ -72,3 +77,3 @@ }); | ||
expect(server.settings.host).to.be.equal('0.0.0.0'); | ||
expect(server.info.host).to.equal('0.0.0.0'); | ||
done(); | ||
@@ -131,3 +136,7 @@ }); | ||
var server = new Hapi.Server('0.0.0.0', 0, { views: { partials: { path: __dirname + '/templates' } } }); | ||
var server = new Hapi.Server('0.0.0.0', 0, { | ||
views: { | ||
engines: { 'html': 'handlebars' } | ||
} | ||
}); | ||
expect(server._views).to.exist; | ||
@@ -139,5 +148,3 @@ done(); | ||
var tls = {}; | ||
var server = new Hapi.Server('0.0.0.0', 0, { tls: tls }); | ||
var server = new Hapi.Server('0.0.0.0', 0, { tls: tlsOptions }); | ||
expect(server.listener instanceof Https.Server).to.equal(true); | ||
@@ -151,3 +158,3 @@ done(); | ||
var server = new Hapi.Server('0.0.0.0', 0, { auth: { scheme: 'basic', loadUserFunc: function () { } } }); | ||
var server = new Hapi.Server('0.0.0.0', 0, { auth: { scheme: 'basic', validateFunc: function () { } } }); | ||
}; | ||
@@ -176,4 +183,4 @@ expect(fn).to.not.throw(Error); | ||
expect(server.settings.host).to.equal('0.0.0.0'); | ||
expect(server.settings.port).to.not.equal(0); | ||
expect(server.info.host).to.equal('0.0.0.0'); | ||
expect(server.info.port).to.not.equal(0); | ||
done(); | ||
@@ -185,7 +192,7 @@ }); | ||
var server = new Hapi.Server('0.0.0.0', 0, { tls: {} }); | ||
var server = new Hapi.Server('0.0.0.0', 0, { tls: tlsOptions }); | ||
server.start(function () { | ||
expect(server.settings.host).to.equal('0.0.0.0'); | ||
expect(server.settings.port).to.not.equal(0); | ||
expect(server.info.host).to.equal('0.0.0.0'); | ||
expect(server.info.port).to.not.equal(0); | ||
done(); | ||
@@ -405,3 +412,3 @@ }); | ||
var server = new Hapi.Server('0.0.0.0', 0, { cache: 'redis' }); | ||
var server = new Hapi.Server('0.0.0.0', 0, { cache: 'memory' }); | ||
server.helper('user', function () { }, { cache: { mode: 'server' } }); | ||
@@ -408,0 +415,0 @@ }; |
@@ -109,7 +109,11 @@ // Load modules | ||
var fail = function (header, settings, definitions) { | ||
var loose = Hapi.utils.clone(Defaults.server.state); | ||
loose.cookies.strictHeader = false; | ||
pass('a="1; b="2"; c=3', { a: '"1', b: '2', c:'3' }, loose); | ||
var fail = function (header, settings, definitions, result) { | ||
it('fails parsing cookie header: ' + header, function (done) { | ||
var ignore = false; | ||
var logged = false; | ||
var cleared = ''; | ||
@@ -132,3 +136,4 @@ | ||
_log: function (tags, data) { | ||
ignore = true; | ||
logged = true; | ||
}, | ||
@@ -143,7 +148,9 @@ clearState: function (name) { | ||
if (ignore) { | ||
if (request.server.settings.state.cookies.failAction !== 'error') { | ||
expect(err).to.not.exist; | ||
expect(settings.failAction !== 'ignore' || logged).to.equal(true); | ||
} | ||
else { | ||
expect(err).to.exist; | ||
expect(logged).to.equal(true); | ||
} | ||
@@ -155,2 +162,3 @@ | ||
expect(request.state).to.deep.equal(result || {}); | ||
done(); | ||
@@ -165,3 +173,3 @@ }); | ||
fail('key=XeyJ0ZXN0aW5nIjoianNvbiJ9', null, { key: { encoding: 'base64json' } }); | ||
fail('key=XeyJ0ZXN0aW5nIjoianNvbiJ9; key=XeyJ0ZXN0aW5dnIjoianNvbiJ9', null, { key: { encoding: 'base64json' } }); | ||
fail('x=XeyJ0ZXN0aW5nIjoianNvbiJ9; x=XeyJ0ZXN0aW5dnIjoianNvbiJ9', null, { x: { encoding: 'base64json' } }); | ||
fail('key=Fe26.2**0c68b200eb53406edefb402bb84e265d056bd11eb6bdebf3627a4fd7d1db6d79*XL-d8bUCuSwIwIYtmjXZ3g*4yVxLHFllbJWLSve93se4w*34771b9abd1cadeb0118e2a337c066f32cb44c223e3610533fc56ef3bbc53d56*HEfokc825mlBi-9jm1I94DWeZJ5uifVRD0kx_o-jKp8', null, { key: { encoding: 'iron', password: 'password' } }); | ||
@@ -180,5 +188,6 @@ fail('key=Fe26.1**0c68b200eb53406edefb402bb84e265d056bd11eb6bdebf3627a4fd7d1db6d79*XL-d8bUCuSwIwIYtmjXZ3g*4yVxLHFllbJWLSve93se4w*34771b9abd1cadeb0118e2a337c066f32cb44c223e3610533fc56ef3bbc53d56*HEfokc825mlBi-9jm1I94DWeZJ5uifVRD0kx_o-jKp8', null, { key: { encoding: 'iron', password: 'passwordx' } }); | ||
fail('key=XeyJ0ZXN0aW5nIjoianNvbiJ9', setLog, { key: { encoding: 'base64json' } }); | ||
fail('key=XeyJ0ZXN0aW5nIjoianNvbiJ9; key=XeyJ0ZXN0aW5dnIjoianNvbiJ9', setLog, { key: { encoding: 'base64json' } }); | ||
fail('y=XeyJ0ZXN0aW5nIjoianNvbiJ9; y=XeyJ0ZXN0aW5dnIjoianNvbiJ9', setLog, { y: { encoding: 'base64json' } }); | ||
fail('sid=a=1&b=2&c=3%20x', setLog, { sid: { encoding: 'form', sign: { password: 'password' } } }); | ||
fail('sid=a=1&b=2&c=3%20x; sid=a=1&b=2&c=3%20x', setLog, { sid: { encoding: 'form', sign: { password: 'password' } } }); | ||
fail('a=1; b=2; key=XeyJ0ZXN0aW5nIjoianNvbiJ9', setLog, { key: { encoding: 'base64json' } }, { a: '1', b: '2' }); | ||
@@ -185,0 +194,0 @@ var clearInvalid = Hapi.utils.clone(Defaults.server.state); |
@@ -28,3 +28,3 @@ // Load modules | ||
expect(Hapi.Utils.version()).to.equal(Package.version); | ||
expect(Hapi.utils.version()).to.equal(Package.version); | ||
done(); | ||
@@ -31,0 +31,0 @@ }); |
@@ -52,5 +52,3 @@ // Load modules | ||
method: routeClone.method, | ||
_timestamp: Date.now(), | ||
route: routeClone.config, | ||
response: { result: {} } | ||
route: routeClone.config | ||
}; | ||
@@ -71,5 +69,3 @@ }; | ||
method: routeClone.method, | ||
_timestamp: Date.now(), | ||
route: routeClone.config, | ||
response: { result: {} } | ||
route: routeClone.config | ||
}; | ||
@@ -80,3 +76,3 @@ }; | ||
var route = { method: 'GET', path: '/', config: { handler: testHandler, response: { schema: { username: S().required() } } } }; | ||
var route = { method: 'GET', path: '/', config: { handler: testHandler, validate: { response: { schema: { username: S().required() } } } } }; | ||
@@ -88,3 +84,3 @@ it('should not raise an error when responding with valid param', function (done) { | ||
request._response = Hapi.Response.generate({ username: 'test' }); | ||
request._response = Hapi.response._generate({ username: 'test' }); | ||
@@ -103,3 +99,3 @@ Validation.response(request, function (err) { | ||
request._response = Hapi.Response.generate(Hapi.error.unauthorized('You are not authorized')); | ||
request._response = Hapi.response._generate(Hapi.error.unauthorized('You are not authorized')); | ||
@@ -117,3 +113,3 @@ Validation.response(request, function (err) { | ||
var request = createRequestObject(query, route); | ||
request._response = Hapi.Response.generate({ wrongParam: 'test' }); | ||
request._response = Hapi.response._generate({ wrongParam: 'test' }); | ||
@@ -131,4 +127,4 @@ Validation.response(request, function (err) { | ||
var request = createRequestObject(query, route); | ||
request.route.response.sample = 0; | ||
request._response = Hapi.Response.generate({ wrongParam: 'test' }); | ||
request.route.validate.response.sample = 0; | ||
request._response = Hapi.response._generate({ wrongParam: 'test' }); | ||
@@ -146,4 +142,4 @@ Validation.response(request, function (err) { | ||
var request = createRequestObject(query, route); | ||
request.route.response.sample = 100; | ||
request._response = Hapi.Response.generate({ wrongParam: 'test' }); | ||
request.route.validate.response.sample = 100; | ||
request._response = Hapi.response._generate({ wrongParam: 'test' }); | ||
@@ -161,5 +157,5 @@ Validation.response(request, function (err) { | ||
var request = createRequestObject(query, route); | ||
request.route.response.failAction = 'log'; | ||
request.route.response.sample = sample; | ||
request._response = Hapi.Response.generate({ wrongParam: 'test' }); | ||
request.route.validate.response.failAction = 'log'; | ||
request.route.validate.response.sample = sample; | ||
request._response = Hapi.response._generate({ wrongParam: 'test' }); | ||
var failureCount = 0; | ||
@@ -195,3 +191,3 @@ | ||
expect(rates[0]).to.be.greaterThan(8); // accept a 15 point margin | ||
expect(rates[49]).to.be.lessThan(44); | ||
expect(rates[49]).to.be.lessThan(45); | ||
@@ -205,4 +201,4 @@ done(); | ||
var request = createRequestObject(query, route); | ||
request.route.response.failAction = 'log'; | ||
request._response = Hapi.Response.generate({ wrongParam: 'test' }); | ||
request.route.validate.response.failAction = 'log'; | ||
request._response = Hapi.response._generate({ wrongParam: 'test' }); | ||
@@ -225,3 +221,3 @@ request._log = function (tags, data) { | ||
var request = createRequestObject(query, route); | ||
request._response = Hapi.Response.generate('test'); | ||
request._response = Hapi.response._generate('test'); | ||
@@ -237,3 +233,3 @@ Validation.response(request, function (err) { | ||
var route = { method: 'GET', path: '/', config: { handler: testHandler, response: true } }; | ||
var route = { method: 'GET', path: '/', config: { handler: testHandler, validate: { response: true } } }; | ||
@@ -240,0 +236,0 @@ var query = null; |
@@ -29,2 +29,3 @@ // Load modules | ||
var testView = new Views({ | ||
engines: { 'html': 'handlebars' }, | ||
path: viewsPath, | ||
@@ -35,2 +36,3 @@ layout: false | ||
var testViewWithLayouts = new Views({ | ||
engines: { 'html': 'handlebars' }, | ||
path: viewsPath, | ||
@@ -43,3 +45,3 @@ layout: true | ||
var fn = (function () { | ||
var html = testView.render('valid/test', { title: 'test', message: 'Hapi' }); | ||
var html = testView.render('valid/test', { title: 'test', message: 'Hapi' }).result; | ||
expect(html).to.exist; | ||
@@ -56,3 +58,3 @@ expect(html.length).above(1); | ||
var fn = (function () { | ||
var html = testViewWithLayouts.render('valid/test', { title: 'test', message: 'Hapi' }); | ||
var html = testViewWithLayouts.render('valid/test', { title: 'test', message: 'Hapi' }).result; | ||
expect(html).to.exist; | ||
@@ -66,6 +68,19 @@ expect(html.length).above(1); | ||
it('should work and not throw with basePath, template name, and no path', function (done) { | ||
var fn = (function () { | ||
var views = new Views({ engines: { 'html': 'handlebars' } }); | ||
var html = views.render('test', { title: 'test', message: 'Hapi' }, { basePath: viewsPath + '/valid' }).result; | ||
expect(html).to.exist; | ||
expect(html.length).above(1); | ||
}); | ||
expect(fn).to.not.throw(); | ||
done(); | ||
}); | ||
it('should throw when referencing non existant partial (with layouts)', function (done) { | ||
var fn = (function () { | ||
var html = testViewWithLayouts.render('invalid/test', { title: 'test', message: 'Hapi' }); | ||
var html = testViewWithLayouts.render('invalid/test', { title: 'test', message: 'Hapi' }).result; | ||
expect(html).to.exist; | ||
@@ -82,3 +97,3 @@ expect(html.length).above(1); | ||
var fn = (function () { | ||
var html = testView.render('invalid/test', { title: 'test', message: 'Hapi' }); | ||
var html = testView.render('invalid/test', { title: 'test', message: 'Hapi' }).result; | ||
expect(html).to.exist; | ||
@@ -97,3 +112,3 @@ expect(html.length).above(1); | ||
opts[testView.options.layoutKeyword] = 1; | ||
var html = testViewWithLayouts.render('valid/test', opts); | ||
testViewWithLayouts.render('valid/test', opts); | ||
}); | ||
@@ -114,17 +129,10 @@ | ||
var fn = (function () { | ||
var tempView = new Views({ | ||
path: viewsPath + '/valid', | ||
partials: { | ||
path: viewsPath + '/valid/partials' | ||
} | ||
}); | ||
var html = tempView.render('testPartials', {}); | ||
expect(html).to.exist; | ||
expect(html.length).above(1); | ||
var tempView = new Views({ | ||
engines: { 'html': 'handlebars' }, | ||
path: viewsPath + '/valid', | ||
partialsPath: viewsPath + '/valid/partials' | ||
}); | ||
expect(fn).to.not.throw(); | ||
var html = tempView.render('testPartials', {}).result; | ||
expect(html).to.equal('Nav:<nav>Nav</nav>|<nav>Nested</nav>'); | ||
done(); | ||
@@ -138,9 +146,8 @@ }); | ||
var tempView = new Views({ | ||
engines: { 'html': 'handlebars' }, | ||
path: viewsPath + '/valid', | ||
partials: { | ||
path: viewsPath + '/valid/partials/' | ||
} | ||
partialsPath: viewsPath + '/valid/partials/' | ||
}); | ||
var html = tempView.render('testPartials', {}); | ||
var html = tempView.render('testPartials', {}).result; | ||
expect(html).to.exist; | ||
@@ -160,11 +167,7 @@ expect(html.length).above(1); | ||
path: viewsPath + '/valid', | ||
partials: { | ||
path: viewsPath + '/valid/partials' | ||
}, | ||
engines: { | ||
'html': { module: 'jade' } | ||
} | ||
partialsPath: viewsPath + '/valid/partials', | ||
engines: { 'html': 'jade' } | ||
}); | ||
var html = tempView.render('testPartials', {}); | ||
var html = tempView.render('testPartials', {}).result; | ||
expect(html).to.exist; | ||
@@ -185,2 +188,3 @@ expect(html.length).above(1); | ||
views: { | ||
engines: { 'html': 'handlebars' }, | ||
path: viewsPath | ||
@@ -191,2 +195,3 @@ } | ||
var server = new Hapi.Server(options); | ||
server.route({ method: 'GET', path: '/{param}', handler: { view: 'valid/handler' } }); | ||
@@ -193,0 +198,0 @@ server.inject({ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
829739
1.59%16
-5.88%0
-100%173
-1.14%13428
-4.21%64
-65.41%