diff --git a/docs/error-handling.md b/docs/error-handling.md new file mode 100644 index 0000000..1bd82a8 --- /dev/null +++ b/docs/error-handling.md @@ -0,0 +1,44 @@ +# Error Handling + +## Try-Catch + + Using generators means that you can try-catch `next`. For example, + this example prepends all error messages with "Error: " + + ```js + app.use(function*(next){ + try { + yield next; + } catch (error) { + error.message = 'Error: ' + error.message; + throw error; + } + }); + ``` + +### Default Error Handler + + The default error handler is essentially a try-catch at + the very beginning of the middleware chain. To use a + different error handler, simply put another try-catch at + the beginning of the middleware chain, and handle the error + there. However, the default error handler is good enough for + most use cases. It will use a status code of `err.status`, + or by default 500. If `err.expose` is true, then `err.message` + will be the reply. Otherwise, a message generated from the + error code will be used (e.g. for the code 500 the message + "Internal Server Error" will be used). All headers will be + cleared from the request, but any headers in `err.headers` + will then be set. You can use a try-catch, as specified + above, to add a header to this list. + +## The Error Event + + Error handlers can be specified with `app.on('error')`. + If no error handler is specified, a default error handler + is used. Error handlers recieve all errors that make their + way back through the middleware chain, if an error is caught + and not thrown again, it will not be handled by the error + handler. If not error event handler is specified, then + `app.onerror` will be used, which simply log the error if + `error.expose` is true and `app.silent` is false. diff --git a/lib/context.js b/lib/context.js index 4f1b83f..93be63f 100644 --- a/lib/context.js +++ b/lib/context.js @@ -117,8 +117,9 @@ const proto = module.exports = { return; } - // unset all headers + // unset all headers, and set those specified this.res._headers = {}; + this.set(err.headers); // force text/plain this.type = 'text'; diff --git a/test/context/onerror.js b/test/context/onerror.js index 5c3b3cc..322c185 100644 --- a/test/context/onerror.js +++ b/test/context/onerror.js @@ -52,6 +52,40 @@ describe('ctx.onerror(err)', () => { }); }); + it('should set headers specified in the error', done => { + const app = new Koa(); + + app.use((ctx, next) => { + ctx.set('Vary', 'Accept-Encoding'); + ctx.set('X-CSRF-Token', 'asdf'); + ctx.body = 'response'; + + throw Object.assign(new Error('boom'), { + status: 418, + expose: true, + headers: { + 'X-New-Header': 'Value' + } + }); + }); + + const server = app.listen(); + + request(server) + .get('/') + .expect(418) + .expect('Content-Type', 'text/plain; charset=utf-8') + .expect('X-New-Header', 'Value') + .end((err, res) => { + if (err) return done(err); + + res.headers.should.not.have.property('vary'); + res.headers.should.not.have.property('x-csrf-token'); + + done(); + }); + }); + describe('when invalid err.status', () => { describe('not number', () => { it('should respond 500', done => {