国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

express.js 路由實(shí)現(xiàn)解讀

sugarmo / 586人閱讀

摘要:關(guān)于的實(shí)現(xiàn)源碼解讀,版本為。主要為路由部分。返回到的遍歷是通過(guò)尾遞歸的方式實(shí)現(xiàn)的,注意到被傳入的方法中,中處理事情最后向傳入,從而是否繼續(xù)遍歷取決于的實(shí)現(xiàn)是否調(diào)用的方法。

關(guān)于express.js的實(shí)現(xiàn)源碼解讀,版本為 4.14。主要為路由部分。

一個(gè)Web框架最重要的模塊是路由功能,該模塊的目標(biāo)是:能夠根據(jù)method、path匹配需要執(zhí)行的方法,并在定義的方法中提供有關(guān)請(qǐng)求和回應(yīng)的上下文。

模塊聲明

express中的路由模塊由Router完成,通過(guò)完成調(diào)用Router()得到一個(gè)router的實(shí)例,router既是一個(gè)對(duì)象,也是一個(gè)函數(shù),原因是實(shí)現(xiàn)了類似C++中的()重載方法,實(shí)質(zhì)指向了對(duì)象的handle方法。router的定義位于router/index.js中。

// router/index.js - line 42
var proto = module.exports = function(options) {
  var opts = options || {};

  // like operator() in C++
  function router(req, res, next) {
    router.handle(req, res, next);
  }
  //...
}
接口定義

router對(duì)外(即開(kāi)發(fā)者)提供了路由規(guī)則定義的接口:getput等對(duì)應(yīng)于HTTP method類別,函數(shù)簽名都是$method(path, fn(req, res), ...),接口的方法通過(guò)元編程動(dòng)態(tài)定義生成,可以這樣做的根本原因是方法名可以使用變量的值定義和調(diào)用,Java中的反射特性也可間接實(shí)現(xiàn)這點(diǎn),從而大量被應(yīng)用于Spring框架中。

// router/index.js - line 507
// create Router#VERB functions
// --> ["get", "post", "put", ...].foreach
methods.concat("all").forEach(function(method){    
  // so that we can write like "router.get(path, ...)"
  proto[method] = function(path){     
    // create a route for the routing rule we defined
    var route = this.route(path)      
    // map the corresponding handlers to the routing rule
    route[method].apply(route, slice.call(arguments, 1));
    return this;
  };
});
路由定義

在規(guī)則定義的接口中,路由規(guī)則的定義需要router保存路由規(guī)則的信息,最重要的是方法、路徑以及匹配時(shí)的調(diào)用方法(下稱handler),還有其他一些細(xì)節(jié)信息,這些信息(也可以看做是配置)的保存由Route對(duì)象完成,一個(gè)Route對(duì)象包含一個(gè)路由規(guī)則。Route對(duì)象通過(guò)router對(duì)象的route()方法進(jìn)行實(shí)例化和初始化后返回。

// router/index.js - line 491
proto.route = function route(path) {
  // create an instance of Route.
  var route = new Route(path);    
  // create an instance of Layer.
  var layer = new Layer(path, {
    sensitive: this.caseSensitive,
    strict: this.strict,
    end: true
  }, route.dispatch.bind(route));
  // layer has a reference to route.
  layer.route = route;
  // router has a list of layers which is created by "route()"
  this.stack.push(layer);
  return route;
};

Route的成員變量包括路徑path,以及HTTP method的路由配置接口集,這里和router中一樣的技巧提供了method所有類別的注冊(cè)函數(shù),此處無(wú)關(guān)緊要,只要route能夠得到路由配置的method值即可,將method作為一個(gè)參數(shù)傳入或者作為方法名調(diào)入都可以。

route()方法除了實(shí)例化一個(gè)Route外,還是實(shí)例化了一個(gè)Layer,這個(gè)的Layer相當(dāng)于是對(duì)應(yīng)Route的總的調(diào)度器,封裝了handlers的調(diào)用過(guò)程,先忽略。

真正將handlers傳入到route中發(fā)生在510行,也即上述route提供的注冊(cè)函數(shù)。由于一條路由設(shè)置中可以傳入多個(gè)handler,因此需要保存有關(guān)handler的列表,每一個(gè)handler由一個(gè)Layer對(duì)象進(jìn)行封裝,用以隱藏異常處理和handler調(diào)用鏈的細(xì)節(jié)。因此,route保存了一個(gè)Layer數(shù)組,按handler在參數(shù)中的聲明順序存放。這里體現(xiàn)Layer的第一個(gè)作用:封裝一條路由中的一個(gè)handler,并隱藏鏈?zhǔn)秸{(diào)用和異常處理等細(xì)節(jié)。

// router/route.js - line 190    
for (var i = 0; i < handles.length; i++) {
  var handle = handles[i];
  /* ... */
  // create a layer for each handler defined in a routing rule
  var layer = Layer("/", {}, handle);
  layer.method = method;

  this.methods[method] = true;
  // add the layer to the list.
  this.stack.push(layer);
}

返回到router中,最初實(shí)例化一個(gè)route的方法route中,還實(shí)例化了一個(gè)Layer,并且router保存了關(guān)于這些Layer的一個(gè)列表,由于我們可以在router定義多個(gè)路由規(guī)則,因此這是Layer的第二個(gè)作用:封裝一條路由中的一個(gè)總的handler,同樣也封裝了鏈?zhǔn)秸{(diào)用和異常處理等細(xì)節(jié)。這個(gè)總的handler即是遍歷調(diào)用route下的所有的handler的過(guò)程,相當(dāng)于一個(gè)總的Controller,每一個(gè)handler實(shí)際上是通過(guò)對(duì)應(yīng)的小的Layer來(lái)完成handler的調(diào)用。

route()方法可知,總的handler定義在routedispatch()方法中,該方法中,的確在遍歷route對(duì)象下的Layer數(shù)組(成員變量stack以及方法中的idx++)。

// router/index.js - line 491
proto.route = function route(path) {
  var route = new Route(path);    
  
  var layer = new Layer(path, {
    sensitive: this.caseSensitive,
    strict: this.strict,
    end: true
  // the "big" layer"s handler is the method "dispatch()" defined in route 
  }, route.dispatch.bind(route));

  layer.route = route;
  
  this.stack.push(layer);
  return route;
};
路由匹配

整理路由配置過(guò)程,思考每個(gè)路由配置信息的保存位置,有:

路由規(guī)則,一條對(duì)應(yīng)于一個(gè)Route中,并包裝一個(gè)Layer。

所有路由規(guī)則保存在Router中的stack數(shù)組中。

對(duì)于一個(gè)路由規(guī)則:

路徑在RouteLayer的成員變量path。

HTTP method在Route下每個(gè)handler對(duì)應(yīng)的Layer中的method成員變量,以及Route下的成員變量methods標(biāo)記了各個(gè)method是否有對(duì)應(yīng)的Layer

handler,每一個(gè)都包裝成一個(gè)Layer,所有的Layer保存在Route中的stack數(shù)組中。

有了如上信息,當(dāng)一個(gè)請(qǐng)求進(jìn)來(lái)需要尋找匹配的路由變得清晰。路由匹配過(guò)程定義在Routerhandle()方法中(router/index.js 135行)(回顧Router()方法實(shí)際上調(diào)用了handle()方法。)

handle()方法中,不關(guān)注解析url字符串等細(xì)節(jié)。從214行可發(fā)現(xiàn),不考慮異常情況,尋找匹配路由的過(guò)程其實(shí)是遍歷所有Layer的過(guò)程:

對(duì)于每個(gè)Layer,判斷req中的path是否與layer中的path匹配,若不匹配,繼續(xù)遍歷(path匹配過(guò)程后述);

若path匹配,則再取req中的method,通過(guò)routemethods成員變量判斷在該route下是否存在匹配的method,若不匹配,繼續(xù)遍歷。

若都匹配,則提取路徑參數(shù)(形如/:userId的通配符),調(diào)用關(guān)于路徑參數(shù)的handler。(通過(guò)router.param()設(shè)置的中間件)

調(diào)用路由配置route的handlers,這又是遍歷route下的小的Layer數(shù)組的過(guò)程。

決定是否返回1繼續(xù)遍歷。返回到stack的遍歷是通過(guò)尾遞歸的方式實(shí)現(xiàn)的,注意到next被傳入layer.handle_request的方法中,handle_request中處理事情最后向handler傳入next,從而是否繼續(xù)遍歷取決于handler的實(shí)現(xiàn)是否調(diào)用的next()方法。express的實(shí)現(xiàn)大量使用尾遞歸尾調(diào)用的模式,如process_params()方法。

簡(jiǎn)化版的路由匹配過(guò)程如下所示:

   // router/index.js - line 214
   proto.handle = function handle(req, res, out) {
    
     // middleware and routes
     var stack = self.stack;
     next();
     
     // for each layer in stack     
     function next(err) {  
       // idx is "index" of the stack
       if (idx >= stack.length) {            
         setImmediate(done, layerError);
         return;
       }
       // 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 the path ?
         match = matchLayer(layer, path);    
         route = layer.route;      
         if (match !== true) {
           continue;
         }
         
         // match the method ?
         var method = req.method;
         var has_method = route._handles_method(method);
         if (!has_method && /**/) {          
           match = false;
           continue;
         }           
       }

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

       // Capture one-time layer values
       // get path parameters.
       req.params = /*...*/;      
       
       // this should be done for the layer
       // invoke relative path parameters middleware, or handlers 
       self.process_params(layer, paramcalled, req, res, function (err) {
         if (route) {
           // invoke all handlers in a route
           // then invoke the "next" recursively
           return layer.handle_request(req, res, next);    
         }
       });
     }
   }
特殊路由

在路由匹配的分析中,省略了大量細(xì)節(jié)。

通過(guò)Router.use()配置的普通中間件:默認(rèn)情況下,相當(dāng)于配置了一個(gè)path"/"的路由,若參數(shù)提供了path,則相當(dāng)于配置了關(guān)于path的全method的路由。不同的是,handlers不使用route封裝,每一個(gè)handler直接使用一個(gè)大的Layer封裝后加入到Routerstack列表中,Layer中的routeundefined。原因是route參雜了有關(guān)http method有關(guān)的判斷,不適用于全局的中間件。

通過(guò)Router.use()配置的子路由, use()方法可以傳入另一個(gè)Router,從而實(shí)現(xiàn)路由模塊化的功能,處理實(shí)際上和普通中間件一樣,但此時(shí)傳入handler為Router,故調(diào)用Router()時(shí)即調(diào)用Routerhandle()方法,使用這樣的技巧實(shí)現(xiàn)了子路由的功能。

// router/index.js - line 276
// if it is a route, invoke the handlers in the route. 
if (route) {
    return layer.handle_request(req, res, next);
}
// if it is a middlewire (including router), invoke Router().
trim_prefix(layer, layerError, layerPath, path);

子路由功能還需要考慮父路徑和子路徑的提取。這在trim_prefix方法(router/index.js 212行),當(dāng)routeundefined時(shí)調(diào)用。直接將req的路徑減去父路由的path即可。為了能夠在子路由結(jié)束時(shí)返回到父路由,需要從子路徑恢復(fù)到帶有父路徑的路徑(信息在req中),結(jié)束時(shí)調(diào)用done(),done指向restore()方法,用于恢復(fù)req的屬性值。

// router/index.js - line 602
// restore obj props after function
function restore(fn, obj) {
  var props = new Array(arguments.length - 2);
  var vals = new Array(arguments.length - 2);
  // save vals.
  for (var i = 0; i < props.length; i++) {
    props[i] = arguments[i + 2];
    vals[i] = obj[props[i]];
  }

  return function(err){
    // restore vals when invoke "done()"
    for (var i = 0; i < props.length; i++) {
      obj[props[i]] = vals[i];
    }

    return fn.apply(this, arguments);
  };
}

通過(guò)app配置的應(yīng)用層路由和中間件,實(shí)際上由app里的成員變量router完成。默認(rèn)會(huì)載入initquery中間件(位于middleware/下),分別用于初始化字段操作以及將query解析放在req下。

通過(guò)Router.param()配置的參數(shù)路由,routerparams成員變量存放param映射到array[: handler]的map,調(diào)用路由前先調(diào)用匹配參數(shù)的中間件。

路徑參數(shù)

現(xiàn)在考慮帶有參數(shù)通配符的路徑配置和匹配過(guò)程。細(xì)節(jié)在Layer對(duì)象中。

路徑的匹配實(shí)際上是通過(guò)正則表達(dá)式的匹配完成的。將形如

"/foo/:bar"

轉(zhuǎn)為

/^/foo/(?:([^/]+?))/?$/i

正則的轉(zhuǎn)換由第三方模塊path-to-regex完成。解析后放在req.params中。

鏈?zhǔn)秸{(diào)用和異常處理

在handler的調(diào)用中都使用了尾調(diào)用尾遞歸模式設(shè)計(jì)(也可以理解為責(zé)任鏈模式、管道模式),包括:

Router中的handle方法調(diào)用匹配路由的總handler和中間件。

Router中的路徑參數(shù)路由(params)的調(diào)用過(guò)程。

Routedispatch方法處理所有的handlers和每一個(gè)Layer中的handle配合。

鏈?zhǔn)秸{(diào)用示意圖:

每一個(gè)節(jié)點(diǎn)都不了解自身的位置以及前后關(guān)系,調(diào)用鏈只能通過(guò)next()調(diào)用下一個(gè),若不調(diào)用則跳過(guò),并調(diào)用done()結(jié)束調(diào)用鏈。

調(diào)用鏈的一個(gè)環(huán)節(jié)仍可以是一個(gè)調(diào)用鏈,形成層次結(jié)構(gòu)(思考上述提到的大Layer和小Layer的關(guān)系

子調(diào)用鏈中的done()方法即是父調(diào)用鏈中的next()方法。

出現(xiàn)異常則:

若能夠接受繼續(xù)進(jìn)行,不中斷調(diào)用鏈,則可以繼續(xù)調(diào)用next方法,帶上err參數(shù),即next(err)。最終通過(guò)done(err)將異常返回給父調(diào)用鏈。

若不能接受,需要中斷,則調(diào)用done方法,,帶上err參數(shù),即done(err)

-- Fin --

進(jìn)階

視圖渲染模塊 render實(shí)現(xiàn),在applications.js 和 view.js 中。

對(duì)reqres的擴(kuò)展,header處理。

express從0.1、1.0、2.0、3.0、4.0的變化與改進(jìn)思路。

與koa框架的對(duì)比

感想

express的代碼其實(shí)不多。

路由部分其實(shí)寫得還是比較亂,大量關(guān)于細(xì)節(jié)的if、else判斷,仍是過(guò)程式的風(fēng)格,功能的實(shí)現(xiàn)并沒(méi)有特別的算法技巧,尤其是路由,直接是一個(gè)一個(gè)試的??蚣艿膶?shí)現(xiàn)并不都是所想的如此神奇或者高超。

一些不當(dāng)?shù)拇a風(fēng)格,如route.get等API中沒(méi)有在函數(shù)簽名中寫明handler參數(shù),直接通過(guò)argument數(shù)組取slice得到,而且為了實(shí)現(xiàn)同一函數(shù)名字的不同函數(shù)參數(shù)的重載,不得不在函數(shù)中判斷參數(shù)的類型再 if、 else 。(js不支持函數(shù)重載)

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/86505.html

相關(guān)文章

  • 解讀express 4.x源碼(1)

    摘要:在后續(xù)的總結(jié)中,我會(huì)繼續(xù)分析,并準(zhǔn)備將一些值得分析的逐一解讀,也會(huì)涉及一些。從一個(gè)官方示例開(kāi)始這是官方給出的一個(gè)簡(jiǎn)單程序,運(yùn)行后訪問(wèn)顯示。第一行載入了框架,我們來(lái)看源代碼中的。代碼的開(kāi)始定義了一個(gè)函數(shù),函數(shù)有形參,,為回調(diào)函數(shù)。 這兩天仔細(xì)看了看express的源碼,對(duì)其的整個(gè)實(shí)現(xiàn)有了較清晰的認(rèn)識(shí),所以想總結(jié)一下寫出來(lái),如果有什么不對(duì)的地方,望指出。 這是第一篇,首先介紹一個(gè)最簡(jiǎn)單的...

    summerpxy 評(píng)論0 收藏0
  • 筆記:解讀express 4.x源碼

    摘要:載入了框架,我們來(lái)看源代碼中的。函數(shù)函數(shù)代碼如下代碼的開(kāi)始定義了一個(gè)函數(shù),函數(shù)有形參,,為回調(diào)函數(shù)。相應(yīng)的,等同于繼承,從而讓有了事件處理的能力。 此為裁剪過(guò)的筆記版本。 原文在此:https://segmentfault.com/a/11...原文在此: https://cnodejs.org/topic/574... 感謝@YiQi ,@leijianning 帶來(lái)的好文章。我稍作...

    jzman 評(píng)論0 收藏0
  • Express源碼學(xué)習(xí)-路由

    摘要:框架核心特性路由定義了路由表用于執(zhí)行不同的請(qǐng)求動(dòng)作。中間件可以設(shè)置中間件來(lái)響應(yīng)請(qǐng)求。注冊(cè)一個(gè)請(qǐng)求路由結(jié)束響應(yīng)開(kāi)啟監(jiān)聽(tīng)端口執(zhí)行上面代碼是一種實(shí)用工具,將為您的源的任何變化并自動(dòng)重啟服務(wù)器監(jiān)控。 Express 簡(jiǎn)介 Express 是一個(gè)簡(jiǎn)潔而靈活的 node.js Web應(yīng)用框架, 提供了一系列強(qiáng)大特性幫助你創(chuàng)建各種 Web 應(yīng)用,和豐富的 HTTP 工具。使用 Express 可以快...

    laznrbfe 評(píng)論0 收藏0
  • 學(xué)習(xí)express.js源代碼的方法

    摘要:學(xué)習(xí)的源代碼的好處自然不少。閱讀源代碼可以幫你實(shí)現(xiàn)你的好奇心。本文會(huì)推薦一些的源代碼分析文章,可以幫助更快的,更加全方位的理解研讀之。 盡管有Hapi,Koa等有力的競(jìng)爭(zhēng)者,express.js依然是非常流行的nodejs web服務(wù)器框架,畢竟它早于2007年就已經(jīng)在開(kāi)發(fā)了。 學(xué)習(xí)expressjs的源代碼的好處自然不少。 它可以幫你深刻理解HTTP協(xié)議,這個(gè)協(xié)議是做前端后端都必然需...

    huaixiaoz 評(píng)論0 收藏0
  • express 源碼閱讀(全)

    摘要:每個(gè)請(qǐng)求都會(huì)對(duì)應(yīng)一個(gè)響應(yīng)。一個(gè)響應(yīng)主要包括狀態(tài)行響應(yīng)頭消息體,將常用的數(shù)據(jù)封裝為類,在上面的代碼中就是該類的一個(gè)對(duì)象。執(zhí)行測(cè)試用例,報(bào)錯(cuò),提示不存在。目前在中,一個(gè)路由是由三個(gè)部分構(gòu)成路徑方法和處理函數(shù)。 1. 簡(jiǎn)介 這篇文章主要的目的是分析理解express的源碼,網(wǎng)絡(luò)上關(guān)于源碼的分析已經(jīng)數(shù)不勝數(shù),這篇文章準(zhǔn)備另辟蹊徑,仿制一個(gè)express的輪子,通過(guò)測(cè)試驅(qū)動(dòng)的開(kāi)發(fā)方式不斷迭代,正...

    Steven 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<