diff --git a/lib/application.js b/lib/application.js index 741fff9fc..035f73dad 100644 --- a/lib/application.js +++ b/lib/application.js @@ -21,8 +21,6 @@ const assert = require('assert'); const Stream = require('stream'); const http = require('http'); const only = require('only'); -const convert = require('koa-convert'); -const deprecate = require('depd')('koa'); /** * Expose `Application` class. @@ -42,6 +40,7 @@ module.exports = class Application extends Emitter { this.proxy = false; this.middleware = []; + this.wrappers = []; this.subdomainOffset = 2; this.env = process.env.NODE_ENV || 'development'; this.context = Object.create(context); @@ -95,26 +94,48 @@ module.exports = class Application extends Emitter { /** * Use the given middleware `fn`. * - * Old-style middleware will be converted. - * * @param {Function} fn * @return {Application} self * @api public */ use(fn) { - if (typeof fn !== 'function') throw new TypeError('middleware must be a function!'); - if (isGeneratorFunction(fn)) { - deprecate('Support for generators will been removed in v3. ' + - 'See the documentation for examples of how to convert old middleware ' + - 'https://github.com/koajs/koa/tree/v2.x#old-signature-middleware-v1x---deprecated'); - fn = convert(fn); - } debug('use %s', fn._name || fn.name || '-'); this.middleware.push(fn); return this; } + /** + * Wrap a middleware with all wrappers + * Also calls the .onUsed hook with useChain + * + * @param {Function} mw the middleware + * @param {Object} useContext the useContext passed to you by onUsed + * @return {Function} the wrapped middleware + * @api public + */ + + prepareMiddleware(mw, useContext) { + let oldMiddleware = useContext.currMiddleware; + useContext = { + app: useContext.app, + useChain: Array.from(useContext.useChain), + currMiddleware: mw + }; + if (oldMiddleware) { + useContext.useChain.push(oldMiddleware); + } + useContext.prepareMiddleware = mw => useContext.app.prepareMiddleware(mw, useContext); + if (mw.onUsed) { + mw.onUsed(useContext); + } + this.wrappers.forEach(wrapper => mw = wrapper(mw, useContext)); + if (typeof mw !== 'function' || isGeneratorFunction(mw)) { + throw new TypeError('middleware must be a non-generator function!'); + } + return mw; + } + /** * Return a request handler callback * for node's native http server. @@ -124,7 +145,12 @@ module.exports = class Application extends Emitter { */ callback() { - const fn = compose(this.middleware); + let fn = compose(this.middleware); + fn._name = 'koa'; + fn = this.prepareMiddleware(fn, { + app: this, + useChain: [] + }); if (!this.listeners('error').length) this.on('error', this.onerror); diff --git a/package.json b/package.json index 94be43923..025b276c5 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "http-errors": "^1.2.8", "is-generator-function": "^1.0.3", "koa-compose": "^3.0.0", - "koa-convert": "^1.2.0", "koa-is-json": "^1.0.0", "mime-types": "^2.0.7", "on-finished": "^2.1.0", diff --git a/test/application/prepareMiddleware.js b/test/application/prepareMiddleware.js new file mode 100644 index 000000000..16a2e2dec --- /dev/null +++ b/test/application/prepareMiddleware.js @@ -0,0 +1,90 @@ + +'use strict'; + +const request = require('supertest'); +const assert = require('assert'); +const compose = require('koa-compose'); +const Koa = require('../..'); + +describe('app.prepareMiddleware(mw, useContext, userName)', () => { + it('should prepare and wrap the middleware', () => { + const app = new Koa(); + let newUseContext; + let before = false; + let after = false; + app.wrappers.push((fn, shouldBeUseContext) => { + assert.equal(shouldBeUseContext.app, app); + return (ctx, next) => { + before = true; + return Promise.resolve().then(() => fn(ctx, next)).then(() => after = true); + }; + }); + let useContext = { + app: app, + useChain: [], + currMiddleware: 'oldMiddleware' + }; + let mw = ctx => ctx.body = 'body'; + mw.onUsed = ctx => newUseContext = ctx; + let newMw = app.prepareMiddleware(mw, useContext); + assert.equal(newUseContext.app, app); + assert.equal(newUseContext.useChain[0], 'oldMiddleware'); + assert.equal(newUseContext.currMiddleware, mw); + assert(newUseContext.prepareMiddleware); + let ctx = {}; + let ret = newMw(ctx, () => {}).then(() => { + assert.equal(ctx.body, 'body'); + assert(after); + }); + assert(before); + return ret; + }); + + it('should work in an actual server', done => { + const app = new Koa(); + let useContext; + + let mw = ctx => ctx.body = 'body'; + mw.onUsed = context => useContext = context; + + app.use(mw); + + const server = app.listen(); + + request(server) + .get('/') + .expect('body') + .expect(200) + .end(err => { + if (err) return done(err); + assert(useContext); + assert.equal(useContext.app, app); + assert.equal(useContext.useChain[0]._name, 'koa'); + done(); + }); + }); + + it('should work support nested middleware', done => { + const app = new Koa(); + let useContext; + + let mw = ctx => ctx.body = 'body'; + mw.onUsed = context => useContext = context; + + app.use(compose([mw])); + + const server = app.listen(); + + request(server) + .get('/') + .expect('body') + .expect(200) + .end(err => { + if (err) return done(err); + assert(useContext); + assert.equal(useContext.app, app); + assert.equal(useContext.useChain[0]._name, 'koa'); + done(); + }); + }); +}); diff --git a/test/application/use.js b/test/application/use.js index 29daef3c3..be68eae7e 100644 --- a/test/application/use.js +++ b/test/application/use.js @@ -2,7 +2,6 @@ 'use strict'; const request = require('supertest'); -const assert = require('assert'); const Koa = require('../..'); describe('app.use(fn)', () => { @@ -43,43 +42,6 @@ describe('app.use(fn)', () => { }); }); - it('should compose mixed middleware', done => { - process.once('deprecation', () => {}); // silence deprecation message - const app = new Koa(); - const calls = []; - - app.use((ctx, next) => { - calls.push(1); - return next().then(() => { - calls.push(6); - }); - }); - - app.use(function * (next){ - calls.push(2); - yield next; - calls.push(5); - }); - - app.use((ctx, next) => { - calls.push(3); - return next().then(() => { - calls.push(4); - }); - }); - - const server = app.listen(); - - request(server) - .get('/') - .expect(404) - .end(err => { - if (err) return done(err); - calls.should.eql([1, 2, 3, 4, 5, 6]); - done(); - }); - }); - // https://github.com/koajs/koa/pull/530#issuecomment-148138051 it('should catch thrown errors in non-async functions', done => { const app = new Koa(); @@ -91,39 +53,4 @@ describe('app.use(fn)', () => { .expect(404) .end(done); }); - - it('should accept both generator and function middleware', done => { - process.once('deprecation', () => {}); // silence deprecation message - const app = new Koa(); - - app.use((ctx, next) => { return next(); }); - app.use(function * (next){ this.body = 'generator'; }); - - request(app.listen()) - .get('/') - .expect(200) - .expect('generator', done); - }); - - it('should throw error for non function', () => { - const app = new Koa(); - - [null, undefined, 0, false, 'not a function'].forEach(v => (() => app.use(v)).should.throw('middleware must be a function!')); - }); - - it('should output deprecation message for generator functions', done => { - process.once('deprecation', message => { - assert(/Support for generators will been removed/.test(message)); - done(); - }); - - const app = new Koa(); - app.use(function * (){}); - }); - - it('should throw error for non function', () => { - const app = new Koa(); - - (() => app.use('not a function')).should.throw('middleware must be a function!'); - }); });