2.1 express@4.15.3
2.1.1 例子
1 'use strict'; 2 3 var express = require('express'); 4 var app = express(); 5 6 app.use((req, res, next) => { 7 console.log('middleware 1 before'); 8 next(); 9 console.log('middleware 1 after'); 10 }); 11 12 app.use((req, res, next) => { 13 console.log('middleware 2 before'); 14 next(); 15 console.log('middleware 2 after'); 16 }); 17 18 app.use((req, res, next) => { 19 console.log('middleware 3 before'); 20 next(); 21 console.log('middleware 3 after'); 22 }); 23 24 app.listen(8888);
啓動後執行「wget localhost:8888」以觸發請求。
輸出:
node
[Sherlock@Holmes Moriarty]$ node app.js middleware 1 before middleware 2 before middleware 3 before middleware 3 after middleware 2 after middleware 1 after
經過調用next(),去進入後續的中間件。
若是少了第14行代碼,那麼middleware 3不會進入。
express
2.1.2 源碼
1. node原生建立一個http server
1 'use strict'; 2 3 var http = require('http'); 4 5 var app = http.createServer(function(req, res) { 6 res.writeHead(200, {'Content-Type': 'text/plain'}); 7 res.end('Hello world'); 8 }); 9 10 app.listen(8889)
2. 經過express()建立的app
express/lib/express.js 16 var mixin = require('merge-descriptors'); 17 var proto = require('./application'); 23 /** 24 * Expose `createApplication()`. 25 */ 26 27 exports = module.exports = createApplication; 28 29 /** 30 * Create an express application. 31 * 32 * @return {Function} 33 * @api public 34 */ 35 36 function createApplication() { 37 var app = function(req, res, next) { 38 app.handle(req, res, next); 39 }; 42 mixin(app, proto, false); 55 return app; 56 } express/lib/application.js 38 var app = exports = module.exports = {}; 616 app.listen = function listen() { 617 var server = http.createServer(this); 618 return server.listen.apply(server, arguments); 619 };
能夠看到 app=require('express')()返回的是createApplication()裏的app,即一個function(req, res, next) {} 函數。
當調用app.listen()時,把該app做爲原生的http.createServer()的回調函數。所以,接收請求時其實是進入了37~39行代碼的回調函數。
進而進入到app.handle(req, res, next)。
api
3. 中間件的添加與觸發
在中間件的處理過程當中,實際上通過幾個對象階段。
app(express/lib/application.js) -> Router(express/lib/router/index.js) -> Layer(express/lib/router/layer.js)
數組
一個app中經過this._router維護一個Router對象。
一個Router經過this.stack 維護不少個Layer對象,每一個Layer對象封裝一箇中間件。
promise
在2.1.1的例子中,添加一箇中間件,經過app.use(fn) -> app._router.use(path, fn) -> app.stack.push(new Layer(paht, {}, fn))app
當一個請求到來時觸發中間件執行,經過
app.handle(req, res, undefined) //原生的http.createServer()的回調函數參數只接收req、res兩個參數,next參數爲undefined)
-> app._router.handle(req, res, done)
-> layer.handle_requeset(req, res, next)
dom
express/lib/application.js 137 app.lazyrouter = function lazyrouter() { 138 if (!this._router) { 139 this._router = new Router({ 140 caseSensitive: this.enabled('case sensitive routing'), 141 strict: this.enabled('strict routing') 142 }); 143 144 this._router.use(query(this.get('query parser fn'))); 145 this._router.use(middleware.init(this)); 146 } 147 }; 158 app.handle = function handle(req, res, callback) { 159 var router = this._router; 160 161 // final handler 162 var done = callback || finalhandler(req, res, { 163 env: this.get('env'), 164 onerror: logerror.bind(this) 165 }); 166 167 // no routes 168 if (!router) { 169 debug('no routes defined on app'); 170 done(); 171 return; 172 } 173 174 router.handle(req, res, done); 175 }; 187 app.use = function use(fn) { ... 213 // setup router 214 this.lazyrouter(); 215 var router = this._router; 216 217 fns.forEach(function (fn) { 218 // non-express app 219 if (!fn || !fn.handle || !fn.set) { 220 return router.use(path, fn); 221 } ... 241 return this; 242 }; express/lib/router/index.js 136 proto.handle = function handle(req, res, out) { 137 var self = this; ... 151 // middleware and routes 152 var stack = self.stack; ... 174 next(); 175 176 function next(err) { ... 317 layer.handle_request(req, res, next); ... 319 } 320 }; 428 proto.use = function use(fn) { ... 464 var layer = new Layer(path, { 465 sensitive: this.caseSensitive, 466 strict: false, 467 end: false 468 }, fn); 469 470 layer.route = undefined; 471 472 this.stack.push(layer); 473 } 474 475 return this; 476 }; express/lib/router/layer.js 86 Layer.prototype.handle_request = function handle(req, res, next) { 87 var fn = this.handle; 88 89 if (fn.length > 3) { 90 // not a standard request handler 91 return next(); 92 } 93 94 try { 95 fn(req, res, next); 96 } catch (err) { 97 next(err); 98 } 99 };
在app._router.handle()裏面,最關鍵的形式是:koa
174 next(); 175 176 function next(err) { 317 layer.handle_request(req, res, next); 319 }
這段代碼把next函數傳回給中間件的第三個參數,得以由中間件代碼來控制往下走的流程。而當中間件代碼調用next()時,再次進入到這裏的next函數,從router.stack取出下游中間件繼續執行。異步
2.2 koa@1.4.0
2.2.1 例子
1 'use strict'; 2 3 var koa = require('koa'); 4 var app = koa(); 5 6 app.use(function*(next) { 7 console.log('middleware 1 before'); 8 yield next; 9 console.log('middleware 1 after'); 10 }); 11 12 app.use(function*(next) { 13 console.log('middleware 2 before'); 14 yield next; 15 console.log('middleware 2 after'); 16 }); 17 18 app.use(function*(next) { 19 console.log('middleware 3 before'); 20 yield next; 21 console.log('middleware 3 after'); 22 }); 23 24 app.listen(8888);
寫法跟express很像,輸出也同樣。async
[Sherlock@Holmes Moriarty]$ node app.js middleware 1 before middleware 2 before middleware 3 before middleware 3 after middleware 2 after middleware 1 after
2.2.2 源碼
koa源碼很精簡,只有四個文件。
1. 建立一個app
koa/lib/application.js 26 /** 27 * Application prototype. 28 */ 29 30 var app = Application.prototype; 31 32 /** 33 * Expose `Application`. 34 */ 35 36 module.exports = Application; 37 38 /** 39 * Initialize a new `Application`. 40 * 41 * @api public 42 */ 43 44 function Application() { 45 if (!(this instanceof Application)) return new Application; 46 this.env = process.env.NODE_ENV || 'development'; 47 this.subdomainOffset = 2; 48 this.middleware = []; 49 this.proxy = false; 50 this.context = Object.create(context); 51 this.request = Object.create(request); 52 this.response = Object.create(response); 53 } ... 61 /** 62 * Shorthand for: 63 * 64 * http.createServer(app.callback()).listen(...) 65 * 66 * @param {Mixed} ... 67 * @return {Server} 68 * @api public 69 */ 70 71 app.listen = function(){ 72 debug('listen'); 73 var server = http.createServer(this.callback()); 74 return server.listen.apply(server, arguments); 75 }; ... 121 app.callback = function(){ 122 if (this.experimental) { 123 console.error('Experimental ES7 Async Function support is deprecated. Please look into Koa v2 as the middleware signature has changed.') 124 } 125 var fn = this.experimental 126 ? compose_es7(this.middleware) 127 : co.wrap(compose(this.middleware)); 128 var self = this; 129 130 if (!this.listeners('error').length) this.on('error', this.onerror); 131 132 return function handleRequest(req, res){ 133 res.statusCode = 404; 134 var ctx = self.createContext(req, res); 135 onFinished(res, ctx.onerror); 136 fn.call(ctx).then(function handleResponse() { 137 respond.call(ctx); 138 }).catch(ctx.onerror); 139 } 140 };
經過var app = koa()返回的app就是一個new Application實例。
同express同樣,也是在app.listen()裏面調用原生的http.createServer(),而且傳進統一處理請求的function(req, res){}
2. 中間件的添加與觸發
koa的同樣經過app.use()添加一箇中間件,可是源碼比express簡單得多,僅僅只是this.middleware.push(fn)。
koa/lib/application.js 102 app.use = function(fn){ 103 if (!this.experimental) { 104 // es7 async functions are not allowed, 105 // so we have to make sure that `fn` is a generator function 106 assert(fn && 'GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function'); 107 } 108 debug('use %s', fn._name || fn.name || '-'); 109 this.middleware.push(fn); 110 return this; 111 };
當一個請求到來時,觸發上面app.callback()源碼裏面的handleRequest(req, res)函數。調用fn.call(ctx)執行中間件鏈條。
那麼這裏的關鍵就在於fn。
13 var compose = require('koa-compose'); ... 121 app.callback = function(){ 122 if (this.experimental) { 123 console.error('Experimental ES7 Async Function support is deprecated. Please look into Koa v2 as the middleware signature has changed.') 124 } 125 var fn = this.experimental 126 ? compose_es7(this.middleware) 127 : co.wrap(compose(this.middleware)); 128 var self = this; 129 130 if (!this.listeners('error').length) this.on('error', this.onerror); 131 132 return function handleRequest(req, res){ 133 res.statusCode = 404; 134 var ctx = self.createContext(req, res); 135 onFinished(res, ctx.onerror); 136 fn.call(ctx).then(function handleResponse() { 137 respond.call(ctx); 138 }).catch(ctx.onerror); 139 } 140 }
這裏的this.experimental不會爲true的了。不然會console.error()。
着重看co.wrap(compose(this.middleware))
這裏的co.wrap()實際上就是上篇博客《Es五、Es六、Es7中的異步寫法》 講的co庫的內容。
co/index.js 26 co.wrap = function (fn) { 27 createPromise.__generatorFunction__ = fn; 28 return createPromise; 29 function createPromise() { 30 return co.call(this, fn.apply(this, arguments)); 31 } 32 };
這裏的fn參數來自compose(this.middleware)返回的Generator函數,Generator函數經過co.call()調用後執行至結束並返回promise對象。
可是co.wrap()自己還不會調用co.call()進而觸發執行中間件鏈條。co.wrap()只是返回了一個createPromise()函數,在該函數裏面纔會執行中間件鏈條。
所以,co.wrap()返回的fn,在請求到來觸發handleRequest(req, res)以後,經過fn.call(ctx)時纔會執行中間件。ctx是針對每次請求包裝的上下文。
這個ctx即createPromise()的this,再經過co.call(this, ...),傳給了compose(this.middleware)返回的Generator函數的this。
這個this在compose源碼裏面(在下面)再經過middleware[i].call(this, next),傳給了用戶的中間件代碼的this。
再回來看compose(this.middleware)如何把中間件數組處理成一個Generator函數返回給co調用。
compose()函數來自koa-compose包,這個包只有一個文件,且很短。
// version 2.5.1 koa-compose/index.js 1 2 /** 3 * Expose compositor. 4 */ 5 6 module.exports = compose; 7 8 /** 9 * Compose `middleware` returning 10 * a fully valid middleware comprised 11 * of all those which are passed. 12 * 13 * @param {Array} middleware 14 * @return {Function} 15 * @api public 16 */ 17 18 function compose(middleware){ 19 return function *(next){ 20 if (!next) next = noop(); 21 22 var i = middleware.length; 23 24 while (i--) { 25 next = middleware[i].call(this, next); 26 } 27 28 return yield *next; 29 } 30 } 31 32 /** 33 * Noop. 34 * 35 * @api private 36 */ 37 38 function *noop(){}
這裏的middleware[i]循環是從最後的中間件往前的遍歷。
首先co.call()觸發的是compose()返回的一個匿名的Generator函數。拿到的參數next實際上傳給了最後一箇中間件的next。
進入匿名函數的循環裏面,最後一箇中間件(好比第3個)調用以後返回一個Iterator(注意Generator調用後還不會執行內部代碼),這個Iterator做爲第2箇中間件的next參數。第二個中間件調用以後一樣返回Iterator對象做爲第一個中間件的next參數。
而第一個中間件返回的Iterator對象被外層的匿名Generator函數yield回去。
觸發以後即是執行第一個中間件,在第一個中間件裏面yield next,即是執行第二個中間件。
2.3 koa@2.3.0
2.3.1 例子
1 'use strict'; 2 3 var Koa = require('koa'); 4 var app = new Koa(); // 再也不直接經過koa()返回一個app 5 6 app.use(async (ctx, next) => { 7 console.log('middleware 1 before'); 8 await next(); 9 console.log('middleware 1 after'); 10 }); 11 12 app.use(async (ctx, next) => { 13 console.log('middleware 2 before'); 14 await next(); 15 console.log('middleware 2 after'); 16 }); 17 18 app.use(async (ctx, next) => { 19 console.log('middleware 3 before'); 20 await next(); 21 console.log('middleware 3 after'); 22 }); 23 24 app.listen(8888);
輸出同上兩個都同樣。
2.3.2 源碼
koa@2的app.listen()和app.use()同koa1差很少。區別在於app.callback()和koa-compose包。
koa/lib/application.js 32 module.exports = class Application extends Emitter { ... 125 callback() { 126 console.log('here'); 127 const fn = compose(this.middleware); 128 console.log('here2'); 129 130 if (!this.listeners('error').length) this.on('error', this.onerror); 131 132 const handleRequest = (req, res) => { 133 res.statusCode = 404; 134 const ctx = this.createContext(req, res); 135 const onerror = err => ctx.onerror(err); 136 const handleResponse = () => respond(ctx); 137 onFinished(res, onerror); 138 return fn(ctx).then(handleResponse).catch(onerror); 139 }; 140 141 return handleRequest; 142 } ... 189 };
koa2不依賴於Generator函數特性,也就不依賴co庫來激發。
經過compose(this.middleware)把全部async函數中間件包裝在一個匿名函數裏頭。
這個匿名函數在請求到來的時候經過fn(ctx)執行。
在該函數裏面,再依次處理全部中間件。
看compose()源碼:
koa-compose/index.js // version 4.0.0 1 'use strict' 2 3 /** 4 * Expose compositor. 5 */ 6 7 module.exports = compose 8 9 /** 10 * Compose `middleware` returning 11 * a fully valid middleware comprised 12 * of all those which are passed. 13 * 14 * @param {Array} middleware 15 * @return {Function} 16 * @api public 17 */ 18 19 function compose (middleware) { 20 if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') 21 for (const fn of middleware) { 22 if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') 23 } 24 25 /** 26 * @param {Object} context 27 * @return {Promise} 28 * @api public 29 */ 30 31 return function (context, next) { 32 // last called middleware # 33 let index = -1 34 return dispatch(0) 35 function dispatch (i) { 36 if (i <= index) return Promise.reject(new Error('next() called multiple times')) 37 index = i 38 let fn = middleware[i] 39 if (i === middleware.length) fn = next 40 if (!fn) return Promise.resolve() 41 try { 42 return Promise.resolve(fn(context, function next () { 43 return dispatch(i + 1) 44 })) 45 } catch (err) { 46 return Promise.reject(err) 47 } 48 } 49 } 50 }
31~49行的代碼,在請求到來時執行,並執行中間件鏈條。
第42~44行代碼就是執行第i箇中間件。傳給中間件的兩個參數context、next函數。當中間件await next()時,調用dispatch(i+1),等待下一個中間執行完畢。
注意到42行把中間件函數的返回值使用Promise.resolve()包裝成Promise值。咱們能夠在中間件裏面返回一個Promise,而且等待該Promise被settle,才從當前中間件返回。
好比2.3.1的例子中的第二個中間件修改爲:
12 app.use(async (ctx, next) => { 13 console.log('middleware 2 before'); 14 await next(); 15 console.log('middleware 2 after'); 16 return new Promise((resolve, reject) => { 17 setTimeout(() => { 18 console.log('timeout'); 19 return resolve(); 20 }, 3000); 21 }); 22 });
那麼輸出會變成:
[Sherlock@Holmes Moriarty]$ node app.js middleware 1 before middleware 2 before middleware 3 before middleware 3 after middleware 2 after timeout middleware 1 after
但注意若是漏寫了第19行代碼,即Promise不會被settle,那麼最後的「middleware 1 after」不會被輸出。