<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          三步法解析Express源碼

          共 7075字,需瀏覽 15分鐘

           ·

          2020-10-20 01:36


          在抖音上有幸看到一個程序員講述如何閱讀源代碼,主要分為三步:領(lǐng)悟思想、把握設(shè)計、體會細(xì)節(jié)。

          1. 領(lǐng)悟思想:只需體會作者設(shè)計框架的初衷和目的

          2. 把握設(shè)計:只需體會代碼的接口和抽象類以及宏觀的設(shè)計

          3. 體會細(xì)節(jié):是基于頂層的抽象接口設(shè)計,逐漸展開代碼的畫卷

          基于上述三步法,迫不及待的拿Express開刀了。本次源碼解析有什么不到位的地方各位讀者可以在下面留言,我們一起交流。

          一、領(lǐng)悟思想

          在Express中文網(wǎng)上,介紹Express是基于Node.js平臺,快速、開放、極簡的Web開發(fā)框架。在這句話里面可以得到解讀出以下幾點(diǎn)含義:

          1. Express是基于Node.js平臺,并且具備快速、極簡的特點(diǎn),說明其初衷就是為了通過擴(kuò)展Node的功能來提高開發(fā)效率。

          2. 開放的特點(diǎn)說明該框架不會對開發(fā)者過多的限制,可以自由的發(fā)揮想象進(jìn)行功能的擴(kuò)展。

          3. Express是Web開發(fā)框架,說明作者的定位就是為了更加方便的幫助我們處理HTTP的請求和響應(yīng)。

          二、把握設(shè)計

          理解了作者設(shè)計的思想,下面從源碼目錄、核心設(shè)計原理及抽象接口三個層面來對Express進(jìn)行整體的把握。

          2.1 源碼目錄

          如下所示是Express的源碼目錄,相比較來說還是比較簡單的。

          ├─application.js---創(chuàng)建Express應(yīng)用后可直接調(diào)用的api均在此處(核心)
          ├─express.js---入口文件,創(chuàng)建一個Express應(yīng)用
          ├─request.js---豐富了http中request實(shí)例上的功能
          ├─response.js---豐富了http中response實(shí)例上的功能
          ├─utils.js---工具函數(shù)
          ├─view.js---與模板渲染相關(guān)的內(nèi)容
          ├─router---與路由相關(guān)的內(nèi)容(核心)
          | ├─index.js
          | ├─layer.js
          | └route.js
          ├─middleware---與中間件相關(guān)的內(nèi)容
          | ├─init.js---會將新增加在request和response新增加的功能掛載到原始請求的request和response的原型上
          | └query.js---將請求url中的query部分添加到request的query屬性上

          2.2 抽象接口

          對源碼的目錄結(jié)構(gòu)有了一定了解,下面利用UML類圖對該系統(tǒng)各個模塊的依賴關(guān)系進(jìn)一步了解,為后續(xù)源碼分析打好基礎(chǔ)。

          2.3 設(shè)計原理

          這一部分是整個Express框架的核心,下圖是整個框架的運(yùn)行流程,一看是不是很懵逼,為了搞清楚這一部分,需要明確四個概念:Application、Router、Layer、Route。

          為了明確上述四個概念,先引入一段代碼

          const express = require('./express');
          const res = require('./response');
          const app = express();
          app.get('/test1', (req, res, next) => {
          console.log('one');
          next();
          }, (req, res) => {
          console.log('two');
          res.end('two');
          })
          app.get('/test2', (req, res, next) => {
          console.log('three');
          next();
          }, (req, res) => {
          console.log('four');
          res.end('four');
          })
          app.listen(3000);
          1. Application
            表示一個Express應(yīng)用,通過express()即可進(jìn)行創(chuàng)建。

          2. Router
            路由系統(tǒng),用于調(diào)度整個系統(tǒng)的運(yùn)行,在上述代碼中該路由系統(tǒng)包含app.get('/test1',……)和app.get('/test2',……)兩大部分

          3. Layer
            代表一層,對于上述代碼中app.get('/test1',……)和app.get('/test2',……)都可以成為一個Layer

          4. Route
            一個Layer中會有多個處理函數(shù)的情況,這多個處理函數(shù)構(gòu)成了Route,而Route中的每一個函數(shù)又成為Route中的Layer。對于上述代碼中,app.get('/test1',……)中的兩個函數(shù)構(gòu)成一個Route,每個函數(shù)又是Route中的Layer。

          了解完上述概念后,結(jié)合該幅圖,就大概能對整個流程有了直觀感受。首先啟動服務(wù),然后客戶端發(fā)起了http://localhost:3000/test2的請求,該過程應(yīng)該如何運(yùn)行呢?

          1. 啟動服務(wù)時會依次執(zhí)行程序,將該路由系統(tǒng)中的路徑、請求方法、處理函數(shù)進(jìn)行存儲(這些信息根據(jù)一定結(jié)構(gòu)存儲在Router、Layer和Route中)

          2. 對相應(yīng)的地址進(jìn)行監(jiān)聽,等待請求到達(dá)。

          3. 請求到達(dá),首先根據(jù)請求的path去從上到下進(jìn)行匹配,路徑匹配正確則進(jìn)入該Layer,否則跳出該Layer。

          4. 若匹配到該Layer,則進(jìn)行請求方式的匹配,若匹配方式匹配正確,則執(zhí)行該對應(yīng)Route中的函數(shù)。

          上述解釋的比較簡單,后續(xù)會在細(xì)節(jié)部分進(jìn)一步闡述。

          三、體會細(xì)節(jié)

          通過上述對Express設(shè)計原理的分析,下面將從兩個方面做進(jìn)一步的源碼解讀,下面流程圖是一個常見的Express項目的過程,首先會進(jìn)行app實(shí)例初始化、然后調(diào)用一系列中間件,最后建立監(jiān)聽。對于整個工程的運(yùn)行來說,主要分為兩個階段:初始化階段、請求處理階段,下面將以app.get()為例來闡述一下該核心細(xì)節(jié)。

          3.1 初始化階段

          下面利用app.get()這個路由來了解一下工程的初始化階段。

          1. 首先來看一下app.get()的內(nèi)容(源代碼中app.get()是通過遍歷methods的方式產(chǎn)生)

            app.get = function(path){
            // ……
            this.lazyrouter();

            var route = this._router.route(path);
            route.get.apply(route, slice.call(arguments, 1));
            return this;
            };
          2. 在app.lazyrouter()會完成router的實(shí)例化過程

            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));
            }
            };

            注意:該過程中其實(shí)是利用了單例模式,保證整個過程中獲取router實(shí)例的唯一性。

          3. 調(diào)用router.route()方法完成layer的實(shí)例化、處理及保存,并返回實(shí)例化后的route。(注意源碼中是proto.route)

            router.prototype.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;// 把route放到layer上

            this.stack.push(layer); // 把layer放到數(shù)組中
            return route;
            };
          4. 將該app.get()中的函數(shù)存儲到route的stack中。(注意源碼中也是通過遍歷method的方式將get掛載到route的prototype上)

            Route.prototype.get = function(){
            var handles = flatten(slice.call(arguments));

            for (var i = 0; i < handles.length; i++) {
            var handle = handles[i];
            // ……
            // 給route添加layer,這個層中需要存放方法名和handler
            var layer = Layer('/', {}, handle);
            layer.method = method;

            this.methods[method] = true;
            this.stack.push(layer);
            }

            return this;
            };

          注意:上述代碼均刪除了源碼中一些異常判斷邏輯,方便讀者看清整體框架。

          通過上述的分析,可以看出初始化階段主要做了兩件事情:

          1. 將路由處理方式(app.get()、app.post()……)、app.use()等劃分為路由系統(tǒng)中的一個Layer。

          2. 對于每一個層中的處理函數(shù)全部存儲至Route對象中,一個Route對象與一個Layer相互映射。

          3.2 請求處理階段

          當(dāng)服務(wù)啟動后即進(jìn)入監(jiān)聽狀態(tài),等待請求到達(dá)后進(jìn)行處理。

          1. app.listen()使服務(wù)進(jìn)入監(jiān)聽狀態(tài)(實(shí)質(zhì)上是調(diào)用了http模塊)

            app.listen = function listen() {
            var server = http.createServer(this);
            return server.listen.apply(server, arguments);
            };
          2. 當(dāng)連接建立會調(diào)用app實(shí)例,app實(shí)例中會立即執(zhí)行app.handle()函數(shù),app.handle()函數(shù)會立即調(diào)用路由系統(tǒng)的處理函數(shù)router.handle()

            app.handle = function handle(req, res, callback) {
            var router = this._router;
            // 如果路由系統(tǒng)中處理不了這個請求,就調(diào)用done方法
            var done = callback || finalhandler(req, res, {
            env: this.get('env'),
            onerror: logerror.bind(this)
            });
            //……
            router.handle(req, res, done);
            };
          3. router.handle()主要是根據(jù)路徑獲取是否有匹配的layer,當(dāng)匹配到之后則調(diào)用layer.prototype.handle_request()去執(zhí)行route中內(nèi)容的處理

            router.prototype.handle = function handle(req, res, out) {
            // 這個地方參數(shù)out就是done,當(dāng)所有都匹配不到,就從路由系統(tǒng)中出來,名字很形象
            var self = this;
            // ……
            var stack = self.stack;

            // ……

            next();

            function next(err) {
            // ……
            // get pathname of request
            var path = getPathname(req);

            // 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;
            // ……
            }

            // no match
            if (match !== true) {
            return done(layerError);
            }
            // ……

            // Capture one-time layer values
            req.params = self.mergeParams
            ? mergeParams(layer.params, parentParams)
            : layer.params;
            var layerPath = layer.path;

            // 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);
            });
            }

            function trim_prefix(layer, layerError, layerPath, path) {
            // ……

            if (layerError) {
            layer.handle_error(layerError, req, res, next);
            } else {
            layer.handle_request(req, res, next);
            }
            }
            };

          4. layer.handle_request()會調(diào)用route.dispatch()觸發(fā)route中內(nèi)容的執(zhí)行

            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);
            }
            };
          5. route中的通過判斷請求的方法和route中l(wèi)ayer的方法是否匹配,匹配的話則執(zhí)行相應(yīng)函數(shù),若所有route中的layer都不匹配,則調(diào)到外層的layer中繼續(xù)執(zhí)行。

            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();
            // ……

            next();
            // 此next方法是用戶調(diào)用的next,如果調(diào)用next會執(zhí)行內(nèi)層的next方法,如果沒有匹配到會調(diào)用外層的next方法
            function next(err) {
            // ……

            var layer = stack[idx++];
            if (!layer) {
            return done(err);
            }

            if (layer.method && layer.method !== method) {
            return next(err);
            }

            // 如果當(dāng)前route中的layer的方法匹配到了,執(zhí)行此layer上的handler
            if (err) {
            layer.handle_error(err, req, res, next);
            } else {
            layer.handle_request(req, res, next);
            }
            }
            };

          通過上述的分析,可以看出初始化階段主要做了兩件事情:

          1. 首先判斷l(xiāng)ayer中的path和請求的path是否一致,一致則會進(jìn)入route進(jìn)行處理,否則調(diào)到下一層layer

          2. 在route中會判斷route中的layer與請求方法是否一致,一致的話則函數(shù)執(zhí)行,否則不執(zhí)行,所有route中的layer執(zhí)行完后跳到下層的layer進(jìn)行執(zhí)行。

          瀏覽 28
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产一级二级黄色 | 人成视频在线观看一区二区 | 操高中生到高潮在线观看免费 | 大香蕉精品在线视频 | 午夜成人一区二区 |