express 中間件的簡單應用與實現

express 中間件的簡單應用與實現

看了慕課網雙越老師的課以後結合本身的理解作了一些簡單的總結,若有不恰當之處,歡迎指正。node

提到 express 就不得不提到中間件,接下來就簡單的介紹一下 expres 中間件的簡單應用與部分經常使用函數的實現。express

1. express 中間件的簡單應用

在平常項目的開發中,登陸驗證是一個很是常見的場景,這個時候 express 中間件就能夠派上用場了。接下來分別使用原生 nodeexpress 中間件的方法實現簡單的登陸驗證。json

應用場景:在獲取博客列表以前要進行登陸驗證,只有在登陸狀態下才能夠獲取博客列表。api

原生 node.js 實現登陸驗證:數組

if(method === 'GET' && req.path === '/api/blog/list') {
  // 若req.session.username有值說明已經登陸
  if (req.session.username){
    // 登陸狀態下能夠獲取博客列表
    獲取博客列表
  } 
}

express 中間件實現登陸驗證:服務器

function loginCheck(req, res, next) {
    if (某個登陸成功的條件) {
       next(); // 登陸成功執行next
    } else {
       res.json({
         errno: -1,
         msg: 'login fail'
    })
    }
}
// 若在登陸狀態下,loginCheck會執行next函數,從而執行第二個中間件,獲取博客列表。
app.get('/api/blog/list', loginCheck, (req, res, next) => {
    得到博客列表
})

雖然上面這個簡單的例子中看上去彷佛原生 node.js 要更簡單一些,可是使用中間件的方式封裝性更好,在一些比價複雜的項目中就能體現出優點了。cookie

2. express 中 use, get, post, next, listen 等方法的實現

實現 express 中間件 流程圖

結合上面的流程圖和最後給出的完整代碼,簡單說明一下實現的過程:session

這裏以 use (在實現思路上和 get, post 是同樣的)爲例說明:app

  • 獲取路徑和中間件函數

    use 將用戶調用時傳入的參數傳給 register 函數,register 函數將獲取到參數後分離出路徑和中間兩部分,路徑存放在 info.path 中,中間件存放在 info.stack 中。而後 register 函數將分離開的路徑和中間件再返回給 useuse 拿到分離後的路徑和中間件以後,會將其賦值給 constructor 函數中 this.routes.all

注意:register 在分離路徑時須要注意的一點是對於沒有路徑只有中間件的參數會將其路徑賦值爲根路徑 /

  • listen 方法的實現

    使用 express 時調用 app.listen(3000) 實際上不僅是監聽 3000 端口還建立了一個 http 服務,參數是一個回調函數,代碼以下:

    listen(...args) {
      const server = http.createServer(this.callback());
      server.listen(...args); 
    }
  • res.json 方法的實現

    在回調函數 callback 中首先實現一個 res.json 方法,實現方式也比較簡單。

    1. 定義 resHeaderContent-typeapplication/json
    2. 執行res.end({JSON.stringify(data)}) 將用戶傳入的 data 對象轉換爲 JSON 字符串輸出到屏幕上。
  • 獲取須要執行的中間件

    這部分經過 match 函數實現。經過 req.urlreq.method.toLowerCase 來獲取須要執行的中間件。中間實現的方法就有不少了,思路就是經過method 找出相應的路徑和中間件,這個已經在 this.routes 中分類存儲了。而後找出路徑和中間件以後利用 indexOf 方法,

    if (url.indexOf(routeInfo.path) === 0) 若是 if 判斷成立說明知足條件,那麼須要執行的中間件也就找出來了。

  • app.next 方法的實現

    這部分經過 handle 函數實現。思路:定義一個 next 函數,在那些須要執行的中間件中,先取出第一個,若是不爲空那麼開始執行,三個參數分別爲 req, res, next。後面須要執行的中間件到底執不執行取決於前一箇中間件的程序中是否調用了 next 方法。代碼以下:

    const next = () => {
          // shift 取得數組的第一項
          const middleware = stack.shift();
          if (middleware) {
            // 執行中間件函數
            middleware(req, res, next);
          }
        };
        // 定義完後當即執行
        next();

    注意:在定義完next函數以後,必須當即執行一次next函數,不然第一個須要執行的中間件也不會執行了。

    中間件中傳入的參數中的next,就是在這裏定義的next函數,若是前一箇中間件程序中執行了next函數,那麼會再執行 stack.shift 取出須要執行的中間件中的下一個中間件。

附上完整代碼:

const http = require('http');
const slice = Array.prototype.slice;

class LikeExpress {
  constructor() {
    // 存放路徑和中間件,all中存放的是調用use傳入的路徑和中間件
    this.routes = {
      all: [],
      get: [],
      post: []
    }
  }

  // 分離出路徑和中間件,info.path中存放路徑,info.stack 中存放中間件
  register(path) {
    const info = {};
    if (typeof path === 'string') {
      info.path = path;
      // info.stack 存放全部的中間件
      // 若是第一個參數是路由在取中間件時就要從數組的第2個位置開始取
      // slice.call(arguments, 1) 的做用就是取arguments從第二個位置開始以後的全部元素都取出來並變成數組的形式。
      info.stack = slice.call(arguments, 1);
    } else {
      // 若是第一個參數不是一個路由,那麼咱們就假定第一個參數是一個根路由
      info.path = '/';
      info.stack = slice.call(arguments, 0);
    }
    return info;
  }

  use() {
    // 實際使用時,參數是經過use傳遞進來的
    // 將全部的參數傳入到register函數中
    const info = this.register.apply(this, arguments);
    // info 是一個對象,info.path 中存放的是路徑,info.stack 中存放的是中間件
    this.routes.all.push(info);

  }

  get() {
    const info = this.register.apply(this, arguments);
    this.routes.get.push(info);

  }

  post() {
    const info = this.register.apply(this, arguments);
    this.routes.post.push(info);
  }

  // 匹配use,get或post方法會執行的中間件
  match(method, url) {
    let stack = [];
    if (url === '/favicon.ico') {
      return stack;
    }
    // 獲取routes
    let curRoutes = [];
    // concat 是數組中的一個方法,若是沒有參數,那麼會生成一個當前數組的副本並將其賦值給前面的變量,若是有參數會將參數加入到生成的副本的後面而後將其賦值給變量
    // 若是是use,那麼就把use中的路徑和中間列表複製到curRoutes中
    // 若是方法是get或post那麼下面這句話,因爲this.routes.all是undefined,因此會將當前curRoutes生成一個副本賦值給curRoutes,其實一點變化都沒有
    curRoutes = curRoutes.concat(this.routes.all);
    // 若是是get或post,那麼就把相應的路徑和中間件複製到curRoutes中
    curRoutes = curRoutes.concat(this.routes[method]);

    curRoutes.forEach(routeInfo => {
      // url='/api/get-cookie' routeInfo.path='/'
      // url='/api/get-cookie' routeInfo.path='/api'
      // url='api/get-cookie' routeInfo.path='/api/get-cookie'
      if (url.indexOf(routeInfo.path) === 0) {
        // 匹配成功
        stack = stack.concat(routeInfo.stack);
      }
    });
    return stack;
  }

  // 核心的 next 機制
  handle(req, res, stack) {
    const next = () => {
      // 拿到第一個匹配的中間件
      // shift 取得數組的第一項
      const middleware = stack.shift();
      if (middleware) {
        // 執行中間件函數
        middleware(req, res, next);
      }
    };
    // 定義完後當即執行
    next();

  }


  // callback是一個(req, res) => {} 的函數,結合http-test中的代碼去理解
  callback() {
    return (req, res) => {
      // res.json 是一個函數,在express中使用時傳入一個對象便可在屏幕中顯示出來,這裏實現了這個功能
      res.json = (data) => {
        res.setHeader('Content-type', 'application/json');
        res.end(
          JSON.stringify(data)
        );
      };

      const url = req.url;
      const method = req.method.toLowerCase();
      // 找到須要執行的中間件(經過路徑和method,有可能一個須要執行的中間件也沒有)
      const resultList = this.match(method, url);
      this.handle(req, res, resultList);
    }
  }

  // express 中listen的做用不單單是監聽端口,還要建立服務器
  listen(...args) {
    const server = http.createServer(this.callback());
    server.listen(...args);
  }
}

module.exports = () => {
  return new LikeExpress()
};

相關文章
相關標籤/搜索