Koa源碼
繼著上文的Co源碼以及與Koa的深入理解,開始學習下Koa是怎么寫的。我看的版本是1.1.2的。
從package.json里面看,Koa的入口是application.js,于是先看這個js。
application引用的模塊
先從最開始引入的一些模塊開始理解。
- debug:最開始引了一個外部的debug模塊。這是個好東西,只要在運行的時候使用
DEBUG=..
就可以了進行代碼的調試了,如下圖,如果有多個的話可以用逗號分隔開。會有比較好看的風格,主要是用來替換console.log的,看上去更專業點。
<img alt="debug使用" width='700px' src="pics//debug.png" />
- events:然后引用了nodejs的內置模塊events,將application的prototype設置了下
- composition:外部模塊,試驗性質。是可以將傳入的函數包裝成一個promise的。
- 主要是支持包括es7的async語法的,如果設置了this.experimental,就會使用這個來執行,否則的話使用co.wrap方法。
- koalization是不是打開了experimental?
- on-finished:外部模塊,是在response結束或者失敗的時候觸發的,如果失敗了的話會帶個error進來。
- response,request,context:3個內部代碼塊,都是重新對req,res和context進行了封裝。
- koa-compose:外部模塊,是個核心方法,將所有的中間件嵌套為循環的genreator,然后就可以交給co去wrap一下變成promise,然后就可以直接執行了。
- koa-is-json:一個小的外部模塊,用來檢測body是不是json
- statuses:外部模塊,簡單的http狀態碼的相互對應而已
- cookies:外部模塊,進行cookie的管理
- accepts:外部模塊,用來對請求頭的accepts進行管理,處理
- assert:外部模塊,用來寫單元測試的
- stream:內置模塊,用來處理流式的數據的
- http:內置模塊,用來createServer的
- only:外部模塊,用來返回一個新對象,包含只在傳入的白名單中的屬性
- co:外部模塊,前面已經研究過的模塊,不再詳細記錄,參見Co
至此,使用的模塊已經清楚,開始看下整個結構。
application的結構設計
Application是被拋出去的總入口,是個構造函數
,他的作用是
- 區分傳入的環境參數
- 初始化存使用的中間件middleware,一個空數組。
- 使用自己的包裝過的3個內部代碼來初始化上下文,req和res。
function Application() {
//這一句的作用挺精髓的,因為application是一個構造函數,如果沒有new調用的話,就return一個new的
//這里返回的是new Application,而不是new Application(),是不是有些奇怪,其實這兩種寫法在沒有參數時沒有區別,在有參數時,必須加上括號~
if (!(this instanceof Application)) return new Application;
//這句就是處理環境參數,默認為開發,代碼里可以根據env的值來決定訪問哪里的數據庫,或者錯誤log的記錄地點等等
this.env = process.env.NODE_ENV || 'development';
//被忽略的子域名位數,比如ppe.b.dianping.com,他的默認子域名就為['ppe','b'],如果設置為3,子域名就為['ppe']
//設置了這個值決定了我們在this.subdomains數組里面可以訪問的值
this.subdomainOffset = 2;
//存中間件,下面的app.use方法直接push就好了
this.middleware = [];
//這個東西也不知道是啥。。
//tudo:X-Forwarded-Proto??
this.proxy = false;
//這三個東西就是作者包裝后的上下文,req,res
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
}
app是Application的原型對象。
強行用emitter的原型來替換了application的原型。
//這樣子就繼承了事件流
//然后下面的代碼this.on('error',fn)來監聽
//this.emit就可以觸發
//這里想到了點評張老師的cortex里面的模塊class也支持implement events這個東西,然后就去看了下他是怎么在browser端支持emitter的,原來是引用了npm的一個模塊events
Object.setPrototypeOf(Application.prototype, Emitter.prototype);
然后對app進行原型上的包裝。
- app.listen:來createServer的,將
this.callback
添加到了request事件上~注意是可以調用多次 - app.toJSON:公有方法,將他作為json輸出時,會將subdomainOffset,proxy,env這幾個值,好像并沒有什么用..
- app.use:很清楚,就是往middleware里面push而已,如果沒有打開試驗選項experimental,就只能傳入generator
- app.callback:處理中間件并且進行返回的地方
- app.createContext:私有方法,創建初始化的上下文
- app.onerror:私有方法,默認的錯誤處理
- respond:用來幫助返回的方法
主要的執行就是app.callback了,這里對代碼進行注釋
app.callback = function(){
//如果打開了實驗的接口,就可以使用es7的async,不然使用koa-compose來進行包裝,然后再傳給co
var fn = this.experimental
? compose_es7(this.middleware)
: co.wrap(compose(this.middleware));
var self = this;
//看是否注冊了錯誤事件,沒有的話使用自身的onerror
if (!this.listeners('error').length) this.on('error', this.onerror);
return function(req, res){
res.statusCode = 404;
//在這里對上下文,以及req和res進行了包裝
var ctx = self.createContext(req, res);
//注冊一下這個是為了處理error
//當error發生時,調用了context的onerror,里面emit了app的onerror,這樣上面的this.onerror就可以被觸發了。
onFinished(res, ctx.onerror);
//然后執行那個co包裝過的promise,成功的話執行respond方法,失敗了執行this.onerror方法
fn.call(ctx).then(function () {
respond.call(ctx);
}).catch(ctx.onerror);
}
};
respond方法也挺有意思,注釋一下
function respond() {
if (false === this.respond) return;
var res = this.res;
//如果頭部已經發送,或者不能寫了,跳過
if (res.headersSent || !this.writable) return;
var body = this.body;
var code = this.status;
//如果是一些不需要返回體的code,比如204:沒有內容,比如304:未修改),直接返回
if (statuses.empty[code]) {
this.body = null;
return res.end();
}
//如果是head請求,返回一個length
//head請求允許請求某個資源的響應頭,而不要真正的資源本身
if ('HEAD' == this.method) {
if (isJSON(body)) this.length = Buffer.byteLength(JSON.stringify(body));
return res.end();
}
//如果就是沒有內容,就設置一個默認的返回
if (null == body) {
this.type = 'text';
body = this.message || String(code);
this.length = Buffer.byteLength(body);
return res.end(body);
}
//對不同的響應體進行判斷并且返回
if (Buffer.isBuffer(body)) return res.end(body);
if ('string' == typeof body) return res.end(body);
if (body instanceof Stream) return body.pipe(res);
body = JSON.stringify(body);
this.length = Buffer.byteLength(body);
res.end(body);
}
本文借鑒了
先寫到這里,順便給個github的傳送門,喜歡的朋友star一下啊,自己平時遇到的問題以及一下學習的經歷及所得都會在github上進行記錄~