靜心打磨手中利刃之Express

本文出自[Century's World]

不知從什么時(shí)候開始,node就開始風(fēng)靡起來(lái),我們甚至都沒有謹(jǐn)慎的研究過他和其他服務(wù)器的區(qū)別,便開始躍躍欲試。相信很多人會(huì)和筆者一樣,在接觸node服務(wù)器的第一時(shí)刻,便接觸到了express,我有個(gè)朋友也說過,學(xué)一個(gè)東西還是要結(jié)合框架來(lái)學(xué)比較好,當(dāng)時(shí)我便聽了話,開始了express+node的學(xué)習(xí),當(dāng)然,express非常順手,給了我很好的體驗(yàn)。而今天,我們撇開浮躁,靜下心來(lái),仔細(xì)研究Express框架。

假如沒有Express

說到底Express只是一個(gè)框架而已,那么,他是一個(gè)什么框架呢,我們撇開Express不談,我們想要完成一個(gè)Node的服務(wù),只要如下的代碼就可以完成:

var http = require("http");
var server = http.createServer(function(request, response) {
  response.writeHead(200, {"Content-Type": "text/html"});
  response.write("Hello World!");
  response.end();
});
server.listen(80);

很簡(jiǎn)單的幾行代碼就實(shí)現(xiàn)了一個(gè)服務(wù)器,假如我們的需求只是簡(jiǎn)單的渲染一個(gè)頁(yè)面的話,我們大可以用這幾行代碼完成我們想做的事情,其實(shí)http這系列的庫(kù)做了很多事情,非常建議大家回頭去看看這一系列Express底層的Api。當(dāng)然了最后我們還是會(huì)用框架的,自己整理的也好,用現(xiàn)在流行的Expres或者Koa也好,目的是為了應(yīng)對(duì)復(fù)雜的使用場(chǎng)景,減少重復(fù)繁瑣的代碼。

小小的實(shí)現(xiàn)一下

說起Express的特點(diǎn),大概就是中間件吧,所有的東西都是通過中間件來(lái)完成的,那么中間件實(shí)現(xiàn)是怎么樣的呢,假如我們自己實(shí)現(xiàn)一個(gè)Express,我們就應(yīng)該先解決中間件的問題,那讓我們來(lái)嘗試一下實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Express。

首先我們抽離createServer的參數(shù)

const app = function(req, res) {
  //TODO someting
}
var server = http.createServer(app);
server.listen(port)

這樣我們邏輯處理就放到了app里面,有的時(shí)候一次運(yùn)行可能產(chǎn)生多個(gè)app的實(shí)例,為了分隔環(huán)境,我們可以用一個(gè)工廠方法或者構(gòu)造行數(shù)生成app

const express = function() {
  return function(req, res) {
  //TODO someting
  }
}

const app = express();
var server = http.createServer(app)
server.listen(port)

接下來(lái)我們先實(shí)現(xiàn)app.use


function Middleware = function(path, fn) {
  if (!(this instanceof Middleware)) {
    return new Middleware(...arguments);
  }
  this.path = path;
  this.fn = fn;
}

app.use = function(path, ...fns) {
  if (arguments.length == 1) {
    fns = path;
    path = '/';
  }
  for(var index in fns) {
    var fn = fns[index]
    const middleware = Middleware(path, fn);
    this.middlewares.push(middleware);
  }
}

我們定義了一個(gè)Middleware的對(duì)象,其中有個(gè)很多框架中常用的hack,也就是在一個(gè)class里面判斷是否是使用new來(lái)創(chuàng)建對(duì)象的,因?yàn)槭褂胣ew來(lái)創(chuàng)建對(duì)象的時(shí)候this一定是當(dāng)前類的實(shí)例,所以我們可以根據(jù)this的類型來(lái)重定向一個(gè)new Function。Middleware用于封裝一個(gè)中間件層,用于綁定中間件和對(duì)應(yīng)的path。在app.use里面我們將中間件都保存在appmiddlewares屬性中,那么接下來(lái),我們就要實(shí)現(xiàn)這個(gè)中間件的處理過程。

首先我們要增加一個(gè)handler的function

function mathMiddleware(url, middleware) {
  // TODO match middleware
}
app.handler = function(req, res) {
  var url = req.url;
  var middlewares = app.middlewares;
  var idx = 0;
  var match = false;
  var middleware;
  function done() {
    //完成這次請(qǐng)求, 比如有error的情況
  }
  function next (err) {
    if (err) {
      done(err)
    }
    while(match === false && idx < middlewares.length) {
      middleware = middlewares[idx];
      match = mathMiddleware(url, middleware)
    }
    middleware.handle(req, res, next);
  }
}

在Middleware中加上一個(gè)handle

Middleware.prototype.handle = function(req, res, next) {
  try {
    this.fn(req, res, next);
  } catch(e)  {
    next(e)
  }
}

這只是簡(jiǎn)單的實(shí)現(xiàn)了一下Express的中間件的邏輯,這也大致是Express的實(shí)現(xiàn)邏輯,我們知道了這種中間件的實(shí)現(xiàn)方式,那么今后在我們的應(yīng)用中,對(duì)某一塊邏輯要使用策略模式、裝飾著模式或者工廠模式的時(shí)候,我們也可以用一個(gè)這樣的中間件的策略去切割代碼,讓邏輯的處理變的非常簡(jiǎn)單而清晰。

別YY了,看一下官方實(shí)現(xiàn)

官方的代碼其實(shí)寫的非常易懂,總的來(lái)說,給我的感覺,express就是一個(gè)微型的框架。

結(jié)構(gòu)
├── application.js
├── express.js
├── middleware
│   ├── init.js
│   └── query.js
├── request.js
├── response.js
├── router
│   ├── index.js
│   ├── layer.js
│   └── route.js
├── utils.js
└── view.js
express.js

這是整個(gè)應(yīng)用的入口,主要的工作的整合了application.js中的關(guān)于app的屬性,構(gòu)造了一個(gè)函數(shù)來(lái)輸出app,將一些輔助函數(shù)輸出,比如Router、Request等等

router

這個(gè)類我們應(yīng)該不陌生,我們?cè)谑褂胑xpress的時(shí)候經(jīng)常使用

var express = require('express');
var router = express.Router();

這個(gè)router其實(shí)是整個(gè)app的核心,包括app.use的中間件實(shí)現(xiàn)也是通過Router實(shí)現(xiàn)的,以下是源碼加上注釋

proto.use = function use(fn) {
  var offset = 0; //用于計(jì)算參數(shù)的個(gè)數(shù)來(lái)獲取所有的中間件
  var path = '/'; //設(shè)置默認(rèn)的path為 /
  if (typeof fn !== 'function') { // use('/', fn)的情況
    var arg = fn;
    while (Array.isArray(arg) && arg.length !== 0) {
      arg = arg[0];
    }
    if (typeof arg !== 'function') {
      offset = 1;
      path = fn;
    }
  }
  //通過offset獲取所有的中間件
  var callbacks = flatten(slice.call(arguments, offset));
  //當(dāng)中間件的個(gè)數(shù)為零的時(shí)候,拋出異常
  if (callbacks.length === 0) {
    throw new TypeError('Router.use() requires a middleware function')
  }

  for (var i = 0; i < callbacks.length; i++) {
    var fn = callbacks[i];
    if (typeof fn !== 'function') {
      throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))
    }
    debug('use %o %s', path, fn.name || '<anonymous>')
    // 創(chuàng)建一個(gè)Layer,類似于之前自己實(shí)現(xiàn)的Middleware
    var layer = new Layer(path, {
      sensitive: this.caseSensitive,
      strict: false,
      end: false
    }, fn);
    layer.route = undefined;
    this.stack.push(layer); //將這個(gè)layer加入到棧中
  }
  return this; //返回自己用于鏈?zhǔn)秸{(diào)用
};

之后會(huì)講到這個(gè)Layer類,這是一個(gè)中間件的承載物,所有的中間件的處理方法和路徑的綁定信息都被封裝在這個(gè)類的實(shí)例中,而Router的use方法即使將這些中間件打包成為一個(gè)Layer,然后存儲(chǔ)到自己的stack中用于之后使用。
然后是一個(gè)Router中的request處理類,用于使用中間件處理請(qǐng)求。

proto.handle = function handle(req, res, out) {
  var self = this; //用self指向老的this
  var idx = 0; // 迭代stack的迭代器
  var protohost = getProtohost(req.url) || '' //獲取協(xié)議名
  var removed = ''; // 定義刪除字段
  var slashAdded = false;
  var paramcalled = {};
  var options = [];
  var stack = self.stack; // layer 的集合
  var parentParams = req.params;
  var parentUrl = req.baseUrl || '';
  var done = restore(out, req, 'baseUrl', 'next', 'params');
  req.next = next;
  if (req.method === 'OPTIONS') { // 處理Options的請(qǐng)求,跨域問題
    done = wrap(done, function(old, err) {
      if (err || options.length === 0) return old(err);
      sendOptionsResponse(res, options, old);
    });
  }
  req.baseUrl = parentUrl;
  req.originalUrl = req.originalUrl || req.url;
  next();
  // 中間件的迭代方法
  function next(err) {
    var layerError = err === 'route' ? null : err;
    //計(jì)算req.url, req.baseUrl
    if (slashAdded) {
      req.url = req.url.substr(1);
      slashAdded = false;
    }
    if (removed.length !== 0) {
      req.baseUrl = parentUrl;
      req.url = protohost + removed + req.url.substr(protohost.length);
      removed = '';
    }
    //處理特殊情況,結(jié)束這次訪問
    if (layerError === 'router') {
      setImmediate(done, null)
      return
    }
    //處理特殊情況,結(jié)束這次訪問, 沒有匹配的路由了
    if (idx >= stack.length) {
      setImmediate(done, layerError);
      return;
    }
    var path = getPathname(req); //獲取當(dāng)前path
    if (path == null) {
      return done(layerError);
    }
    var layer;
    var match;
    var route;
    while (match !== true && idx < stack.length) {
      // TODO: 循環(huán)獲取匹配的Layer
    }
    // 沒有匹配的
    if (match !== true) {
      return done(layerError);
    }
    //TODO: 根據(jù)layer注入req.params
    //TODO: 根據(jù)需要調(diào)用layer.handle_request或者layer.handle_error
  }
};

當(dāng)然了我省略了一部分代碼,給大家大致的介紹了一下router是如何處理中間件的,處理中間件的關(guān)鍵就這么兩個(gè)函數(shù),一個(gè)是use,一個(gè)是handlehandle用于以中間件的形式迭代處理請(qǐng)求,use用于注冊(cè)中間件

application.js

這是一個(gè)app的類,定義了app的屬性和函數(shù),那么application又和router有什么聯(lián)系呢,我們從api中可以發(fā)現(xiàn),幾乎router有的函數(shù),在app中都可以使用,比如router.use和app.use,router.get和app.get,那么官方的實(shí)現(xiàn)中,是怎么實(shí)現(xiàn)的呢,是否是簡(jiǎn)單的繼承呢。我們帶著疑問去觀察這個(gè)類,我們會(huì)發(fā)現(xiàn)application中有個(gè)router的屬性,在中間件的表現(xiàn)上,application只是一個(gè)傀儡,大部分的實(shí)現(xiàn)都還是依靠router的,application的中間件的操作都是交由其router來(lái)處理的,也就是說app.use()是約等于app.router.use()的。比如:

app.param = function param(name, fn) {
  this.lazyrouter();
  if (Array.isArray(name)) {
    for (var i = 0; i < name.length; i++) {
      this.param(name[i], fn);
    }
    return this;
  }
  this._router.param(name, fn);
  return this;
};

話雖這么說,但是app的router是懶加載的,當(dāng)調(diào)用use之類的函數(shù)的時(shí)候會(huì)判斷當(dāng)前是否已經(jīng)創(chuàng)建了router,否則會(huì)創(chuàng)建一個(gè)router。除此之外,還會(huì)對(duì)參數(shù)做一些校驗(yàn)和轉(zhuǎn)換,因此還是推薦不直接使用app.router的方式的。

middleware

(有點(diǎn)無(wú)聊)這個(gè)middleware目錄下只是express內(nèi)置的兩個(gè)中間件,一個(gè)query是用于在req.query注入url中的query參數(shù)的,init是一個(gè)初始化的中間件,它把req、res相互引用了一些,并mixin了一些req, res的屬性,還有x-powered-by額?默認(rèn)給express打個(gè)廣告?

Layer

(有那么點(diǎn)意思)這是一個(gè)藏在Router下的對(duì)象,用于包裝中間件和對(duì)應(yīng)的path。

request&response

(比較無(wú)聊)這兩個(gè)類里面主要是一些api這個(gè)和express的核心部分的關(guān)系沒有那么大,他的主要工作主要集中在封裝了一些工具方法,一個(gè)方便開發(fā)者使用的req, res的屬性集合的對(duì)象。

看源碼有什么用?

總的來(lái)說,這次的Express源碼之旅是很有幫助的,這是我開始這個(gè)源碼計(jì)劃的第一個(gè)項(xiàng)目,選擇Express的原因是這個(gè)框架的代碼確實(shí)看起來(lái)比較簡(jiǎn)單,不需要編譯,其次Express還是我現(xiàn)在用的最多的node服務(wù)框架,當(dāng)然之后會(huì)考慮使用koa,所以之后很有可能會(huì)帶來(lái)koa的源碼解讀。作為一個(gè)node服務(wù)的框架,這次源碼閱讀讓我更加了解Node的http這個(gè)模塊的東西,有很多的基礎(chǔ)的模塊在jshttp中,閱讀它們會(huì)讓我更加理解一些關(guān)于http的問題,查看了整個(gè)中間件的實(shí)現(xiàn),讓我對(duì)這種模式豁然開朗,之后希望能夠在項(xiàng)目中靈活的運(yùn)用。對(duì)request和response的閱讀讓我知道了很多之前看文檔沒有仔細(xì)觀察到的api。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容