最近項目要使用koa,因此提早學習一下,順便看了koa框架的源碼.html
注:源碼是koa2.xnode
koa的源碼很簡潔,關鍵代碼只有4個文件,固然還包括一些依賴npm包git
const Koa = require('koa'); const app = new Koa(); app.use(async (ctx, next) => { await next(); ctx.type = 'text/html'; ctx.body = '<h1>Hello, koa2!</h2>'; }); app.listen(3000); console.log('app started at port 3000....');
咱們由上面的代碼開始深刻到koa的源碼:github
上面代碼的開頭引入koa框架,接着const app = new Koa();建立koa實例app,koa的構造函數很簡單,以下:npm
constructor() { super(); this.proxy = false; this.middleware = []; //用來存放中間件 this.subdomainOffset = 2; this.env = process.env.NODE_ENV || 'development'; //運行環境 this.context = Object.create(context); //建立context對象 this.request = Object.create(request); //建立request對象 this.response = Object.create(response); //建立response對象 }
use(fn) { if (typeof fn !== 'function') throw new TypeError('middleware must be a function!'); 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/docs/migration.md'); fn = convert(fn); } debug('use %s', fn._name || fn.name || '-'); this.middleware.push(fn); return this; }
use函數首先判斷參數是不是函數,不是就報錯,而後判斷這個函數是不是generator函數,若是是generator則須要轉換一下,經過兩個判斷後,將這個函數push到middleware數組中保存.最後返回this(也是app實例)json
isten() { debug('listen'); const server = http.createServer(this.callback()); return server.listen.apply(server, arguments); }
listen函數裏面調用http.createServe()建立http服務,關鍵是參數this.callback(),看一下代碼:數組
callback() { const fn = compose(this.middleware); if (!this.listeners('error').length) this.on('error', this.onerror); const handleRequest = (req, res) => { res.statusCode = 404; const ctx = this.createContext(req, res); const onerror = err => ctx.onerror(err); const handleResponse = () => respond(ctx); onFinished(res, onerror); return fn(ctx).then(handleResponse).catch(onerror); }; return handleRequest; }
首先,把全部middleware進行了組合,使用了koa-compose,咱們也不用去管他的內部實現,簡單來講就是返回了一個promise數組的遞歸調用。promise
這裏的 const fn = compose(this.middleware);對應的調用代碼以下:(查看koa-compose源碼):cookie
return function (context, next) { // last called middleware #
let index = -1
return dispatch(0) function dispatch (i) { if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i let fn = middleware[i] if (i === middleware.length) fn = next if (!fn) return Promise.resolve() try { return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } } }
return前面的做用是作參數檢測,return後面也就是上面貼出來的代碼纔是咱們要關注的:app
運行流程:
i = 0 ==> index = 0 ==> fn = middleware[0] ==> return Promise.resolve(//若是fn裏面存在await,則functions next()函數會被掉用, 則運行return dispatch(i + 1),以此類推)
知道中間件(固然通常指最後一箇中間件)中不存在await或者全部的中間件都加載完畢(i === middleware.length) ,compose函數則最終返回.
例如:
const Koa = require('koa')
const app = new Koa()
app.use(async function (ctx, next) {
console.log('>> one');
await next();
console.log('<< one');
});
app.use(ctx => {
ctx.body='hello world gcy';
})
上面代碼註冊了兩個中間件,通過const fn = compose(this.middleware)後返回:
fn的形式以下:
Promise.resolve(function(ctx) {
console.log('>> one');
return Promose.resolve(function(ctx){
ctx.body='hello world gcy';
}).then(() => {
console.log('<< one');
})
})
執行結果: 後臺輸出:>> one -- > 頁面輸出:hello world gcy --> 後臺輸出:<< one
接下來:
if (!this.listeners('error').length) this.on('error', this.onerror);
在處理http請求以前,koa會註冊一個默認的錯誤處理函數,但咱們每次http請求錯誤其實是由ctx.onerror處理的:
const onerror = err => ctx.onerror(err);
onFinished(res, onerror);
fn(ctx).then(() => respond(ctx)).catch(onerror)
ctx.onFinished 是確保一個流在關閉、完成和報錯時都會執行相應的回調函數。onerror 就是咱們http請求錯誤處理函數.
咱們看看這個匿名函數,把http code默認設置爲404,接着利用createContext函數把node返回的req和res進行了組合建立出context
來看下createContext函數:
createContext(req, res) {
const context = Object.create(this.context);
const request = context.request = Object.create(this.request);
const response = context.response = Object.create(this.response);
context.app = request.app = response.app = this;
context.req = request.req = response.req = req;
context.res = request.res = response.res = res;
request.ctx = response.ctx = context;
request.response = response;
response.request = request;
context.originalUrl = request.originalUrl = req.url;
context.cookies = new Cookies(req, res, {
keys: this.keys,
secure: request.secure
});
request.ip = request.ips[0] || req.socket.remoteAddress || '';
context.accept = request.accept = accepts(req);
context.state = {};
return context;
}
這裏面都是一堆的組合和賦值,context,request, response相互掛載
值得注意的是:context.req/context.res 和context.request/context.response的區別,context.req/context.res表明nodejs的req和res對象,而context.request/context.response是koa的request和response對象
這個函數最後返回context,而後傳入fn函數,此時fn函數被執行.
callback()函數中調用的respond函數裏面不過是一些收尾工做,例如判斷http code爲空如何輸出啦,http method是head如何輸出啦,body返回是流或json時如何輸出。
context.js
delegate(proto, 'request') //Request相關方法委託,從而讓context做爲調用入口
onerror(err) //中間件執行過程當中異常處理邏輯
request.js,response.js
分別對res和req進行了抽象和封裝