使用Node.js實現一個express框架

手寫一個express系列

express的基本用法

const express = require("express");
const app = express();

app.get("/test", (req, res, next) => {
  console.log("會所技師到位*1");
//   res.end("會所技師開始服務1");
  next();
});

app.get("/test", (req, res, next) => {
  console.log("會所技師到位*2");
  res.end("會所技師開始服務2");
});

app.listen(8888, (err) => {
  !err && console.log("會所裏面有大保健嗎?");
});
  • 當我訪問localhost:8888/test時候,返回了:會所技師開始服務 2,服務端打印了
會所技師到位*1
會所技師到位*2
  • 從上面能夠看到什麼?前端

    • express默認引入調用後返回一個app對象
    • app.listen 會啓動進程監聽端口
    • 每次收到請求,對應的urlmethod會觸發相應掛載在app上對應的回調函數
    • 調用 next 方法,會觸發下一個

一塊兒來實現一個簡單的express框架

  • 定義屬於咱們的express文件入口,這裏使用class來實現
class express {

}

module.exports = express;
  • 須要的原生模塊http,建立進程監聽端口
const { createServer } = require("http");
  • 給 class 定義 listen 方法,監聽端口
class express {
  listen(...args) {
    createServer(cb).listen(...args);
  }
}
  • 這樣就能夠經過調用 classlisten 去調用 http 模塊的 listen 了,這裏的cb咱們能夠先無論,你要知道每次接受到請求,必然會調用 cb 函數,這個是 createServer 原生模塊幫咱們封裝好的

實現接收到請求觸發

  • 實現app.get app.post等方法express

    • 目前咱們接受到響應,就會觸發 cb 這個回調函數,那咱們打印下,看看是什麼參數?
class express {
  cb() {
    return (req, res) => {
      console.log(res, res, "來客人了");
    };
  }
  listen(...args) {
    createServer(this.cb()).listen(...args);
  }
}
  • 發現此時的 reqres 正是咱們想要的可讀流和可寫流.

  • 開始編寫 getpost 方法redux

    • 這裏注意,有路由是'/'的,這種是無論任何路由都會觸發一次
constructor() {
    this.routers = {
      get: [],
      post: [],
    };
  }

  get(path, handle) {
    this.routers.get.push({
      path,
      handle,
    });
  }
  post(path, handle) {
    this.routers.post.push({
      path,
      handle,
    });
  }
  • 初始化時候定義 get、post 的數組儲存對應的 pathhandle.
  • 須要觸發路由回調的時候,首先要找到對應的請求方式下對應的 urlhandle 方法,而後觸發回調.
  • 如何找到對應請求方式下的 url 對應的 handle 方法? 在接到請求時候就要遍歷一次後端

    • 這裏要考慮匹配多個路由,意味着,咱們可能遇到像最開始同樣,有兩個 get 方式的 test 路由
cb() {
    return (req, res) => {
      const method = req.method.toLowerCase();
      console.log(this.routers[method], ",method");
      const url = req.url;
      this.routers[method].forEach((item) => {
        item.path === url && item.handle(req, res);
      });
    };
  }
  listen(...args) {
    createServer(this.cb()).listen(...args);
  }
  • 上面根據 method 找到對應的數組,遍歷找到請求的路由,觸發回調,此時已經能正常返回數據了
[ { method: 'get', path: '/test', handle: [Function] } ] ,method
  • 此時最簡單的express已經完成了,可是咱們好像忘了最重要的中間件

完成最重要的中間件功能

  • 首先要知道,express中間件分兩種,一種帶路由的,那就是根據路由決定是否觸發
  • 另一種就是不帶路由的,像靜態資源這種. 是用戶訪問任何路由都要觸發一次的
  • 那咱們須要一個 all 數組儲存這種任意路由都須要匹配觸發的
constructor() {
    this.routers = {
      get: [],
      post: [],
      all: [],
    };
  }
  • 以前的直接經過 push 方式是太粗暴.若是用戶須要中間件功能,不傳路由,那就要作特殊處理,這裏經過一箇中間函數處理下
  • 改造getpost方法,定義handleAddRouter方法
handleAddRouter(path, handle) {
    let router = {};
    if (typeof path === "string") {
      router = {
        path,
        handle,
      };
    } else {
      router = {
        path: "/",
        handle: path,
      };
    }
    return router;
  }

  get(path, handle) {
    const router = this.handleAddRouter(path, handle);
    this.routers.get.push(router);
  }

  post(path, handle) {
    const router = this.handleAddRouter(path, handle);
    this.routers.post.push(router);
  }

  use(path, handle) {
    const router = this.handleAddRouter(path, handle);
    this.routers.all.push(router);
  }
  • 每次添加以前,先觸發一次handleAddRouter,若是是 path 爲空的中間件,直接傳入函數的,那麼 path 幫它設置成'/'
  • 咱們還遺留了一個點,next的實現,由於咱們如今加了all這個數組後,意味着可能有多箇中間件,那麼可能一次請求打過來,就要觸發多個路由
這裏要注意,promise.then 源碼實現和 express 的 next、以及 koa 的洋蔥圈、redux 的中間件實現,有着一丁點類似,當你能真的領悟先後端框架源碼時候,發現大都類似
  • 閱讀個人文章,足以擊破全部先後端源碼.並且能夠手寫出來, 咱們只學最核心的,抓重點學習,野蠻生長!

實現next

  • 思路:數組

    • 首先要找到全部匹配的路由
    • 而後逐個執行(看 next 的調用)
  • 定義search方法,找到全部匹配的路由
search(method, url) {
    const matchedList = [];
    [...this.routers[method], ...this.routers.all].forEach((item) => {
      item.path === url && matchedList.push(item.handle);
    });
    return matchedList;
  }

  cb() {
    return (req, res) => {
      const method = req.method.toLowerCase();
      const url = req.url;
      const matchedList = this.search(method, url);
    };
  }
  • matchedList就是咱們想要找到的全部路由
  • 爲了完成next,咱們要將req ,res , matchedList存入閉包中,定義handle方法
handle(req, res, matchedList) {
    const next = () => {
      const midlleware = matchedList.shift();
      if (midlleware) {
        midlleware(req, res, next);
      }
    };
    next();
  }
  cb() {
    return (req, res) => {
      const method = req.method.toLowerCase();
      const url = req.url;
      const matchedList = this.search(method, url);
      this.handle(req, res, matchedList);
    };
  }

  • 這樣咱們就完成了next方法,只要手動調用 next 就會調用下一個匹配到的路由回調函數

  • 不到一百行代碼,就完成了這個簡單的express框架

寫在最後

  • 只要你根據我這些文章去認真本身實現一次,一年內拿個 P6 應該沒什麼問題
  • 大道至簡,但願你能經過這些文章真的學到框架的原理,進而本身能寫出一些框架,走向更高的層級
  • 我是Peter,曾經 20 萬人超級羣桌面軟件的架構師,如今就任於明源雲,擔任分公司前端負責人,目前深圳這邊須要招聘兩位中高級前端,3D數據可視化方向,期待你的到來
  • 若是感受本文對你有幫助,別忘了點個在看關注. 咱們的技術團隊也會不斷產出原創文章, 一塊兒見證各位的成長
相關文章
相關標籤/搜索