Repository: koajs/koa
Pull request #848 (2016-10-28 05:16:57)

Return middleware chain promise from `callback()`
The v2.x version of #847, see there for more details.


codecov-io: ## [Current coverage](https://codecov.io/gh/koajs/koa/pull/848?src=pr) is 100% (diff: 100%)
> Merging [#848](https://codecov.io/gh/koajs/koa/pull/848?src=pr) into [v2.x](https://codecov.io/gh/koajs/koa/branch/v2.x?src=pr) will not change coverage

```diff
@@           v2.x   #848   diff @@
==================================
  Files         4      4          
  Lines       418    418          
  Methods      80     80          
  Messages      0      0          
  Branches    100    100          
==================================
  Hits        418    418          
  Misses        0      0          
  Partials      0      0          
```

> Powered by [Codecov](https://codecov.io?src=pr). Last update [2a16426...72acde6](https://codecov.io/gh/koajs/koa/compare/2a16426afe87338bb83bc1eb64d57335656e33d9...72acde67e04eedd24149c321c42a584dcc49223d?src=pr)
PlasmaPower: I'm going to rebase this - it'll be very helpful in rewriting a test, so I hope it gets merged soon.
PlasmaPower: Rebased! Let me know if anything still needs done before merging. As for testing, my new jest test suite will use this feature in a test. Also, I'm not sure this is complicated enough to need it's own test.
nickb1080: 🎉  excellent! @jonathanong -- think we can get this merged soon? Also, I'm going to experiment a bit on some patterns for mounting a Koa app inside a Connect-style app and would be happy to contribute some docs about it once I have an approach that feels right.
PlasmaPower: Rebased again. Anything blocking merge?

Patch:
diff --git a/Readme.md b/Readme.md
index e1cd7e9..fb47dad 100644
--- a/Readme.md
+++ b/Readme.md
@@ -44,11 +44,11 @@ app.listen(3000);
 
 
 ## Middleware
+
 Koa is a middleware framework that can take 3 different kinds of functions as middleware:
 
   * common function
   * async function
-  * generator function
 
 Here is an example of logger middleware with each of the different functions:
 
@@ -78,69 +78,118 @@ app.use((ctx, next) => {
 });
 ```
 
-### GeneratorFunction
+### Koa v1.x Middleware Signature
+
+The middleware signature changed between v1.x and v2.x.  The older signature is deprecated.
+
+**Old signature middleware support will be removed in v3**
+
+Please see the [Migration Guide](docs/migration.md) for more information on upgrading from v1.x and
+using v1.x middleware with v2.x.
+
+## Context, Request and Response
 
-To use generator functions, you must use a wrapper such as [co](https://github.com/tj/co) that is no longer supplied with Koa.
+Each middleware receives a Koa `Context` object that encapsulates an incoming
+http message and the corresponding response to that message.  `ctx` is often used
+as the parameter name for the context object.
 
 ```js
-app.use(co.wrap(function *(ctx, next) {
-  const start = new Date();
-  yield next();
-  const ms = new Date() - start;
-  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
-}));
+app.use(async (ctx, next) => { await next(); });
 ```
 
-### Old signature middleware (v1.x) - Deprecated
-
-**Old signature middleware (v1.x) support will be removed in v3**
+Koa provides a `Request` object as the `request` property of the `Context`.  
+Koa's `Request` object provides helpful methods for working with
+http requests which delegate to an [IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
+from the node `http` module.
 
-Koa v2.x will try to convert legacy signature, generator middleware on `app.use`, using [koa-convert](https://github.com/koajs/convert).
-It is however recommended that you choose to migrate all v1.x middleware as soon as possible.
+Here is an example of checking that a requesting client supports xml.
 
 ```js
-// Koa will convert
-app.use(function *(next) {
-  const start = new Date();
-  yield next;
-  const ms = new Date() - start;
-  console.log(`${this.method} ${this.url} - ${ms}ms`);
+app.use(async (ctx, next) => {
+  ctx.assert(ctx.request.accepts('xml'), 406);
+  // equivalent to:
+  // if (!ctx.request.accepts('xml')) ctx.throw(406);
+  await next();
 });
 ```
 
-You could do it manually as well, in which case Koa will not convert.
+Koa provides a `Response` object as the `response` property of the `Context`.  
+Koa's `Response` object provides helpful methods for working with
+http responses which delegate to a [ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse)
+.  
 
-```js
-const convert = require('koa-convert');
+Koa's pattern of delegating to Node's request and response objects rather than extending them
+provides a cleaner interface and reduces conflicts between different middleware and with Node
+itself as well as providing better support for stream handling.  The `IncomingMessage` can still be
+directly accessed as the `req` property on the `Context` and `ServerResponse` can be directly
+accessed as the `res` property on the `Context`.
 
-app.use(convert(function *(next) {
-  const start = new Date();
-  yield next;
-  const ms = new Date() - start;
-  console.log(`${this.method} ${this.url} - ${ms}ms`);
-}));
+Here is an example using Koa's `Response` object to stream a file as the response body.
+
+```js
+app.use(async (ctx, next) => {
+  await next();
+  ctx.response.type = 'xml';
+  ctx.response.body = fs.createReadStream('really_large.xml');
+});
 ```
 
+The `Context` object also provides shortcuts for methods on its `request` and `response`.  In the prior
+examples,  `ctx.type` can be used instead of `ctx.request.type` and `ctx.accepts` can be used
+instead of `ctx.request.accepts`.
+
+For more information on `Request`, `Response` and `Context`, see the [Request API Reference](docs/api/request.md),
+[Response API Reference](docs/api/response.md) and [Context API Reference](docs/api/context.md).
+
+## Koa Application
+
+The object created when executing `new Koa()` is known as the Koa application object.
+
+The application object is Koa's interface with node's http server and handles the registration
+of middleware, dispatching to the middleware from http, default error handling, as well as
+configuration of the context, request and response objects.
+
+Learn more about the application object in the [Application API Reference](docs/api/index.md).
+
+## Documentation
+
+ - [Usage Guide](docs/guide.md)
+ - [Error Handling](docs/error-handling.md)
+ - [Koa for Express Users](docs/koa-vs-express.md)
+ - [FAQ](docs/faq.md)
+ - [API documentation](docs/api/index.md)
+
 ## Babel setup
 
-For Node 4.0+ and Babel 6.0 you can setup like this:
+If you're not using `node v7.6+`, we recommend setting up `babel` with [`babel-preset-env`](https://github.com/babel/babel-preset-env):
 
 ```bash
-$ npm install babel-register babel-plugin-transform-async-to-generator --save
+$ npm install babel-register babel-preset-env --save
 ```
 
+Setup `babel-register` in your entry file:
+
 ```js
-// set babel in entry file
-require('babel-register')({
-  plugins: ['transform-async-to-generator']
-});
+require('babel-register');
 ```
 
-Check out an example in koa's [test](test/babel/index.js).
+And have your `.babelrc` setup:
+
+```json
+{
+  "presets": [
+    ["env", {
+      "targets": {
+        "node": true
+      }
+    }]
+  ]
+}
+```
 
 ## Troubleshooting
 
-Check the [Troubleshooting Guide](docs/troubleshooting.md) or [Debugging Koa](docs/guide.md#debugging-koa) in 
+Check the [Troubleshooting Guide](docs/troubleshooting.md) or [Debugging Koa](docs/guide.md#debugging-koa) in
 the general Koa guide.
 
 ## Running tests
@@ -155,7 +204,6 @@ See [AUTHORS](AUTHORS).
 
 ## Community
 
- - [API](docs/api/index.md) documentation
  - [Badgeboard](https://koajs.github.io/badgeboard) and list of official modules
  - [Examples](https://github.com/koajs/examples)
  - [Middleware](https://github.com/koajs/koa/wiki) list
@@ -163,13 +211,9 @@ See [AUTHORS](AUTHORS).
  - [G+ Community](https://plus.google.com/communities/101845768320796750641)
  - [Reddit Community](https://www.reddit.com/r/koajs)
  - [Mailing list](https://groups.google.com/forum/#!forum/koajs)
- - [Guide](docs/guide.md)
- - [FAQ](docs/faq.md)
  - [中文文档](https://github.com/guo-yu/koa-guide)
  - __[#koajs]__ on freenode
 
-
-
 ## Backers
 
 Support us with a monthly donation and help us continue our activities.
diff --git a/docs/migration.md b/docs/migration.md
new file mode 100644
index 0000000..52625b4
--- /dev/null
+++ b/docs/migration.md
@@ -0,0 +1,133 @@
+# Migrating from Koa v1.x to v2.x
+
+## New middleware signature 
+
+Koa v2 introduces a new signature for middleware.
+
+**Old signature middleware (v1.x) support will be removed in v3**
+
+The new middleware signature is:
+
+```js
+// uses async arrow functions
+app.use(async (ctx, next) => {
+  try {
+    await next() // next is now a function
+  } catch (err) {
+    ctx.body = { message: err.message }
+    ctx.status = err.status || 500
+  }
+})
+
+app.use(async ctx => {
+  const user = await User.getById(this.session.userid) // await instead of yield
+  ctx.body = user // ctx instead of this
+})
+```
+
+You don't have to use asynchronous functions - you just have to pass a function that returns a promise. 
+A regular function that returns a promise works too!
+
+The signature has changed to pass `Context` via an explicit parameter, `ctx` above, instead of via
+`this`.  The context passing change makes koa more compatible with es6 arrow functions, which capture `this`.
+
+## Using v1.x Middleware in v2.x
+
+Koa v2.x will try to convert legacy signature, generator middleware on `app.use`, using [koa-convert](https://github.com/koajs/convert).
+It is however recommended that you choose to migrate all v1.x middleware as soon as possible.
+
+```js
+// Koa will convert
+app.use(function *(next) {
+  const start = new Date();
+  yield next;
+  const ms = new Date() - start;
+  console.log(`${this.method} ${this.url} - ${ms}ms`);
+});
+```
+
+You could do it manually as well, in which case Koa will not convert.
+
+```js
+const convert = require('koa-convert');
+
+app.use(convert(function *(next) {
+  const start = new Date();
+  yield next;
+  const ms = new Date() - start;
+  console.log(`${this.method} ${this.url} - ${ms}ms`);
+}));
+```
+
+## Upgrading middleware
+
+You will have to convert your generators to async functions with the new middleware signature:
+
+```js
+app.use(async ctx => {
+  const user = await Users.getById(this.session.user_id);
+  await next();
+  ctx.body = { message: 'some message' };
+})
+```
+
+Upgrading your middleware may require some work. One migration path is to update them one-by-one.
+
+1. Wrap all your current middleware in `koa-convert`
+2. Test
+3. `npm outdated` to see which koa middleware is outdated
+4. Update one outdated middleware, remove using `koa-convert`
+5. Test
+6. Repeat steps 3-5 until you're done
+
+
+## Updating your code
+
+You should start refactoring your code now to ease migrating to Koa v2:
+
+- Return promises everywhere!
+- Do not use `yield*`
+- Do not use `yield {}` or `yield []`.
+  - Convert `yield []` into `yield Promise.all([])`
+  - Convert `yield {}` into `yield Bluebird.props({})`
+
+You could also refactor your logic outside of Koa middleware functions. Create functions like 
+`function* someLogic(ctx) {}` and call it in your middleware as 
+`const result = yield someLogic(this)`.
+Not using `this` will help migrations to the new middleware signature, which does not use `this`.
+
+## Application object constructor requires new 
+
+In v1.x, the Application constructor function could be called directly, without `new` to 
+instantiate an instance of an application.  For example:
+
+```js
+var koa = require('koa');
+var app = module.exports = koa();
+```
+
+v2.x uses es6 classes which require the `new` keyword to be used.
+
+```js
+var koa = require('koa');
+var app = module.exports = new koa();
+```
+
+## ENV specific logging behavior removed
+
+An explicit check for the `test` environment was removed from error handling. 
+
+## Dependency changes
+
+- [co](https://github.com/tj/co) is no longer bundled with Koa.  Require or import it directly.
+- [composition](https://github.com/thenables/composition) is no longer used and deprecated.
+
+## v1.x support
+
+The v1.x branch is still supported but should not receive feature updates.  Except for this migration
+guide, documentation will target the latest version.
+
+## Help out
+
+If you encounter migration related issues not covered by this migration guide, please consider 
+submitting a documentation pull request.
diff --git a/lib/application.js b/lib/application.js
index ec30cdc..d68a995 100644
--- a/lib/application.js
+++ b/lib/application.js
@@ -107,8 +107,7 @@ module.exports = class Application extends Emitter {
     if (isGeneratorFunction(fn)) {
       deprecate('Support for generators will be removed in v3. ' +
                 'See the documentation for examples of how to convert old middleware ' +
-                'https://github.com/koajs/koa/blob/master/Readme.md' +
-                '#old-signature-middleware-v1x---deprecated');
+                'https://github.com/koajs/koa/blob/master/docs/migration.md');
       fn = convert(fn);
     }
     debug('use %s', fn._name || fn.name || '-');
@@ -135,7 +134,7 @@ module.exports = class Application extends Emitter {
       const onerror = err => ctx.onerror(err);
       const handleResponse = () => respond(ctx);
       onFinished(res, onerror);
-      fn(ctx).then(handleResponse).catch(onerror);
+      return fn(ctx).then(handleResponse).catch(onerror);
     };
 
     return handleRequest;
diff --git a/lib/context.js b/lib/context.js
index 94b5a42..8e46d20 100644
--- a/lib/context.js
+++ b/lib/context.js
@@ -121,8 +121,16 @@ const proto = module.exports = {
       return;
     }
 
-    // unset all headers, and set those specified
-    this.res._headers = {};
+    const { res } = this;
+
+    // first unset all headers
+    if (typeof res.getHeaderNames === 'function') {
+      res.getHeaderNames().forEach(name => res.removeHeader(name));
+    } else {
+      res._headers = {}; // Node < 7.7
+    }
+
+    // then set those specified
     this.set(err.headers);
 
     // force text/plain
diff --git a/lib/response.js b/lib/response.js
index 87ece39..0e77207 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -44,7 +44,10 @@ module.exports = {
    */
 
   get header() {
-    return this.res._headers || {};
+    const { res } = this;
+    return typeof res.getHeaders === 'function'
+      ? res.getHeaders()
+      : res._headers || {};  // Node < 7.7
   },
 
   /**


Test patch:
diff --git a/test/context/onerror.js b/test/context/onerror.js
index 61b989d..d567259 100644
--- a/test/context/onerror.js
+++ b/test/context/onerror.js
@@ -1,8 +1,10 @@
 
 'use strict';
 
+const assert = require('assert');
 const request = require('supertest');
 const Koa = require('../..');
+const context = require('../helpers/context');
 
 describe('ctx.onerror(err)', () => {
   it('should respond', done => {
@@ -169,5 +171,23 @@ describe('ctx.onerror(err)', () => {
         .expect('Content-Type', 'text/plain; charset=utf-8')
         .expect('Internal Server Error', done);
     });
+
+    it('should use res.getHeaderNames() accessor when available', () => {
+      let removed = 0;
+      const ctx = context();
+
+      ctx.app.emit = () => {};
+      ctx.res = {
+        getHeaderNames: () => ['content-type', 'content-length'],
+        removeHeader: () => removed++,
+        end: () => {},
+        emit: () => {}
+      };
+
+      ctx.onerror(new Error('error'));
+
+      assert.equal(removed, 2);
+    });
   });
 });
+
diff --git a/test/response/header.js b/test/response/header.js
index a378067..d12816d 100644
--- a/test/response/header.js
+++ b/test/response/header.js
@@ -1,7 +1,9 @@
 
 'use strict';
 
+const request = require('supertest');
 const response = require('../helpers/context').response;
+const Koa = require('../..');
 
 describe('res.header', () => {
   it('should return the response header object', () => {
@@ -10,6 +12,28 @@ describe('res.header', () => {
     res.header.should.eql({ 'x-foo': 'bar' });
   });
 
+  it('should use res.getHeaders() accessor when available', () => {
+    const res = response();
+    res.res._headers = null;
+    res.res.getHeaders = () => ({ 'x-foo': 'baz' });
+    res.header.should.eql({ 'x-foo': 'baz' });
+  });
+
+  it('should return the response header object when no mocks are in use', async () => {
+    const app = new Koa();
+    let header;
+
+    app.use(ctx => {
+      ctx.set('x-foo', '42');
+      header = Object.assign({}, ctx.response.header);
+    });
+
+    await request(app.listen())
+      .get('/');
+
+    header.should.eql({ 'x-foo': '42' });
+  });
+
   describe('when res._headers not present', () => {
     it('should return empty object', () => {
       const res = response();