摘要:載入了框架,我們來看源代碼中的。函數(shù)函數(shù)代碼如下代碼的開始定義了一個(gè)函數(shù),函數(shù)有形參,,為回調(diào)函數(shù)。相應(yīng)的,等同于繼承,從而讓有了事件處理的能力。
此為裁剪過的筆記版本。
原文在此:https://segmentfault.com/a/11...
原文在此: https://cnodejs.org/topic/574...
感謝@YiQi ,@leijianning 帶來的好文章。我稍作修改和合并,只是為了更加清晰一點(diǎn)點(diǎn)。
基于的版本tags:4.4.2。
把express代碼跑起來從一個(gè)官方示例開始:
var express = require("express"); var app = express(); app.get("/", function(req, res){ res.send("Hello World"); }); app.listen(3000);
代碼運(yùn)行后,訪問localhost:3000顯示Hello World。
逐行分析首先第一行,典型的Node.js模塊載入代碼。載入了express框架,我們來看express源代碼中的index.js。
module.exports = require("./lib/express");
只是簡(jiǎn)單的導(dǎo)入了./lib/express.js,所以繼續(xù)深挖看此代碼。
exports = module.exports = createApplication;
從這里我們可以看出,實(shí)例程序的第一行導(dǎo)入了函數(shù)createApplication函數(shù)。第二行則是運(yùn)行了這個(gè)函數(shù),然后返回值賦給了app。
函數(shù)createApplication函數(shù)createApplication代碼如下
var EventEmitter = require("events").EventEmitter; var mixin = require("utils-merge"); var proto = require("./application"); var req = require("./request"); var res = require("./response"); function createApplication() { var app = function(req, res, next) { app.handle(req, res, next); }; mixin(app, proto); mixin(app, EventEmitter.prototype); app.request = { __proto__: req, app: app }; app.response = { __proto__: res, app: app }; app.init(); return app; }
代碼的開始定義了一個(gè)函數(shù),函數(shù)有形參req,res,next為回調(diào)函數(shù)。函數(shù)體只有一條語句,執(zhí)行app.handle,此函數(shù)在application.js文件中定義,此處是通過mixin導(dǎo)入,它的作用就是將每對(duì)[req,res]進(jìn)行逐級(jí)分發(fā),作用在每個(gè)定義好的路由及中間件上,直到最后完成。接下來會(huì)對(duì)此函數(shù)分析。
然后來看看中間的兩行:
mixin(app, proto); mixin(app, EventEmitter.prototype);
函數(shù)mixin,從功能上來說,就是其實(shí)就是讓app拷貝proto的所有屬性,等同于app繼承自proto。是的,JavaScript這樣的動(dòng)態(tài)語言,可以動(dòng)態(tài)的指定繼承的基礎(chǔ)類。proto在頭部的require處載入的是./lib/application.js文件,其中定義了大部分express的public api,如app.set,app.get,app.use等。相應(yīng)的,mixin(app, EventEmitter.prototype)等同于繼承EventEmitter,從而讓app有了事件處理的能力。
想要具體了解mixin的同學(xué),可以看到,此函數(shù)為在頭部的require處載入的utils-merge模塊,它的代碼如下
exports = module.exports = function(a, b){ if (a && b) { for (var key in b) { a[key] = b[key]; } } return a; };
再來看接下來的兩行:
app.request = { __proto__: req, app: app }; app.response = { __proto__: res, app: app };
這里定義了app的request和response對(duì)象,使用了對(duì)象的字面量表示法,使其分別繼承自req(頂部導(dǎo)入的request.js)和res(頂部導(dǎo)入的response.js),并反向引用了app自身。
比如此官方實(shí)例中調(diào)用了res.send,此方法就在response.js內(nèi)定義,因?yàn)橹付薬pp.response的原型為response.js,就等于res也有了response.js的全部屬性和方法,自然也就有了send方法。
接下來是app.init();。顯然,作用是初始化,做哪些工作呢?
app.init = function(){ this.cache = {}; this.settings = {}; this.engines = {}; this.defaultConfiguration(); };
設(shè)定了cache對(duì)象(render的時(shí)候用到),各種setting的存儲(chǔ)對(duì)象,engines對(duì)象(模板引擎),最后進(jìn)行默認(rèn)的配置。
好了,createApplication函數(shù)就是這些
函數(shù)get實(shí)例程序第三行中調(diào)用了從app.get()方法。才函數(shù)是動(dòng)態(tài)定義的。定義在此:
methods.forEach(function(method){ app[method] = function(path){ if ("get" == method && 1 == arguments.length) return this.set(path); this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, [].slice.call(arguments, 1)); return this; }; });
methods在頂部模塊引入中定義,其實(shí)是一個(gè)包含各個(gè)HTTP請(qǐng)求方法的數(shù)組,代碼在此https://github.com/jshttp/met... 。數(shù)組內(nèi)包括get,put等元素。
而且get方法是被"重載"的,即當(dāng)app.get();的參數(shù)只有一個(gè)時(shí)候,執(zhí)行的是獲取變量的功能,否則,執(zhí)行route組件中的route.get方法,將該路由和回調(diào)函數(shù)(即第二個(gè)參數(shù))存儲(chǔ)進(jìn)一個(gè)棧中(后續(xù)會(huì)進(jìn)一步分析)?;氐皆瓉淼膯栴},在這里,關(guān)鍵是看中間的
this.lazyrouter();
我們看它的具體代碼
app.lazyrouter = function() { if (!this._router) { this._router = new Router({ caseSensitive: this.enabled("case sensitive routing"), strict: this.enabled("strict routing") }); this._router.use(query()); this._router.use(middleware.init(this)); } };
此代碼在第一次執(zhí)行時(shí),如果this._route沒有定義的話,就定義它,并添加基本的路由。
注意最后一句用到了middleware模塊的init方法,繼續(xù)上代碼:
exports.init = function(app){ return function expressInit(req, res, next){ if (app.enabled("x-powered-by")) res.setHeader("X-Powered-By", "Express"); req.res = res; res.req = req; req.next = next; req.__proto__ = app.request; res.__proto__ = app.response; res.locals = res.locals || Object.create(null); next(); }; };
expressInit函數(shù)是一個(gè)中間件,可以給req設(shè)置X-Powered-By的值,也會(huì)初始化request和response,通過設(shè)置屬性__proto__,把a(bǔ)pp.request和app.respone繼承到request.js和response.js上。
函數(shù)listen最開頭的官方示例中還有最后一句app.listen(3000),實(shí)現(xiàn)代碼如下:
app.listen = function(){ var server = http.createServer(this); return server.listen.apply(server, arguments); };
實(shí)際上是調(diào)用了Node.js原生的http模塊的CreatServer方法,代碼:
http.createServer(this);
中的this,就是app,也就是createApplication返回的函數(shù),這個(gè)函數(shù)指向到app.handle(),因此,app.handle()就是所有請(qǐng)求的主入口。
路由模塊出場(chǎng),談及Router,Route的關(guān)系重新再看this.lazyrouter(),從名字來看,好像是懶加載router,那我們看看源碼:
app.lazyrouter = function lazyrouter() { if (!this._router) { this._router = new Router({ caseSensitive: this.enabled("case sensitive routing"), strict: this.enabled("strict routing") }); this._router.use(query(this.get("query parser fn"))); this._router.use(middleware.init(this)); } };
果然是,如果_router不存在,就new一個(gè)Router出來,而這個(gè)Router就是我們剛才在目錄結(jié)構(gòu)中看到的router目錄,也就是今天的主角Router模塊。
Router.route繼續(xù)上邊的代碼,加載完_router之后,執(zhí)行了this._router.route(path)這樣一行代碼,那這行代碼做了什么呢?我們?cè)趓outer目錄下的index.js中找到了它的實(shí)現(xiàn):
proto.route = function route(path) { var route = new Route(path); var layer = new Layer(path, { sensitive: this.caseSensitive, strict: this.strict, end: true }, route.dispatch.bind(route)); layer.route = route; this.stack.push(layer); return route; };
我們可以看到,這里new了一個(gè)Route對(duì)象,并且new了一個(gè)Layer對(duì)象,然后將Route對(duì)象賦值給layer.route,最后將這個(gè)Layer添加到stack數(shù)組中。那這個(gè)Route又是什么呢,它和Router模塊有什么關(guān)系呢,我來說下我的理解:
Route模塊對(duì)應(yīng)的是route.js,主要是來處理路由信息的,每條路由都會(huì)生成一個(gè)Route實(shí)例。
Router模塊對(duì)應(yīng)的是index.js,Router是一個(gè)route的集合,在Router模塊下可以定義多個(gè)route
每個(gè)express創(chuàng)建的實(shí)例都會(huì)懶加載一個(gè)_router來進(jìn)行路由處理,這個(gè)_router就是一個(gè)Router類型。
這就是Route和Router的關(guān)系。
好了,我們接著看函數(shù)get()的代碼,拿到route對(duì)象之后,通過apply的方式調(diào)用了route的對(duì)應(yīng)method函數(shù),假如我們現(xiàn)在使用的是get函數(shù),那現(xiàn)在method就等于get??吹竭@里大家就會(huì)發(fā)現(xiàn),express實(shí)例在處理路由的步驟是這樣的:
先創(chuàng)建一個(gè)Router對(duì)象
然后用Router對(duì)象和對(duì)應(yīng)的path來生成一個(gè)Route對(duì)象
最后由Route對(duì)象來處理具體的路由實(shí)現(xiàn)
route.method好了,那接下來我們繼續(xù)深入研究,看看route.method究竟做了什么,我們找到route.js文件,發(fā)現(xiàn)如下的代碼:
methods.forEach(function(method){ Route.prototype[method] = function(){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== "function") { var type = toString.call(handle); var msg = "Route." + method + "() requires callback functions but got a " + type; throw new Error(msg); } debug("%s %s", method, this.path); var layer = Layer("/", {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }; });
原來route和application運(yùn)用了同樣的技巧,通過循環(huán)methods來動(dòng)態(tài)添加method函數(shù)。
我們直接看函數(shù)內(nèi)部實(shí)現(xiàn),首先通過入?yún)@取到handles,這里的handles就是我們定義的路由中間件函數(shù),這里我們可以看到是一個(gè)數(shù)組,所以我們可以給一個(gè)路由添加多個(gè)中間件函數(shù)。常用的設(shè)置路由函數(shù)是這樣的,
route.get("/hello",function(){})
但是,為了方便,設(shè)置多個(gè)也是可以的:
route.get("/hello",function(){},function(){},function(){}))
因此,handles是一個(gè)數(shù)組。接下來循環(huán)handles,在每個(gè)循環(huán)中利用handle來創(chuàng)建一個(gè)Layer對(duì)象,然后將Layer對(duì)象push到stack中去,這個(gè)stack其實(shí)是Route內(nèi)部維護(hù)的一個(gè)數(shù)組,用來存放所有的Layer對(duì)象。那么,對(duì)象Layer是什么東西呢?
我們可以route對(duì)象設(shè)置Layer的代碼:
route.get("/hello",function(){})
我們可以app對(duì)象設(shè)置Layer的代碼:
app.get("/hello",function(){})
也就是說,在route層面,也可以如同app一樣的設(shè)置Layer,因此官方文檔中提到了,route被認(rèn)為是mini app,就是這樣來的。
Layer對(duì)象那我們繼續(xù)往下看,看看layer.js的源代碼:
function Layer(path, options, fn) { if (!(this instanceof Layer)) { return new Layer(path, options, fn); } debug("new %s", path); var opts = options || {}; this.handle = fn; this.name = fn.name || ""; this.params = undefined; this.path = undefined; this.regexp = pathRegexp(path, this.keys = [], opts); if (path === "/" && opts.end === false) { this.regexp.fast_slash = true; } }
上邊是Layer的構(gòu)造函數(shù),我們可以看到這里定義handle,params,path和regexp等幾個(gè)主要的屬性:
handle,它就是我們剛剛在route中創(chuàng)建Layer對(duì)象傳入的中間件函數(shù)
params其實(shí)就是req.params
path就是我們定義路由時(shí)傳入的path。
regexp進(jìn)行路由匹配的時(shí)候就是靠它來搞定的,而它的值是由pathRegexp得來的,其實(shí)這個(gè)pathRegexp對(duì)應(yīng)的是一個(gè)第三方模塊path-to-regexp,它的功能是將path轉(zhuǎn)換成regexp,具體用法大家可以自行查看。
Layer.match()看完屬性,我們?cè)賮砜纯碙ayer有什么方法:
Layer.prototype.match = function match(path) { if (path == null) { // no path, nothing matches this.params = undefined; this.path = undefined; return false; } if (this.regexp.fast_slash) { // fast path non-ending match for / (everything matches) this.params = {}; this.path = ""; return true; } var m = this.regexp.exec(path); if (!m) { this.params = undefined; this.path = undefined; return false; } // store values this.params = {}; this.path = m[0]; var keys = this.keys; var params = this.params; for (var i = 1; i < m.length; i++) { var key = keys[i - 1]; var prop = key.name; var val = decode_param(m[i]); if (val !== undefined || !(hasOwnProperty.call(params, prop))) { params[prop] = val; } } return true; };
match函數(shù)主要用來匹配path的,當(dāng)我們向express發(fā)送一個(gè)http請(qǐng)求時(shí),當(dāng)前請(qǐng)求對(duì)應(yīng)的是哪個(gè)路由,就是通過這個(gè)match函數(shù)來判斷的,如果path中帶有參數(shù),match還會(huì)把參數(shù)提取出來賦值給params,所以說match是整個(gè)路由中很重要的一點(diǎn)。
Layer.prototype.handle_error = function handle_error(error, req, res, next) { var fn = this.handle; if (fn.length !== 4) { // not a standard error handler return next(error); } try { fn(error, req, res, next); } catch (err) { next(err); } };
這個(gè)是錯(cuò)誤處理函數(shù),專門用來處理錯(cuò)誤的。
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle; if (fn.length > 3) { // not a standard request handler return next(); } try { fn(req, res, next); } catch (err) { next(err); }
};
從上邊的代碼我們可以看到調(diào)用了fn,而這個(gè)fn就是layer的handle屬性,就是我們定義路由時(shí)傳入的路由中間件,現(xiàn)在總結(jié)下,Layer到底是做什么的呢,它和Route之間的關(guān)系如何。說說我的理解:
可以發(fā)現(xiàn)Route和Layer是一對(duì)多的關(guān)系,每個(gè)Route都會(huì)維護(hù)一個(gè)Layer數(shù)組
每個(gè)Route代表一個(gè)路由
每個(gè)Layer對(duì)應(yīng)的是路由的每一個(gè)中間件函數(shù)。Layer存儲(chǔ)了每個(gè)路由的path和handle等信息,并且實(shí)現(xiàn)了match和handle的功能。
講完了Route和Layer的關(guān)系,我們?cè)賮砘仡^看看Router和Layer的關(guān)系,我們?cè)賮砜纯磇ndex.js中prop.route的代碼:
proto.route = function route(path) { var route = new Route(path); var layer = new Layer(path, { sensitive: this.caseSensitive, strict: this.strict, end: true }, route.dispatch.bind(route)); layer.route = route; this.stack.push(layer); return route; };
從代碼我們可以看出來Router每次添加一個(gè)route,都會(huì)把route包裝到layer中,并且將layer添加到自己的stack中。
那為什么要把route包裝到layer中呢,前邊我們已經(jīng)仔細(xì)研究了Layer模塊的代碼,我們發(fā)現(xiàn)Layer具有match和handle的功能,這樣我們就可以通過Layer的match來進(jìn)行route的匹配了。
route.dispatch()這里有一個(gè)關(guān)鍵點(diǎn)我們需要特別講解下,上邊的代碼中在創(chuàng)建Layer對(duì)象的時(shí)候傳入的handle函數(shù)為route.dispatch.bind(route),我們來看看route.js中的route.dispatch:
Route.prototype.dispatch = function dispatch(req, res, done) { var idx = 0; var stack = this.stack; if (stack.length === 0) { return done(); } var method = req.method.toLowerCase(); if (method === "head" && !this.methods["head"]) { method = "get"; } req.route = this; next(); function next(err) { if (err && err === "route") { return done(); } var layer = stack[idx++]; if (!layer) { return done(err); } if (layer.method && layer.method !== method) { return next(err); } if (err) { layer.handle_error(err, req, res, next); } else { layer.handle_request(req, res, next); } } };
我們發(fā)現(xiàn)dispatch中通過next()獲取stack中的每一個(gè)layer來執(zhí)行相應(yīng)的路由中間件,這樣就保證了我們定義在路由上的多個(gè)中間件函數(shù)被按照定義的順序依次執(zhí)行。到這里我們已經(jīng)知道了單個(gè)路由是被如何執(zhí)行的,那我們定義的多個(gè)路由之間又是如何被依次執(zhí)行的呢,現(xiàn)在我們來看看index.js中的handle函數(shù)(有刪減):
proto.handle = function handle(req, res, out) { // middleware and routes var stack = self.stack; next(); function next(err) { // find next matching layer var layer; var match; var route; while (match !== true && idx < stack.length) { layer = stack[idx++]; match = matchLayer(layer, path); route = layer.route; if (match !== true) { continue; } if (!route) { // process non-route handlers normally continue; } } // no match if (match !== true) { return done(layerError); } // this should be done for the layer self.process_params(layer, paramcalled, req, res, function (err) { if (err) { return next(layerError || err); } if (route) { return layer.handle_request(req, res, next); } trim_prefix(layer, layerError, layerPath, path); }); } };
此處代碼也是利用next(),來處理stack中的每一個(gè)Layer,這里的stack是Router.stack,stack中存貯了多個(gè)route對(duì)應(yīng)的layer
獲取到每個(gè)layer對(duì)象
用請(qǐng)求的path與layer進(jìn)行匹配,此處匹配用的是layer.match
3.1 如果能匹配到對(duì)應(yīng)的layer,則獲得layer.route
3.2 如果route不為空則執(zhí)行對(duì)應(yīng)的layer.handle_request()
3.3 如果route為空說明這個(gè)layer是通過use()添加的非路由中間件
需要特別說明的是,如果通過use()添加的非路由中間件沒有指定path,則會(huì)在layer.match中默認(rèn)返回true,也就是說,沒有指定path的非路由中間件會(huì)匹配所有的http請(qǐng)求。
總結(jié)我們接下來來重新梳理一下??纯磂xpress究竟是如何對(duì)http請(qǐng)求進(jìn)行路由的。
當(dāng)客戶端發(fā)送一個(gè)http請(qǐng)求后,會(huì)先進(jìn)入express實(shí)例對(duì)象對(duì)應(yīng)的router.handle函數(shù)中
router.handle函數(shù)會(huì)通過next()遍歷stack中的每一個(gè)layer進(jìn)行match
如果match返回true,則獲取layer.route,執(zhí)行route.dispatch函數(shù)
route.dispatch同樣是通過next()遍歷stack中的每一個(gè)layer,然后執(zhí)行l(wèi)ayer.handle_request,也就是調(diào)用中間件函數(shù)。
直到所有的中間件函數(shù)被執(zhí)行完畢,整個(gè)路由處理結(jié)束。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/93035.html
摘要:在后續(xù)的總結(jié)中,我會(huì)繼續(xù)分析,并準(zhǔn)備將一些值得分析的逐一解讀,也會(huì)涉及一些。從一個(gè)官方示例開始這是官方給出的一個(gè)簡(jiǎn)單程序,運(yùn)行后訪問顯示。第一行載入了框架,我們來看源代碼中的。代碼的開始定義了一個(gè)函數(shù),函數(shù)有形參,,為回調(diào)函數(shù)。 這兩天仔細(xì)看了看express的源碼,對(duì)其的整個(gè)實(shí)現(xiàn)有了較清晰的認(rèn)識(shí),所以想總結(jié)一下寫出來,如果有什么不對(duì)的地方,望指出。 這是第一篇,首先介紹一個(gè)最簡(jiǎn)單的...
摘要:就是每一個(gè)教程里面開始教學(xué)的事例,啟動(dòng)服務(wù)器的回調(diào)函數(shù)。,從入口開始分析源碼首先是把模塊的屬性全部進(jìn)里面去,在把事件的屬性全部進(jìn)里面去,這是為了給增加事件功能。 express4.X源碼解讀第一天 express4.X 跟3.X 有很大區(qū)別,4.X 去除了connect的依賴,3.X基于connect的中間件基本全部不能用,如果還有可以使用的,也是4.X重寫的。所以要想繼續(xù)使用這些熟悉...
摘要:本筆記共四篇源碼閱讀筆記源碼閱讀筆記源碼閱讀筆記服務(wù)器啟動(dòng)與請(qǐng)求處理源碼閱讀筆記對(duì)象起因前兩天閱讀了的基礎(chǔ),和中間件的基礎(chǔ)。的前端樂園原文鏈接源碼閱讀筆記服務(wù)器啟動(dòng)與請(qǐng)求處理 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務(wù)器の啟動(dòng)與請(qǐng)求處理Koa源碼閱讀筆記(4) -- ctx對(duì)象 起因 前兩天閱讀了K...
摘要:接下來我們深入函數(shù),看看它干了什么。在我們寫的代碼里,我們會(huì)手動(dòng)將元素掛載到樹上。到這里,我們已經(jīng)完成了元素掛載的全過程,接下來我們看一看更新的時(shí)候會(huì)發(fā)生什么。這部分應(yīng)該是負(fù)責(zé)的,我們要在組件的方法中調(diào)用。 etch簡(jiǎn)介 首先我們有必要介紹一下etch。 etch是atom團(tuán)隊(duì)下的開源項(xiàng)目,是一套非常簡(jiǎn)潔然而功能十分完善的virtualDOM機(jī)制。我在偶然的情況下接觸到了這個(gè)開源項(xiàng)...
摘要:如果此時(shí)我們不想把文件輸出到內(nèi)存里,可以通過修改的源代碼來實(shí)現(xiàn)。服務(wù)啟動(dòng)成功。。。根據(jù)請(qǐng)求的,拼接出 ? webpack-dev-middleware 是express的一個(gè)中間件,它的主要作用是以監(jiān)聽模式啟動(dòng)webpack,將webpack編譯后的文件輸出到內(nèi)存里,然后將內(nèi)存的文件輸出到epxress服務(wù)器上;下面通過一張圖片來看一下它的工作原理: showImg(https:...
閱讀 2877·2021-11-16 11:55
閱讀 2628·2021-09-29 09:34
閱讀 3446·2021-09-01 14:21
閱讀 3781·2019-08-29 12:36
閱讀 706·2019-08-26 10:55
閱讀 3998·2019-08-26 10:20
閱讀 1039·2019-08-23 18:19
閱讀 1206·2019-08-23 17:56