摘要:引言最近空閑時間讀了一下的源碼在閱讀的源碼的過程中,我的感受是代碼簡潔思路清晰不得不佩服大神的水平。調(diào)用的時候就跟有區(qū)別使用必須使用來調(diào)用除了上面的的構造函數(shù)外,還暴露了一些公用的,比如兩個常見的,一個是,一個是。
引言
最近空閑時間讀了一下Koa2的源碼;在閱讀Koa2(version 2.2.0)的源碼的過程中,我的感受是代碼簡潔、思路清晰(不得不佩服大神的水平)。
下面是我讀完之后的一些感受。
Koa基本組成Koa 是一個輕量級的、極富表現(xiàn)力的 http 框架。
一個web request會通過 Koa 的中間件棧,來動態(tài)完成 response 的處理。
Koa2 采用了 async 和 await 的語法來增強中間件的表現(xiàn)力。
Koa 不在內(nèi)核方法中綁定任何中間件,它僅僅提供了一個輕量優(yōu)雅的函數(shù)庫。
Koa源碼非常精簡,只有四個文件:
application.js:框架入口;負責管理中間件,以及處理請求
context.js:context對象的原型,代理request與response對象上的方法和屬性
request.js:request對象的原型,提供請求相關的方法和屬性
response.js:response對象的原型,提供響應相關的方法和屬性
application.js// application.js module.exports = class Application extends Emitter { constructor() { super(); this.proxy = false; // 是否信任 proxy header 參數(shù),默認為 false this.middleware = []; //保存通過app.use(middleware)注冊的中間件 this.subdomainOffset = 2; // 子域默認偏移量,默認為 2 this.env = process.env.NODE_ENV || "development"; // 環(huán)境參數(shù),默認為 NODE_ENV 或 ‘development’ this.context = Object.create(context); //context模塊,通過context.js創(chuàng)建 this.request = Object.create(request); //request模塊,通過request.js創(chuàng)建 this.response = Object.create(response); //response模塊,通過response.js創(chuàng)建 } // ... }
application.js 是 koa 的入口主要文件,暴露應用的 class, 這個 class 繼承自 EventEmitter ,這里可以看出跟 koa1.x 的不同,koa1.x 是用的是構造函數(shù)的方式,koa2 大量使用 es6 的語法。調(diào)用的時候就跟 koa1.x 有區(qū)別
var koa = require("koa"); // koa 1.x var app = koa(); // koa 2.x // 使用class必須使用new來調(diào)用 var app = new koa();
application.js除了上面的的構造函數(shù)外,還暴露了一些公用的api,比如兩個常見的,一個是listen,一個是use。
use函數(shù)// application.js 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函數(shù)做的事很簡單:注冊一個中間件fn,其實就是將fn放入middleware數(shù)組。
listen函數(shù)// application.js listen(...args) { debug("listen"); const server = http.createServer(this.callback()); return server.listen(...args); }
listen方法首先會通過this.callback方法來返回一個函數(shù)作為http.createServer的回調(diào)函數(shù),然后進行監(jiān)聽。我們已經(jīng)知道,http.createServer的回調(diào)函數(shù)接收兩個參數(shù):req和res,下面來看this.callback的實現(xiàn):
// application.js 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; }
首先,callback方法把所有middleware進行了組合,使用了koa-compose,我們來看一下koa-compose的代碼:
// koa-compose function compose (middleware) { // 傳入的middleware必須是一個數(shù)組 if (!Array.isArray(middleware)) throw new TypeError("Middleware stack must be an array!") // 傳入的middleware的每一個元素都必須是函數(shù) for (const fn of middleware) { if (typeof fn !== "function") throw new TypeError("Middleware must be composed of functions!") } return function (context, next) { 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] //下面兩行代碼是處理最后一個中間件還有next的情況的,其實就是直接resolve出來 if (i === middleware.length) fn = next if (!fn) return Promise.resolve() try { // 這里就是傳入next執(zhí)行中間件代碼了 return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } } }
可以看到koa-compose基本就是個dispatch函數(shù)的遞歸調(diào)用。其中最重要的就是下面這段代碼:
return Promise.resolve(fn(context, function next () { return dispatch(i + 1) }))
這段代碼等價于:
fn(context, function next () { return dispatch(i + 1) }) return Promise.resolve()
這里middlewareFunction的第二個參數(shù)(也就是next)是動態(tài)傳遞進去的信使,它會調(diào)取dispatch(index)執(zhí)行下一個的middleware。最后會返回一個Resolved(已完成)狀態(tài)的Promise對象。這個對象的作用我們稍后再說。
我們先暫時回到callback方法里面,前面說了它先對middleware進行了組合,生成了一個函數(shù)fn。
然后,callback方法返回http.createServer所需要的回調(diào)函數(shù)handleRequest。
handleRequest函數(shù),先把http code默認設置為404,接著利用createContext函數(shù)把node返回的req和res進行了封裝創(chuàng)建出context,
然后通過onFinished(res, onerror)監(jiān)聽http response,當請求結束時執(zhí)行回調(diào)。這里傳入的回調(diào)是context.onerror(err),即當錯誤發(fā)生時才執(zhí)行。
最后返回 fn(ctx).then(handleResponse).catch(onerror)的執(zhí)行結果,這里的fn函數(shù)就是就是組合所有middleware后生成的函數(shù),調(diào)用它執(zhí)行所有middleware后會返回前面提到的Resolved(已完成)狀態(tài)的Promise對象,之后執(zhí)行響應處理函數(shù)respond(ctx)(respond函數(shù)里面也主要是一些收尾工作,例如判斷http code為空如何輸出啦,http method是head如何輸出啦,body返回是流或json時如何輸出;代碼就不貼了,感興趣的小伙伴自己可以去看一下),當拋出異常時同樣使用 context.onerror(err)處理。
我們可以看看createContext函數(shù)
// application.js 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; }
createContext創(chuàng)建context的時候,還會同時創(chuàng)建request和response,通過下圖可以比較直觀地看到所有這些對象之間的關系。
圖中:
最左邊一列表示每個文件的導出對象
中間一列表示每個Koa應用及其維護的屬性
右邊兩列表示對應每個請求所維護的一些列對象
黑色的線表示實例化
紅色的線表示原型鏈
藍色的線表示屬性
通過上面的分析,我們已經(jīng)可以大概得知Koa處理請求的過程:當請求到來的時候,會通過 req 和 res 來創(chuàng)建一個 context (ctx) ,然后執(zhí)行中間件。
content.jscontent.js 主要的功能提供了對request和response對象的方法與屬性便捷訪問能力。
其中使用了node-delegates(有興趣的可以看一下源碼),將context.request與context.response上的方法與屬性代理到context上。
在源碼中,我們可以看到:
// context.js delegate(proto, "response") .method("attachment") // ... .access("status") // ... .getter("writable"); delegate(proto, "request") .method("acceptsLanguages") // ... .access("querystring") // ... .getter("ip");request.js
request.js 封裝了請求相關的屬性以及方法。通過 application.js 中的createContext方法,代理對應的 request 對象。
const request = context.request = Object.create(this.request); // ... context.req = request.req = response.req = req; // ... request.response = response;
request.req為原生的請求對象,在 request.js 中屬性的獲取都是通過 ths.req來獲取的(即 request.req)。
response.jsresponse.js 封裝了響應相關的屬性以及方法。與 request 相同,通過createContext方法代理對應的 response 對象。
const response = context.response = Object.create(this.response); // ... context.res = request.res = response.res = res; // ... response.request = request;結語
關于Koa2的源碼就先分析到這,希望對大家有所幫助。
如有不同的看法,歡迎交流!
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83586.html
摘要:本筆記共四篇源碼閱讀筆記源碼閱讀筆記源碼閱讀筆記服務器啟動與請求處理源碼閱讀筆記對象起因前兩天終于把自己一直想讀的源代碼讀了一遍。首先放上關鍵的源代碼在上一篇源碼閱讀筆記服務器啟動與請求處理中,我們已經(jīng)分析了的作用。 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務器の啟動與請求處理Koa源碼閱讀筆記(4...
摘要:于是抱著知其然也要知其所以然的想法,開始閱讀的源代碼。問題讀源代碼時,自然是帶著諸多問題的。源代碼如下在被處理完后,每當有新請求,便會調(diào)用,去處理請求。接下來會繼續(xù)寫一些閱讀筆記,因為看的源代碼確實是獲益匪淺。 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務器の啟動與請求處理Koa源碼閱讀筆記(4) -...
摘要:本筆記共四篇源碼閱讀筆記源碼閱讀筆記源碼閱讀筆記服務器啟動與請求處理源碼閱讀筆記對象起因前兩天閱讀了的基礎,和中間件的基礎。的前端樂園原文鏈接源碼閱讀筆記服務器啟動與請求處理 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務器の啟動與請求處理Koa源碼閱讀筆記(4) -- ctx對象 起因 前兩天閱讀了K...
摘要:本打算教一步步實現(xiàn),因為要解釋的太多了,所以先簡化成版本,從實現(xiàn)部分功能到閱讀源碼,希望能讓你好理解一些。 本打算教一步步實現(xiàn)koa-router,因為要解釋的太多了,所以先簡化成mini版本,從實現(xiàn)部分功能到閱讀源碼,希望能讓你好理解一些。希望你之前有讀過koa源碼,沒有的話,給你鏈接 最核心需求-路由匹配 router最重要的就是路由匹配,我們就從最核心的入手 router.get...
摘要:接上次挖的坑,對相關的源碼進行分析第一篇。和同為一批人進行開發(fā),與相比,顯得非常的迷你。在接收到一個請求后,會拿之前提到的與來創(chuàng)建本次請求所使用的上下文。以及如果沒有手動指定,會默認指定為。 接上次挖的坑,對koa2.x相關的源碼進行分析 第一篇。 不得不說,koa是一個很輕量、很優(yōu)雅的http框架,尤其是在2.x以后移除了co的引入,使其代碼變得更為清晰。 express和ko...
閱讀 1325·2023-04-26 00:10
閱讀 2428·2021-09-22 15:38
閱讀 3746·2021-09-22 15:13
閱讀 3503·2019-08-30 13:11
閱讀 646·2019-08-30 11:01
閱讀 3028·2019-08-29 14:20
閱讀 3208·2019-08-29 13:27
閱讀 1726·2019-08-29 11:33