国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

nodejs模塊學(xué)習(xí)之connect解析

2019-11-19 16:10:29
字體:
供稿:網(wǎng)友

nodejs 發(fā)展很快,從 npm 上面的包托管數(shù)量就可以看出來。不過從另一方面來看,也是反映了 nodejs 的基礎(chǔ)不穩(wěn)固,需要開發(fā)者創(chuàng)造大量的輪子來解決現(xiàn)實的問題。

知其然,并知其所以然這是程序員的天性。所以把常用的模塊拿出來看看,看看高手怎么寫的,學(xué)習(xí)其想法,讓自己的技術(shù)能更近一步。

引言

express 是 nodejs 中最流行的 web 框架。express 中對 http 中的 request 和 response 的處理,還有以中間件為核心的處理流程,非常靈活,足以應(yīng)對任何業(yè)務(wù)的需求。

而 connect 曾經(jīng)是 express 3.x 之前的核心,而 express 4.x 已經(jīng)把 connect 移除,在 express 中自己實現(xiàn)了 connect 的接口??梢哉f connect 造就了 express 的靈活性。

因此,我很好奇,connect 是怎么寫的。

爭取把每一行代碼都弄懂。

connect 解析

我們要先從 connect 的官方例子開始

var  connect = require( 'connect' );  var  http = require( 'http' );  var  app = connect();  // gzip/deflate outgoing responses  var  compression = require( 'compression' );  app.use(compression()); // store session state in browser cookie  var  cookieSession = require( 'cookie-session' );  app.use(cookieSession({     keys: [ 'secret1' ,  'secret2' ]  }));  // parse urlencoded request bodies into req.body  var  bodyParser = require( 'body-parser' );  app.use(bodyParser.urlencoded({extended:  false }));   // respond to all requests  app.use( function (req, res){    res.end( 'Hello from Connect!/n' );  });  //create node.js http server and listen on port  http.createServer(app).listen(3000); 

從示例中可以看到一個典型的 connect 的使用:

 var  app = connect() // 初始化  app.use( function (req, res, next) {     // do something  }) // http 服務(wù)器,使用  http.createServer(app).listen(3000); 

先倒著看,從調(diào)用的地方更能看出來,模塊怎么使用的。我們就先從  http.createServer(app)  來看看。

nodejs doc 的官方文檔中可以知,  createServer  函數(shù)的參數(shù)是一個回調(diào)函數(shù),這個回調(diào)函數(shù)是用來響應(yīng)  request  事件的。從這里看出,示例代碼中  app  中函數(shù)簽就是  (req, res) ,也就是說  app  的接口為  function (req, res) 。

但是從示例代碼中,我們也可以看出  app  還有一個  use  方法。是不是覺得很奇怪,js 中函數(shù)實例上,還以帶方法,這在 js 中就叫 函數(shù)對象,不僅能調(diào)用,還可以帶實例變量。給個例子可以看得更清楚:

function  handle () {    function  app(req, res, next) { app.handle(req, res, next)}   app.handle =  function  (req, res, next) {     console.log( this );    }   app.statck = [];    return  app;  }  var  app = handle();  app()  // ==> { [Function: app] handle: [Function], stack: [] }  app.apply({})  // ==>{ [Function: app] handle: [Function], stack: [] } 

可以看出:函數(shù)中的實例函數(shù)中的 this 就是指當(dāng)前的實例,不會因為你使用 apply 進行環(huán)境改變。

其他就跟對象沒有什么區(qū)別。

再次回到示例代碼,因該可以看懂了,  connect  方法返回了一個函數(shù),這個函數(shù)能直接調(diào)用,有 use 方法,用來響應(yīng) http 的 request 事件。

到此為此,示例代碼就講完了。 我們開始進入到 connect 模塊的內(nèi)部。

connect 只有一個導(dǎo)出方法。就是如下:

 var  merge = require( 'utils-merge' );  module.exports = createServer;  var  proto = {};  function  createServer() {    // 函數(shù)對象,這個對象能調(diào)用,能加屬性    function  app(req, res, next){ app.handle(req, res, next); }    merge(app, proto);  // ===等于調(diào)用 Object.assign    merge(app, EventEmitter.prototype);  // === 等于調(diào)用 Object.assign    app.route =  '/' ;    app.stack = [];    return  app;  } 

從代碼中可以看出,createServer 函數(shù)把 app 函數(shù)返回了,app 函數(shù)有三個參數(shù),多了一個 next (這個后面講),app函數(shù)把 proto 的方法合并了。還有 EventEmitter 的方法也合并了,還增加了 route 和 stack 的屬性。

從前面代碼來看,響應(yīng) request 的事件的函數(shù),是 app.handle 方法。這個方法如下:

 proto.handle =  function  handle(req, res, out) {    var  index = 0;    var  protohost = getProtohost(req.url) ||  '' ;  //獲得 http://www.baidu.com    var  removed =  '' ;    var  slashAdded =  false ;    var  stack =  this .stack;     // final function handler    var  done = out || finalhandler(req, res, {     env: env,     onerror: logerror    });  // 接口 done(err);     // store the original URL    req.originalUrl = req.originalUrl || req.url;     function  next(err) {     if  (slashAdded) {      req.url = req.url.substr(1);  // 除掉 / 之后的字符串      slashAdded =  false ;  // 已經(jīng)拿掉     }      if  (removed.length !== 0) {      req.url = protohost + removed + req.url.substr(protohost.length);      removed =  '' ;     }      // next callback     var  layer = stack[index++];      // all done     if  (!layer) {      defer(done, err);  // 沒有中間件,調(diào)用 finalhandler 進行處理,如果 err 有值,就返回 404 進行處理      return ;     }      // route data     var  path = parseUrl(req).pathname ||  '/' ;     var  route = layer.route;      // skip this layer if the route doesn't match     if  (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) {      return  next(err);  // 執(zhí)行下一個     }      // skip if route match does not border "/", ".", or end     var  c = path[route.length];     if  (c !== undefined && '/ ' !== c && ' . ' !== c) {      return next(err); // 執(zhí)行下一個     }      // trim off the part of the url that matches the route     if (route.length !== 0 && route !== ' / ') {      removed = route;      req.url = protohost + req.url.substr(protohost.length + removed.length);       // ensure leading slash      if (!protohost && req.url[0] !== ' / ') {       req.url = ' /' + req.url;       slashAdded =  true ;      }     }      // call the layer handle     call(layer.handle, route, err, req, res, next);    }     next();  }; 

代碼中有相應(yīng)的注釋,可以看出,next 方法就是一個遞歸調(diào)用,不斷的對比 route 是否匹配,如果匹配則調(diào)用 handle, 如果不匹配,則調(diào)用下一個 handle.

call 函數(shù)的代碼如下:

 function  call(handle, route, err, req, res, next) {    var  arity = handle.length;    var  error = err;    var  hasError = Boolean(err);     debug( '%s %s : %s' , handle.name ||  '<anonymous>' , route, req.originalUrl);     try  {     if  (hasError && arity === 4) {      // error-handling middleware      handle(err, req, res, next);      return ;     }  else  if  (!hasError && arity < 4) {      // request-handling middleware      handle(req, res, next);      return ;     }    }  catch  (e) {     // replace the error     error = e;    }     // continue    next(error);  } 

可以看出一個重點:對錯誤處理,connect 的要求 是函數(shù)必須是 四個參數(shù),而 express 也是如此。如果有錯誤, 中間件沒有一個參數(shù)的個數(shù)是 4, 就會錯誤一直傳下去,直到后面的  defer(done, err);  進行處理。

還有 app.use 添加中間件:

 proto.use =  function  use(route, fn) {    var  handle = fn;  // fn 只是一個函數(shù)的話 三種接口 // 1. err, req, res, next 2. req, res, 3, req, res, next    var  path = route;     // default route to '/'    if  ( typeof  route !==  'string' ) {     handle = route;     path =  '/' ;    }     // wrap sub-apps    if  ( typeof  handle.handle ===  'function' ) {  // 自定義中的函數(shù)對象     var  server = handle;     server.route = path;     handle =  function  (req, res, next) {  // req, res, next 中間件      server.handle(req, res, next);     };    }     // wrap vanilla http.Servers    if  (handle  instanceof  http.Server) {     handle = handle.listeners( 'request' )[0];  // (req, res) // 最后的函數(shù)    }     // strip trailing slash    if  (path[path.length - 1] ===  '/' ) {     path = path.slice(0, -1);    }     // add the middleware    debug( 'use %s %s' , path ||  '/' , handle.name ||  'anonymous' );    this .stack.push({ route: path, handle: handle });     return  this ;  }; 

從代碼中,可以看出,use 方法添加中間件到 this.stack 中,其中 fn 中間件的形式有兩種: function (req, res, next) 和 handle.handle(req, res, next) 這兩種都可以。還有對 fn 情況進行特殊處理。

總的處理流程就是這樣,用 use 方法添加中間件,用 next 編歷中間件,用 finalHandle 進行最后的處理工作。

在代碼中還有一個函數(shù)非常奇怪:

 /* istanbul ignore next */  var  defer =  typeof  setImmediate ===  'function'    ? setImmediate    :  function (fn){ process.nextTick(fn.bind.apply(fn, arguments)) } 

defer  函數(shù)中的  fn.bind.apply(fn, arguments) ,這個方法主要解決了,一個問題,不定參的情況下,第一個參數(shù)函數(shù),怎樣拿到的問題,為什么這樣說呢?如果中我們要達(dá)到以上的效果,需要多多少行代碼?

 function  () {     var  cb = Array.from(arguments)[0];     var  args = Array.from(arguments).splice(1);     process.nextTick( function () {       cb.apply( null ,args);     })  } 

這還是 connect 兼容以前的 es5 之類的方法。如果在 es6 下面,方法可以再次簡化

 function (..args){ process.nextTick(fn.bind(...args)) } 

總結(jié)

connect 做為 http 中間件模塊,很好地解決對 http 請求的插件化處理的需求,把中間件組織成請求上的一個處理器,挨個調(diào)用中間件對 http 請求進行處理。

其中 connect 的遞歸調(diào)用,和對 js 的函數(shù)對象的使用,讓值得學(xué)習(xí),如果讓我來寫,就第一個調(diào)個的地方,就想不到使用 函數(shù)對象 來進行處理。

而且 next 的設(shè)計如此精妙,整個框架的使用和概念上,對程序員基本上沒有認(rèn)知負(fù)擔(dān),這才是最重要的地方。這也是為什么 express 框架最受歡迎。koa 相比之下,多幾個概念,還使用了不常用的 yield 方法。

connect 的設(shè)計理念可以用在,類似 http 請求模式上, 如 rpc, tcp 處理等。

我把 connect 的設(shè)計方法叫做 中間件模式,對處理 流式模式,會有較好的效果。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持武林網(wǎng)。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 波密县| 辽源市| 青龙| 大英县| 察雅县| 渝中区| 荥阳市| 喀什市| 静安区| 大渡口区| 日土县| 稻城县| 广河县| 比如县| 屏东县| 渭源县| 抚州市| 石楼县| 淅川县| 文水县| 习水县| 通渭县| 涿州市| 锡林浩特市| 宾川县| 灵武市| 噶尔县| 花垣县| 马山县| 简阳市| 永州市| 韶山市| 桓台县| 女性| 象州县| 湘西| 抚顺市| 鹿邑县| 海南省| 象山县| 融水|