diff --git a/History.md b/History.md index 5057dc6..f188cfd 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,33 @@ +2.0.0 / +================== + + * merge v1.1.2 and v1.2.0 changes + * include `koa-convert` so that generator functions still work + * NOTE: generator functions are deprecated in v2 and will be removed in v3 + * improve linting + * improve docs + +1.2.0 / 2016-03-03 +================== + + * add support for `err.headers` in `ctx.onerror()` + - see: https://github.com/koajs/koa/pull/668 + - note: you should set these headers in your custom error handlers as well + - docs: https://github.com/koajs/koa/blob/master/docs/error-handling.md + * fix `cookies`' detection of http/https + - see: https://github.com/koajs/koa/pull/614 + * deprecate `app.experimental = true`. Koa v2 does not use this signature. + * add a code of conduct + * test against the latest version of node + * add a lot of docs + +1.1.2 / 2015-11-05 +================== + + * ensure parseurl always working as expected + * fix Application.inspect() – missing .proxy value. + 2.0.0-alpha.3 / 2015-11-05 ================== diff --git a/docs/api/context.md b/docs/api/context.md index 2cac9bf..a92d062 100644 --- a/docs/api/context.md +++ b/docs/api/context.md @@ -8,14 +8,14 @@ which would force middleware to re-implement this common functionality. A `Context` is created _per_ request, and is referenced in middleware - as the receiver, or the `this` identifier, as shown in the following + as the receiver, or the `ctx` identifier, as shown in the following snippet: ```js -app.use(function * () { - this; // is the Context - this.request; // is a koa Request - this.response; // is a koa Response +app.use(async (ctx, next) => { + ctx; // is the Context + ctx.request; // is a koa Request + ctx.response; // is a koa Response }); ``` @@ -55,7 +55,7 @@ app.use(function * () { The recommended namespace for passing information through middleware and to your frontend views. ```js -this.state.user = yield User.find(id); +ctx.state.user = yield User.find(id); ``` ### ctx.app @@ -90,13 +90,13 @@ koa uses the [cookies](https://github.com/jed/cookies) module where options are The following combinations are allowed: ```js -this.throw(403); -this.throw('name required', 400); -this.throw(400, 'name required'); -this.throw('something exploded'); +ctx.throw(403); +ctx.throw('name required', 400); +ctx.throw(400, 'name required'); +ctx.throw('something exploded'); ``` - For example `this.throw('name required', 400)` is equivalent to: + For example `ctx.throw('name required', 400)` is equivalent to: ```js const err = new Error('name required'); @@ -113,8 +113,8 @@ throw err; You may optionally pass a `properties` object which is merged into the error as-is, useful for decorating machine-friendly errors which are reported to the requester upstream. ```js -this.throw(401, 'access_denied', { user: user }); -this.throw('access_denied', { user: user }); +ctx.throw(401, 'access_denied', { user: user }); +ctx.throw('access_denied', { user: user }); ``` koa uses [http-errors](https://github.com/jshttp/http-errors) to create errors. @@ -126,14 +126,14 @@ koa uses [http-errors](https://github.com/jshttp/http-errors) to create errors. method. ```js -this.assert(this.state.user, 401, 'User not found. Please login!'); +ctx.assert(ctx.state.user, 401, 'User not found. Please login!'); ``` koa uses [http-assert](https://github.com/jshttp/http-assert) for assertions. ### ctx.respond - To bypass Koa's built-in response handling, you may explicitly set `this.respond = false;`. Use this if you want to write to the raw `res` object instead of letting Koa handle the response for you. + To bypass Koa's built-in response handling, you may explicitly set `ctx.respond = false;`. Use this if you want to write to the raw `res` object instead of letting Koa handle the response for you. Note that using this is __not__ supported by Koa. This may break intended functionality of Koa middleware and Koa itself. Using this property is considered a hack and is only a convenience to those wishing to use traditional `fn(req, res)` functions and middleware within Koa. diff --git a/docs/api/index.md b/docs/api/index.md index c0a5fc8..b966c5a 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -179,7 +179,7 @@ app.keys = new KeyGrip(['im a newer secret', 'i like turtle'], 'sha256'); with the `{ signed: true }` option: ```js -this.cookies.set('name', 'tobi', { signed: true }); +ctx.cookies.set('name', 'tobi', { signed: true }); ``` ## app.context diff --git a/docs/api/request.md b/docs/api/request.md index 9b2ca10..a930bfb 100644 --- a/docs/api/request.md +++ b/docs/api/request.md @@ -44,7 +44,7 @@ Get origin of URL, include `protocol` and `host`. ```js -this.request.origin +ctx.request.origin // => http://example.com ``` @@ -53,7 +53,7 @@ this.request.origin Get full request URL, include `protocol`, `host` and `url`. ```js -this.request.href +ctx.request.href // => http://example.com/foo/bar?q=1 ``` @@ -96,7 +96,7 @@ this.request.href Get request `Content-Type` void of parameters such as "charset". ```js -const ct = this.request.type +const ct = ctx.request.type // => "image/png" ``` @@ -105,7 +105,7 @@ const ct = this.request.type Get request charset when present, or `undefined`: ```js -this.request.charset +ctx.request.charset // => "utf-8" ``` @@ -130,7 +130,7 @@ this.request.charset setter does _not_ support nested objects. ```js -this.query = { next: '/login' } +ctx.query = { next: '/login' } ``` ### request.fresh @@ -140,18 +140,18 @@ this.query = { next: '/login' } ```js // freshness check requires status 20x or 304 -this.status = 200; -this.set('ETag', '123'); +ctx.status = 200; +ctx.set('ETag', '123'); // cache is ok -if (this.fresh) { - this.status = 304; +if (ctx.fresh) { + ctx.status = 304; return; } // cache is stale // fetch new data -this.body = yield db.find('something'); +ctx.body = yield db.find('something'); ``` ### request.stale @@ -165,7 +165,7 @@ this.body = yield db.find('something'); ### request.secure - Shorthand for `this.protocol == "https"` to check if a request was + Shorthand for `ctx.protocol == "https"` to check if a request was issued via TLS. ### request.ip @@ -188,8 +188,8 @@ this.body = yield db.find('something'); parts of the host. This can be changed by setting `app.subdomainOffset`. For example, if the domain is "tobi.ferrets.example.com": - If `app.subdomainOffset` is not set, this.subdomains is `["ferrets", "tobi"]`. - If `app.subdomainOffset` is 3, this.subdomains is `["tobi"]`. + If `app.subdomainOffset` is not set, `ctx.subdomains` is `["ferrets", "tobi"]`. + If `app.subdomainOffset` is 3, `ctx.subdomains` is `["tobi"]`. ### request.is(types...) @@ -201,26 +201,26 @@ this.body = yield db.find('something'); ```js // With Content-Type: text/html; charset=utf-8 -this.is('html'); // => 'html' -this.is('text/html'); // => 'text/html' -this.is('text/*', 'text/html'); // => 'text/html' +ctx.is('html'); // => 'html' +ctx.is('text/html'); // => 'text/html' +ctx.is('text/*', 'text/html'); // => 'text/html' // When Content-Type is application/json -this.is('json', 'urlencoded'); // => 'json' -this.is('application/json'); // => 'application/json' -this.is('html', 'application/*'); // => 'application/json' +ctx.is('json', 'urlencoded'); // => 'json' +ctx.is('application/json'); // => 'application/json' +ctx.is('html', 'application/*'); // => 'application/json' -this.is('html'); // => false +ctx.is('html'); // => false ``` For example if you want to ensure that only images are sent to a given route: ```js -if (this.is('image/*')) { +if (ctx.is('image/*')) { // process } else { - this.throw(415, 'images only!'); + ctx.throw(415, 'images only!'); } ``` @@ -247,45 +247,45 @@ In the case of missing accept headers where any type is acceptable, the first ty ```js // Accept: text/html -this.accepts('html'); +ctx.accepts('html'); // => "html" // Accept: text/*, application/json -this.accepts('html'); +ctx.accepts('html'); // => "html" -this.accepts('text/html'); +ctx.accepts('text/html'); // => "text/html" -this.accepts('json', 'text'); +ctx.accepts('json', 'text'); // => "json" -this.accepts('application/json'); +ctx.accepts('application/json'); // => "application/json" // Accept: text/*, application/json -this.accepts('image/png'); -this.accepts('png'); +ctx.accepts('image/png'); +ctx.accepts('png'); // => false // Accept: text/*;q=.5, application/json -this.accepts(['html', 'json']); -this.accepts('html', 'json'); +ctx.accepts(['html', 'json']); +ctx.accepts('html', 'json'); // => "json" // No Accept header -this.accepts('html', 'json'); +ctx.accepts('html', 'json'); // => "html" -this.accepts('json', 'html'); +ctx.accepts('json', 'html'); // => "json" ``` - You may call `this.accepts()` as many times as you like, + You may call `ctx.accepts()` as many times as you like, or use a switch: ```js -switch (this.accepts('json', 'html', 'text')) { +switch (ctx.accepts('json', 'html', 'text')) { case 'json': break; case 'html': break; case 'text': break; - default: this.throw(406, 'json, html, or text only'); + default: ctx.throw(406, 'json, html, or text only'); } ``` @@ -295,10 +295,10 @@ switch (this.accepts('json', 'html', 'text')) { ```js // Accept-Encoding: gzip -this.acceptsEncodings('gzip', 'deflate', 'identity'); +ctx.acceptsEncodings('gzip', 'deflate', 'identity'); // => "gzip" -this.acceptsEncodings(['gzip', 'deflate', 'identity']); +ctx.acceptsEncodings(['gzip', 'deflate', 'identity']); // => "gzip" ``` @@ -307,7 +307,7 @@ this.acceptsEncodings(['gzip', 'deflate', 'identity']); ```js // Accept-Encoding: gzip, deflate -this.acceptsEncodings(); +ctx.acceptsEncodings(); // => ["gzip", "deflate", "identity"] ``` @@ -320,10 +320,10 @@ this.acceptsEncodings(); ```js // Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5 -this.acceptsCharsets('utf-8', 'utf-7'); +ctx.acceptsCharsets('utf-8', 'utf-7'); // => "utf-8" -this.acceptsCharsets(['utf-7', 'utf-8']); +ctx.acceptsCharsets(['utf-7', 'utf-8']); // => "utf-8" ``` @@ -332,7 +332,7 @@ this.acceptsCharsets(['utf-7', 'utf-8']); ```js // Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5 -this.acceptsCharsets(); +ctx.acceptsCharsets(); // => ["utf-8", "utf-7", "iso-8859-1"] ``` @@ -343,10 +343,10 @@ this.acceptsCharsets(); ```js // Accept-Language: en;q=0.8, es, pt -this.acceptsLanguages('es', 'en'); +ctx.acceptsLanguages('es', 'en'); // => "es" -this.acceptsLanguages(['en', 'es']); +ctx.acceptsLanguages(['en', 'es']); // => "es" ``` @@ -355,7 +355,7 @@ this.acceptsLanguages(['en', 'es']); ```js // Accept-Language: en;q=0.8, es, pt -this.acceptsLanguages(); +ctx.acceptsLanguages(); // => ["es", "pt", "en"] ``` diff --git a/docs/api/response.md b/docs/api/response.md index 338ea7e..b50a70f 100644 --- a/docs/api/response.md +++ b/docs/api/response.md @@ -104,7 +104,7 @@ so you can make a correction. ### response.length Return response Content-Length as a number when present, or deduce - from `this.body` when possible, or `undefined`. + from `ctx.body` when possible, or `undefined`. ### response.body @@ -149,7 +149,7 @@ If `response.status` has not been set, Koa will automatically set the status to const PassThrough = require('stream').PassThrough; app.use(function * (next) { - this.body = someHTTPStream.on('error', this.onerror).pipe(PassThrough()); + ctx.body = someHTTPStream.on('error', ctx.onerror).pipe(PassThrough()); }); ``` @@ -162,7 +162,7 @@ app.use(function * (next) { Get a response header field value with case-insensitive `field`. ```js -const etag = this.get('ETag'); +const etag = ctx.get('ETag'); ``` ### response.set(field, value) @@ -170,14 +170,14 @@ const etag = this.get('ETag'); Set response header `field` to `value`: ```js -this.set('Cache-Control', 'no-cache'); +ctx.set('Cache-Control', 'no-cache'); ``` ### response.append(field, value) Append additional header `field` with value `val`. ```js -this.append('Link', ''); +ctx.append('Link', ''); ``` ### response.set(fields) @@ -185,7 +185,7 @@ this.append('Link', ''); Set several response header `fields` with an object: ```js -this.set({ +ctx.set({ 'Etag': '1234', 'Last-Modified': date }); @@ -200,7 +200,7 @@ this.set({ Get response `Content-Type` void of parameters such as "charset". ```js -const ct = this.type; +const ct = ctx.type; // => "image/png" ``` @@ -209,10 +209,10 @@ const ct = this.type; Set response `Content-Type` via mime string or file extension. ```js -this.type = 'text/plain; charset=utf-8'; -this.type = 'image/png'; -this.type = '.png'; -this.type = 'png'; +ctx.type = 'text/plain; charset=utf-8'; +ctx.type = 'image/png'; +ctx.type = '.png'; +ctx.type = 'png'; ``` Note: when appropriate a `charset` is selected for you, for @@ -222,7 +222,7 @@ this.type = 'png'; ### response.is(types...) - Very similar to `this.request.is()`. + Very similar to `ctx.request.is()`. Check whether the response type is one of the supplied types. This is particularly useful for creating middleware that manipulate responses. @@ -236,13 +236,13 @@ const minify = require('html-minifier'); app.use(function * minifyHTML(next) { yield next; - if (!this.response.is('html')) return; + if (!ctx.response.is('html')) return; - let body = this.body; + let body = ctx.body; if (!body || body.pipe) return; if (Buffer.isBuffer(body)) body = body.toString(); - this.body = minify(body); + ctx.body = minify(body); }); ``` @@ -255,19 +255,19 @@ app.use(function * minifyHTML(next) { is not present `alt` or "/" is used. ```js -this.redirect('back'); -this.redirect('back', '/index.html'); -this.redirect('/login'); -this.redirect('http://google.com'); +ctx.redirect('back'); +ctx.redirect('back', '/index.html'); +ctx.redirect('/login'); +ctx.redirect('http://google.com'); ``` To alter the default status of `302`, simply assign the status before or after this call. To alter the body, assign it after this call: ```js -this.status = 301; -this.redirect('/cart'); -this.body = 'Redirecting to shopping cart'; +ctx.status = 301; +ctx.redirect('/cart'); +ctx.body = 'Redirecting to shopping cart'; ``` ### response.attachment([filename]) @@ -291,7 +291,7 @@ this.body = 'Redirecting to shopping cart'; You can either set it as a `Date` or date string. ```js -this.response.lastModified = new Date(); +ctx.response.lastModified = new Date(); ``` ### response.etag= @@ -300,7 +300,7 @@ this.response.lastModified = new Date(); Note that there is no corresponding `response.etag` getter. ```js -this.response.etag = crypto.createHash('md5').update(this.body).digest('hex'); +ctx.response.etag = crypto.createHash('md5').update(ctx.body).digest('hex'); ``` ### response.vary(field) diff --git a/docs/error-handling.md b/docs/error-handling.md index 32b8b26..6fc579d 100644 --- a/docs/error-handling.md +++ b/docs/error-handling.md @@ -2,16 +2,16 @@ ## Try-Catch - Using generators means that you can try-catch `next`. For example, - this example prepends all error messages with "Error: " + Using async functions means that you can try-catch `next`. + This example adds a `.status` to all errors: ```js app.use(async (ctx, next) => { try { await next(); - } catch (error) { - error.message = 'Error: ' + error.message; - throw error; + } catch (err) { + err.status = err.statusCode || err.status || 500; + throw err; } }); ``` @@ -32,13 +32,29 @@ will then be set. You can use a try-catch, as specified above, to add a header to this list. + Here is an example of creating your own error handler: + +```js +app.use(async (ctx, next) => { + try { + await next(); + } catch (err) { + // will only respond with JSON + this.status = err.statusCode || err.status || 500; + this.body = { + message: err.message + }; + } +}) +``` + ## 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 + Error event listeners can be specified with `app.on('error')`. + If no error listener is specified, a default error listener + is used. Error listener receive 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 + and not thrown again, it will not be passed to the error + listener. If no error event listener is specified, then `app.onerror` will be used, which simply log the error if `error.expose` is true and `app.silent` is false.