作者:@htoooth
本文為作者原創(chuàng),轉(zhuǎn)載請注明出處:http://www.cnblogs.com/htoooth/p/7122364.html

nodejs模塊學(xué)習(xí): connect2 解析

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

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

引言

上一篇文章中,我討論了 connect 模塊,它做為 http 的中間件,設(shè)計得很靈活,接口設(shè)計也很少,非常便于使用。

其實 connect 模塊的思想就是把 http 請求和回應(yīng)看成流水線,而中間件則是流水線上的處理器,滿足路由匹配,則調(diào)用相應(yīng)的方法,直到結(jié)果返回。

我就在想,這套思想能不能用在別的地方? 如 rpc, tcp 的請求處理,它們也都是一問一答的模式,也都可以抽象成流水線的模式進行處理,每個中間件對其中的數(shù)據(jù)進行處理,最后把結(jié)果返回了。

在仔細(xì)研究了 connect 的源碼的基礎(chǔ)上,我精減了部分代碼,拿掉了 http 部分, 還有 url 匹配的部分,保留最有用的部分,同時增加了參數(shù)化配置和上下文環(huán)境。

于是就有了: connect2這個模塊,最小化 connect 的功能,保留了 next 和錯識處理等已知的概念,沒有帶它的功能。它可以做為一個基礎(chǔ)的模塊嵌入到一個 rpc,tcp , http 中去,然后利用中間件的思想去完成你的業(yè)務(wù)。

下面,就仔細(xì)說說,我對它的考慮和使用說明。

解析

我對該模塊的第一個考慮就是,他的實現(xiàn)跟某種協(xié)議無關(guān)。可以看看 connect2 的使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var connect = require('connect2');
 
var app = connect();
 
app.use(function(ctx, req, res, next){
  console.log('md1')
  next()
});
 
app.use(function(ctx, req, res) {
  console.log('md2')
  next()
})
 
function main() {
  let context = {};
  let req = {};
  let res = {};
 
  app(context, req, res);
}
 
main();

可以看出,基本使用的方法于 connect 的模塊相同,但是已經(jīng)沒有調(diào)用 http 的服務(wù)器了,它能在一個普通的函數(shù)中調(diào)用。

同時,多了一個 context, 這個我覺得挺重要的,用 express 做項目時,要跟蹤請求全鏈路的路徑,這在 java 中還好辦,有 ThreadLocal 。這在 nodejs 中沒有什么很好的辦法,只能通過參數(shù)的形式,把 requestId 傳下去。而 context 就是放這一類參數(shù)很好的地方。

有了這個 context 還可以把 協(xié)議 上下文也放到里面,實現(xiàn)更有用的功能。

導(dǎo)出函數(shù)是這樣寫的:

1
2
3
4
5
6
7
8
9
10
function createServer(opts) {
  function app (ctx, req, res, next) { app.handle(ctx, req, res, next)}
 
  Object.assign(app, proto);
  Object.assign(app, opts);
  Object.assign(app, EventEmitter.prototype);
  app.stack = [];
  app.route = '';
  return app;
}

多了一個配置的 opts,會把 opts 的屬性復(fù)制到 app 上面。后面會說一下有哪些方法可以配置。

而 use 方法也是基本沒有怎么改變,刪除了 http 的部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
proto.use = function(route, fn) {
  let handle = fn;
  let path = route;
 
  if (typeof route !== 'string') {
    handle = route;
    path = '';
  }
 
  if (typeof handle.handle === 'function') {
    let server = handle;
    server.route = path;
 
    handle = function(ctx, req, res, next) {
      server.handle(ctx, req, res, next);
    };
  }
 
  this.stack.push({handle: handle, route: path});
}

下面說說其中的核心 handle 功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
proto.handle = function(ctx, req, res, out) {
  let i = 0;
  let done = out || (this.finalHandler && this.finalHandler(ctx, req, res)) || NOOP;
  let dispatchContext = (this.dispatchContext && this.dispatchContext()) || {};
  let self = this;
 
  Object.assign(ctx, {
    app: this,
    req: req,
    res: res
  })
 
  let next = function(err) {
    let layer = self.stack[i++]
 
    if (!layer) {
      defer(done, err)
      return;
    }
 
    if (layer.route && self.dispatch && !self.dispatch(dispatchContext, layer.route, req)) {
      return next(err)
    }
 
    debug('use %s %s', layer.route || 'none', layer.handle.name || 'anonymous');
    call(layer.handle, layer.route, err, ctx, req, res, next);
  }
 
  next();
}

任何請求都有一個結(jié)束, 在 connect 中使用的是 finalhanlder 模塊,也只能處理 http 問題,這里我們與 協(xié)議無關(guān),因此這里就需要留下 一個接口,這個接口就通過 opts 進行配置的。

另外還有匹配參數(shù)的方法,dispatch 這塊是把 url 參數(shù)匹配的算法移出,通過初始化參數(shù)的形式返回。

更多的例子可以在 test/server.js 中找到。

總結(jié)

connect2 就是對 connect 的一個精減。針對的更加普遍的問題,對一些東西能進行流水線的形式進行處理,將變化寫成中間件,然后對所以的數(shù)據(jù)進行處理,在合適的時候返回。

特別合適網(wǎng)絡(luò)服務(wù)器,自定義協(xié)議的部分,想想,通過這個模塊,除了底層的協(xié)議,跟 http 不一樣,其他都一樣,這樣寫業(yè)務(wù)是不是很爽呢?

很快就要把這個模塊融入到一個項目中,還有想把該項目給 typescript 化。

http://www.cnblogs.com/htoooth/p/7122364.html