摘要:函數(shù)表示增加一個(gè)路由,函數(shù)表示監(jiān)聽服務(wù)器。內(nèi)部創(chuàng)建一個(gè)叫做的類,并為該類添加兩個(gè)方法,和。建立類,并將原來(lái)內(nèi)的代碼移動(dòng)到類中。內(nèi)部在寫代碼之前,先梳理一下上面所有的概念之間的關(guān)系和。代表一個(gè)應(yīng)用程序。修改原有的函數(shù)。
express源碼閱讀
簡(jiǎn)介:這篇文章的主要目的是分析express的源碼,但是網(wǎng)絡(luò)上express的源碼評(píng)析已經(jīng)數(shù)不勝數(shù),所以本文章另辟蹊徑,準(zhǔn)備仿制一個(gè)express的輪子,當(dāng)然輪子的主體思路是閱讀express源碼所得。
源碼地址:expross
1. 搭建結(jié)構(gòu)有了想法,下一步就是搭建一個(gè)山寨的框架,萬(wàn)事開頭難,就從建立一個(gè)文件夾開始吧!
首先建立一個(gè)文件夾,叫做expross(你沒(méi)有看錯(cuò),山寨從名稱開始)。
expross | |-- application.js
接著創(chuàng)建application.js文件,文件的內(nèi)容就是官網(wǎng)的例子。
var http = require("http"); http.createServer(function(req, res) { res.writeHead(200, {"Content-Type": "text/plain"}); res.end("Hello World"); }).listen(3000);
一個(gè)簡(jiǎn)單的http服務(wù)就創(chuàng)建完成了,你可以在命令行中啟動(dòng)它,而expross框架的搭建就從這個(gè)文件出發(fā)。
1.1 第一劃 Application在實(shí)際開發(fā)過(guò)程中,web后臺(tái)框架的兩個(gè)核心點(diǎn)就是路由和模板。路由說(shuō)白了就是一組URL的管理,根據(jù)前端訪問(wèn)的URL執(zhí)行對(duì)應(yīng)的處理函數(shù)。怎樣管理一組URL和其對(duì)應(yīng)的執(zhí)行函數(shù)呢?首先想到的就是數(shù)組(其實(shí)我想到的是對(duì)象)。
創(chuàng)建一個(gè)名稱叫做router的數(shù)組對(duì)象。
var http = require("http"); //路由 var router = []; router.push({path: "*", fn: function(req, res) { res.writeHead(200, {"Content-Type": "text/plain"}); res.end("404"); }}, {path: "/", fn: function(req, res) { res.writeHead(200, {"Content-Type": "text/plain"}); res.end("Hello World"); }}); http.createServer(function(req, res) { //自動(dòng)匹配 for(var i=1,len=router.length; irouter數(shù)組用來(lái)管理所有的路由,數(shù)組的每個(gè)對(duì)象有兩個(gè)屬性組成,path表示路徑,fn表示路徑對(duì)應(yīng)的執(zhí)行函數(shù)。一切看起來(lái)都很不錯(cuò),但是這并不是一個(gè)框架,為了組成一個(gè)框架,并且貼近express,這里繼續(xù)對(duì)上面的代碼進(jìn)一步封裝。
首先定義一個(gè)類:Application
var Application = function() {}在這個(gè)類上定義二個(gè)函數(shù):
Application.prototype.use = function(path, cb) {}; Application.prototype.listen = function(port) {};把上面的實(shí)現(xiàn),封裝到這個(gè)類中。use 函數(shù)表示增加一個(gè)路由,listen 函數(shù)表示監(jiān)聽http服務(wù)器。
var http = require("http"); var Application = function() { this.router = [{ path: "*", fn: function(req, res) { res.writeHead(200, {"Content-Type": "text/plain"}); res.end("Cannot " + req.method + " " + req.url); } }]; }; Application.prototype.use = function(path, cb) { this.router.push({ path: path, fn: cb }); }; Application.prototype.listen = function(port) { var self = this; http.createServer(function(req, res) { for(var i=1,len=self.router.length; i可以像下面這樣啟動(dòng)它:
var app = new Application(); app.use("/", function(req, res) { res.writeHead(200, {"Content-Type": "text/plain"}); res.end("Hello World"); }); app.listen(3000);看樣子已經(jīng)和express的外觀很像了,為了更像,這里創(chuàng)建一個(gè)expross的文件,該文件用來(lái)實(shí)例化Application。代碼如下:
var Application = require("./application"); exports = module.exports = createApplication; function createApplication() { var app = new Application(); return app; }為了更專業(yè),調(diào)整目錄結(jié)構(gòu)如下:
-----expross | | | |-- index.js | | | |-- lib | | | |-- application.js | |-- expross.js | |---- test.js運(yùn)行node test.js,走起……
1.2 第二劃 Layer為了進(jìn)一步優(yōu)化代碼,這里抽象出一個(gè)概念:Layer。代表層的含義,每一層就是上面代碼中的router數(shù)組的一個(gè)項(xiàng)。
Layer含有兩個(gè)成員變量,分別是path和handle,path代表路由的路徑,handle代表路由的處理函數(shù)fn。
------------------------------------------------ | 0 | 1 | 2 | 3 | ------------------------------------------------ | Layer | Layer | Layer | Layer | | |- path | |- path | |- path | |- path | | |- handle| |- handle| |- handle| |- handle| ------------------------------------------------ router 內(nèi)部創(chuàng)建一個(gè)叫做layer的類,并為該類添加兩個(gè)方法,handle_request和match。match用來(lái)匹配請(qǐng)求路徑是否符合該層,handle_request用來(lái)執(zhí)行路徑對(duì)應(yīng)的處理函數(shù)。
function Layer(path, fn) { this.handle = fn; this.name = fn.name || ""; this.path = path; } //簡(jiǎn)單處理 Layer.prototype.handle_request = function (req, res) { var fn = this.handle; if(fn) { fn(req, res); } } //簡(jiǎn)單匹配 Layer.prototype.match = function (path) { if(path === this.path) { return true; } return false; } 因?yàn)閞outer數(shù)組中存放的將是Layer對(duì)象,所以修改Application.prototype.use代碼如下:
Application.prototype.use = function(path, cb) { this.router.push(new Layer(path, cb)); };當(dāng)然也不要忘記Application構(gòu)造函數(shù)的修改。
var Application = function() { this.router = [new Layer("*", function(req, res) { res.writeHead(200, {"Content-Type": "text/plain"}); res.end("Cannot " + req.method + " " + req.url); })]; };接著改變listen函數(shù),將其主要的處理邏輯抽取成handle函數(shù),用來(lái)匹配處理請(qǐng)求信息。這樣可以讓函數(shù)本身的語(yǔ)意更明確,并且遵守單一原則。
Application.prototype.handle = function(req, res) { var self = this; for(var i=0,len=self.router.length; ilisten函數(shù)變得簡(jiǎn)單明了。
Application.prototype.listen = function(port) { var self = this; http.createServer(function(req, res) { self.handle(req, res); }).listen(port); };運(yùn)行node test.js,走起……
1.3 第三劃 router在Application類中,成員變量router負(fù)責(zé)存儲(chǔ)應(yīng)用程序的所有路由和其處理函數(shù),既然存在這樣一個(gè)對(duì)象,為何不將其封裝成一個(gè)Router類,這個(gè)類負(fù)責(zé)管理所有的路由,這樣職責(zé)更加清晰,語(yǔ)意更利于理解。
so,這里抽象出另一個(gè)概念:Router,代表一個(gè)路由組件,包含若干層的信息。
建立Router類,并將原來(lái)Application內(nèi)的代碼移動(dòng)到Router類中。
var Router = function() { this.stack = [new Layer("*", function(req, res) { res.writeHead(200, {"Content-Type": "text/plain"}); res.end("Cannot " + req.method + " " + req.url); })]; }; Router.prototype.handle = function(req, res) { var self = this; for(var i=0,len=self.stack.length; i為了利于管理,現(xiàn)將路由相關(guān)的文件放到一個(gè)目錄中,命名為router。將Router類文件命名為index.js保存到router文件夾內(nèi),并將原來(lái)的layer.js移動(dòng)到該文件夾?,F(xiàn)目錄結(jié)構(gòu)如下:
-----expross | | | |-- index.js | | | |-- lib | | | |-- router | | | | | |-- index.js | | |-- layer.js | | | | | |-- application.js | |-- expross.js | |---- test.js修改原有application.js文件,將代碼原有router的數(shù)組移除,新增加_router對(duì)象,該對(duì)象是Router類的一個(gè)實(shí)例。
var Application = function() { this._router = new Router(); }; Application.prototype.use = function(path, fn) { var router = this._router; return router.use(path, fn); }; Application.prototype.handle = function(req, res) { var router = this._router; router.handle(req, res); };到現(xiàn)在為止,整體的框架思路已經(jīng)非常的明確,一個(gè)應(yīng)用對(duì)象包括一個(gè)路由組件,一個(gè)路由組件包括n個(gè)層,每個(gè)層包含路徑和處理函數(shù)。每次請(qǐng)求就遍歷應(yīng)用程序指向的路由組件,通過(guò)層的成員函數(shù)match來(lái)進(jìn)行匹配識(shí)別URL訪問(wèn)的路徑,如果成功則調(diào)用層的成員函數(shù)handle_request進(jìn)行處理。
運(yùn)行node test.js,走起……
1.4 第四劃 route如果研究過(guò)路由相關(guān)的知識(shí)就會(huì)發(fā)現(xiàn),路由其實(shí)是由三個(gè)參數(shù)構(gòu)成的:請(qǐng)求的URI、HTTP請(qǐng)求方法和路由處理函數(shù)。之前的代碼只處理了其中兩種,對(duì)于HTTP請(qǐng)求方法這個(gè)參數(shù)卻刻意忽略,現(xiàn)在是時(shí)候把它加進(jìn)來(lái)了。
按照上面的結(jié)構(gòu),如果加入請(qǐng)求方法參數(shù),肯定會(huì)加入到Layer里面。但是再加入之前,需要仔細(xì)分析一下路由的常見方式:
GET /pages GET /pages/1 POST /page PUT /pages/1 DELETE /pages/1HTTP的請(qǐng)求方法有很多,上面的路由列表是一組常見的路由樣式,遵循REST原則。分析一下會(huì)發(fā)現(xiàn)大部分的請(qǐng)求路徑其實(shí)是相似或者是一致的,如果將每個(gè)路由都建立一個(gè)Layer添加到Router里面,從效率或者語(yǔ)意上都稍微有些不符,因?yàn)樗麄兪且唤MURL,負(fù)責(zé)管理page相關(guān)信息的URL,能否把這樣類似訪問(wèn)路徑相同而請(qǐng)求方法不同的路由劃分到一個(gè)組里面呢?
答案是可以行的,這就需要再次引入一個(gè)概念:route,專門來(lái)管理具體的路由信息。
------------------------------------------------ | 0 | 1 | 2 | 3 | ------------------------------------------------ | item | item | item | item | | |- method| |- method| |- method| |- method| | |- handle| |- handle| |- handle| |- handle| ------------------------------------------------ route 內(nèi)部在寫代碼之前,先梳理一下上面所有的概念之間的關(guān)系:application、expross、router、route和layer。
-------------- | Application | --------------------------------------------------------- | | | ----- ----------- | 0 | 1 | 2 | 3 | ... | | |-router | ----> | | Layer | --------------------------------------------------------- -------------- | 0 | |-path | | item | item | item | item | | application | | |-route | ----> | |- method| |- method| |- method| |- method| ... | |-----|-----------| | |- handle| |- handle| |- handle| |- handle| | | | Layer | --------------------------------------------------------- | 1 | |-path | route | | |-route | |-----|-----------| | | Layer | | 2 | |-path | | | |-route | |-----|-----------| | ... | ... | ----- ----------- routerapplication代表一個(gè)應(yīng)用程序。expross是一個(gè)工廠類負(fù)責(zé)創(chuàng)建application對(duì)象。router是一個(gè)路由組件,負(fù)責(zé)整個(gè)應(yīng)用程序的路由系統(tǒng)。route是路由組件內(nèi)部的一部分,負(fù)責(zé)存儲(chǔ)真正的路由信息,內(nèi)部的每一項(xiàng)都代表一個(gè)路由處理函數(shù)。router內(nèi)部的每一項(xiàng)都是一個(gè)layer對(duì)象,layer內(nèi)部保存一個(gè)route和其代表的URI。
如果一個(gè)請(qǐng)求來(lái)臨,會(huì)現(xiàn)從頭至尾的掃描router內(nèi)部的每一層,而處理每層的時(shí)候會(huì)先對(duì)比URI,匹配掃描route的每一項(xiàng),匹配成功則返回具體的信息,沒(méi)有任何匹配則返回未找到。
創(chuàng)建Route類,定義三個(gè)成員變量和三個(gè)方法。path代表該route所對(duì)應(yīng)的URI,stack代表上圖中route內(nèi)部item所在的數(shù)組,methods用來(lái)快速判斷該route中是是否存在某種HTTP請(qǐng)求方法。
var Route = function(path) { this.path = path; this.stack = []; this.methods = {}; }; Route.prototype._handles_method = function(method) { var name = method.toLowerCase(); return Boolean(this.methods[name]); }; Route.prototype.get = function(fn) { var layer = new Layer("/", fn); layer.method = "get"; this.methods["get"] = true; this.stack.push(layer); return this; }; Route.prototype.dispatch = function(req, res) { var self = this, method = req.method.toLowerCase(); for(var i=0,len=self.stack.length; i在上面的代碼中,并沒(méi)有定義前面結(jié)構(gòu)圖中的item對(duì)象,而是使用了Layer對(duì)象進(jìn)行替代,主要是為了方便快捷,從另一種角度看,其實(shí)二者是存在很多共同點(diǎn)的。另外,為了利于理解,代碼中只實(shí)現(xiàn)了GET方法,其他方法的代碼實(shí)現(xiàn)是類似的。
既然有了Route類,接下來(lái)就改修改原有的Router類,將route集成其中。
Router.prototype.handle = function(req, res) { var self = this, method = req.method; for(var i=0,len=self.stack.length; i代碼中,暫時(shí)去除use方法,創(chuàng)建get方法用來(lái)添加請(qǐng)求處理函數(shù),route方法是為了返回一個(gè)新的Route對(duì)象,并將改層加入到router內(nèi)部。
最后修改Application類中的函數(shù),去除use方法,加入get方法進(jìn)行測(cè)試。
Application.prototype.get = function(path, fn) { var router = this._router; return router.get(path, fn); }; Application.prototype.route = function (path) { return this._router.route(path); };測(cè)試代碼如下:
var expross = require("./expross"); var app = expross(); app.get("/", function(req, res) { res.writeHead(200, {"Content-Type": "text/plain"}); res.end("Hello World"); }); app.route("/book") .get(function(req, res) { res.writeHead(200, {"Content-Type": "text/plain"}); res.end("Get a random book"); }); app.listen(3000);運(yùn)行node test.js,走起……
1.5 第五劃 nextnext 主要負(fù)責(zé)流程控制。在實(shí)際的代碼中,有很多種情況都需要進(jìn)行權(quán)限控制,例如:
var expross = require("./expross"); var app = expross(); app.get("/", function(req, res) { res.writeHead(200, {"Content-Type": "text/plain"}); res.end("first"); }); app.get("/", function(req, res) { res.writeHead(200, {"Content-Type": "text/plain"}); res.end("second"); }); app.listen(3000);上面的代碼如果執(zhí)行會(huì)發(fā)現(xiàn)永遠(yuǎn)都返回first,但是有的時(shí)候會(huì)根據(jù)前臺(tái)傳來(lái)的參數(shù)動(dòng)態(tài)判斷是否執(zhí)行接下來(lái)的路由,怎樣才能跳過(guò)first進(jìn)入second?express引入了next的概念。
跳轉(zhuǎn)到任意layer,成本是比較高的,大多數(shù)的情況下并不需要。在express中,next跳轉(zhuǎn)函數(shù),有兩種類型:
跳轉(zhuǎn)到下一個(gè)處理函數(shù)。執(zhí)行 next()。
跳轉(zhuǎn)到下一組route。執(zhí)行 next("route")。
要想使用next的功能,需要在代碼書寫的時(shí)候加入該參數(shù):
var expross = require("./expross"); var app = expross(); app.get("/", function(req, res, next) { console.log("first"); next(); }); app.get("/", function(req, res) { res.writeHead(200, {"Content-Type": "text/plain"}); res.end("second"); }); app.listen(3000);而該功能的實(shí)現(xiàn)也非常簡(jiǎn)單,主要是在調(diào)用處理函數(shù)的時(shí)候,除了需要傳入req、res之外,再傳一個(gè)流程控制函數(shù)next。
Router.prototype.handle = function(req, res) { var self = this, method = req.method, i = 1, len = self.stack.length, stack; function next() { if(i >= len) { return self.stack[0].handle_request(req, res); } stack = self.stack[i++]; if(stack.match(req.url) && stack.route && stack.route._handles_method(method)) { return stack.handle_request(req, res, next); } else { next(); } } next(); };修改原有Router的handle函數(shù)。因?yàn)橐刂屏鞒?,所以for循環(huán)并不是很合適,可以更換為while循環(huán),或者干脆使用類似遞歸的手法。
代碼中定義一個(gè)next函數(shù),然后執(zhí)行next函數(shù)進(jìn)行自啟動(dòng)。next內(nèi)部和之前的操作類似,主要是執(zhí)行handle_request函數(shù)進(jìn)行處理,不同之處是調(diào)用該函數(shù)的時(shí)候,將next本身當(dāng)做參數(shù)傳入,這樣可以在內(nèi)部執(zhí)行該函數(shù)進(jìn)行下一個(gè)處理,類似給handle_request賦予for循環(huán)中++的能力。
按照相同的方式,修改Route的dispatch函數(shù)。
Route.prototype.dispatch = function(req, res, done) { var self = this, method = req.method.toLowerCase(), i = 0, len = self.stack.length, stack; function next(gt) { if(gt === "route") { return done(); } if(i >= len) { return done(); } stack = self.stack[i++]; if(method === stack.method) { return stack.handle_request(req, res, next); } else { next(); } } next(); };代碼思路基本和上面的相同,唯一的差別就是增加route判斷,提供跳過(guò)當(dāng)前整組處理函數(shù)的能力。
Layer.prototype.handle_request = function (req, res, next) { var fn = this.handle; if(fn) { fn(req, res, next); } } Router.prototype.route = function route(path) { var route = new Route(path); var layer = new Layer(path, function(req, res, next) { route.dispatch(req, res, next) }); layer.route = route; this.stack.push(layer); return route; };最后不要忘記修改Layer的handle_request函數(shù)和Router的route函數(shù)。
1.6 后記該小結(jié)基本結(jié)束,當(dāng)然如果要繼續(xù)還可以寫很多內(nèi)容,包括錯(cuò)誤處理、函數(shù)重載、高階函數(shù)(生成各種HTTP函數(shù)),以及各種神奇的用法,如繼承、緩存、復(fù)用等等。
但是我覺(jué)得搭建結(jié)構(gòu)這一結(jié)已經(jīng)將express的基本結(jié)構(gòu)捋清了,如果重頭到尾的走下來(lái),再去讀框架的源碼應(yīng)該是沒(méi)有問(wèn)題的。
接下來(lái)繼續(xù)山寨express 的其他部分。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/79822.html
摘要:一點(diǎn)閱讀器源自追書神器,免費(fèi)使用目前已初步開發(fā)完成項(xiàng)目地址歡迎,,推薦一個(gè)之前用文章類閱讀寫的一點(diǎn)閱讀微信小程序一點(diǎn)文章已上線,可以再微信搜索一點(diǎn)文章體驗(yàn)在線體驗(yàn)地址點(diǎn)擊這里體驗(yàn)服務(wù)器太,渲染慢部分效果截圖一點(diǎn)閱讀器優(yōu)勢(shì)一點(diǎn)閱讀器追書神 vue-reader 一點(diǎn)閱讀器!API源自追書神器,免費(fèi)使用!目前已初步開發(fā)完成! Github項(xiàng)目地址:https://github.com/An...
摘要:一點(diǎn)閱讀器源自追書神器,免費(fèi)使用目前已初步開發(fā)完成項(xiàng)目地址歡迎,,推薦一個(gè)之前用文章類閱讀寫的一點(diǎn)閱讀微信小程序一點(diǎn)文章已上線,可以再微信搜索一點(diǎn)文章體驗(yàn)在線體驗(yàn)地址點(diǎn)擊這里體驗(yàn)服務(wù)器太,渲染慢部分效果截圖一點(diǎn)閱讀器優(yōu)勢(shì)一點(diǎn)閱讀器追書神 vue-reader 一點(diǎn)閱讀器!API源自追書神器,免費(fèi)使用!目前已初步開發(fā)完成! Github項(xiàng)目地址:https://github.com/An...
摘要:一點(diǎn)閱讀器源自追書神器,免費(fèi)使用目前已初步開發(fā)完成項(xiàng)目地址歡迎,,推薦一個(gè)之前用文章類閱讀寫的一點(diǎn)閱讀微信小程序一點(diǎn)文章已上線,可以再微信搜索一點(diǎn)文章體驗(yàn)在線體驗(yàn)地址點(diǎn)擊這里體驗(yàn)服務(wù)器太,渲染慢部分效果截圖一點(diǎn)閱讀器優(yōu)勢(shì)一點(diǎn)閱讀器追書神 vue-reader 一點(diǎn)閱讀器!API源自追書神器,免費(fèi)使用!目前已初步開發(fā)完成! Github項(xiàng)目地址:https://github.com/An...
摘要:學(xué)習(xí)的源代碼的好處自然不少。閱讀源代碼可以幫你實(shí)現(xiàn)你的好奇心。本文會(huì)推薦一些的源代碼分析文章,可以幫助更快的,更加全方位的理解研讀之。 盡管有Hapi,Koa等有力的競(jìng)爭(zhēng)者,express.js依然是非常流行的nodejs web服務(wù)器框架,畢竟它早于2007年就已經(jīng)在開發(fā)了。 學(xué)習(xí)expressjs的源代碼的好處自然不少。 它可以幫你深刻理解HTTP協(xié)議,這個(gè)協(xié)議是做前端后端都必然需...
閱讀 3295·2021-10-13 09:39
閱讀 2045·2021-09-27 13:36
閱讀 3115·2021-09-22 16:02
閱讀 2628·2021-09-10 10:51
閱讀 1606·2019-08-29 17:15
閱讀 1560·2019-08-29 16:14
閱讀 3544·2019-08-26 11:55
閱讀 2580·2019-08-26 11:50