Skip to content

Instantly share code, notes, and snippets.

@zemccartney
Last active September 19, 2019 16:08
Show Gist options
  • Save zemccartney/c53c839e231930542203693c3a955133 to your computer and use it in GitHub Desktop.
Save zemccartney/c53c839e231930542203693c3a955133 to your computer and use it in GitHub Desktop.
Very rough crash-only hapi server
'use strict';
// Adapted from Exiting README example: https://github.com/kanongil/exiting
const Boom = require('@hapi/boom');
const Exiting = require('exiting');
const Hapi = require('@hapi/hapi');
const server = Hapi.Server();
const manager = Exiting.createManager(server);
server.events.on('stop', () => {
console.log('Server stopped.');
});
const provision = async () => {
server.ext('onPreResponse', async (request, h) => {
// We're trying to express: if the current response represents a bug
// ,then (gracefully) shutdown the server. In production, we'd rely on pm2
// (or some other tooling) to reboot the server
// We crash to:
// - guarantee that any hanging/leaked resources stemming from the bug
// are closed
// - hopefully simplify server's fault model to simplify debugging: crashing = bug (programmer's responsibility to fix)
// TODO Should we stop gracefully on this condition? Or is crashing hard, regardless of other clients,
// the safest option (insofar as it mitigates the risk of other clients immediately encountering this bug)
if (request.response.isBoom && request.response.isDeveloperError) {
console.error('Programmer Error detected; crashing server');
console.error(request.response);
// Reply with the error while stopping the server in the background
// TODO Does this create an unpredictable race, potentially leaving server in an undefined state? Should we await manager.stop();
manager.stop();
return h.continue;
// TODO What does this behavior mean for the web clients?
// https://www.tjvantoll.com/2015/09/13/fetch-and-errors/
// At least for fetch, would get to catch block. Not sure about axios, request, etc.
}
return h.continue;
});
server.route({
method: 'get',
path: '/test/{trigger?}',
options: {
handler: (request, h) => {
const { trigger } = request.params;
switch (trigger) {
case 'throw':
console.log('throwing will not crash; desired behavior');
// hapi treats these as plain errors (just calls boomify)
// I assume so hapi doesn't assume anything about user's error handling
// conventions; user might intend to throw Errors i.e. not use Boom
// see: https://github.com/hapijs/hapi/blob/7a5d2820ccf7f2b82785982cb35a5d0de9a5831e/lib/toolkit.js#L47-L57
throw new Error('regular throw');
case 'boom':
console.log('regular boom error will not crash; desired behavior');
// returning or throwing yields same result
throw Boom.notFound('Not found');
case 'internal':
console.log('standard internal error will not crash; ambiguous?');
// ambiguous b/c Boom.internal does NOT set isDeveloperError,
// even though Boom docs refer to internal as an alias of
// badImplementation, which does set err.isDeveloperError
// Why? Am I just confused?
throw Boom.internal('BAD!');
case 'impl':
console.log('standard bad implementation error will crash; desired behavior');
throw Boom.badImplementation('');
case 'why':
console.log('throwing nonerror will crash the server');
// https://github.com/hapijs/hapi/blob/7a5d2820ccf7f2b82785982cb35a5d0de9a5831e/lib/toolkit.js#L51-L52
// Illustrates that programmer errors can be system errors (as defined by Boom: https://github.com/hapijs/bounce#issystemerrs)
// or misuse of hapi; either case represents an unrecoverable error (bug) caused by the programmer
throw 'thx cinco';
case 'bug':
// triggers a ReferenceError
console.log('programmer error will crash; desired behavior');
return request.bugs.nugs;
default:
return 'success';
}
}
}
});
await manager.start();
console.log('Server started at:', server.info.uri);
};
provision();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment