08.nodejs封裝一個類似express路由

nodejs封裝一個類似express路由

路由

如前面文章所述,路由是前端瀏覽器訪問路徑,後端程序解析訪問路徑並返回其對應的頁面,這一實現過程稱爲路由。express實現的路由如下:

app.get("index",function(req,res){
    res.send("hello express")
})

假設運行在本地,以上程序實現訪問 http://127.0.0.1/index時,後端nodejs執行對應的function也就是返回helloworld.

這種方式定義和解析路由非常方便且清晰明瞭。

與thinkphp的對比

之前開發網站使用的thinkphp框架是MVC架構,如果使用默認配置時,路由是根據 模塊 -> 控制器 -> 下屬文件來確定訪問路徑與處理函數的關係的,比如有文件admin->user->list存在,那麼訪問admin/user/list`tp框架就會運行list.php文件中的函數來執行處理邏輯並返回執行結果。

在thinkPHP中如果想要自定義路徑實現訪問http://127.0.0.1/abc來執行admin->user->list,需要修改rewrite配置,而nodejs的express框架默認就採用自定義的配置,也就是可以直接指定http://127.0.0.1/abc的處理函數,如上例所示。

實現

這篇文章的目的是我們要用nodejs來自己實現封裝一個類似exprss的路由。

自定義路由文件主要用到了nodejs的內置模塊:url、fs、http和模板引擎ejs模塊。

路由文件單獨封裝爲一個文件:express_router.js,app一個文件:app.js。

思路

express_router主要有兩個功能,第一是註冊路由處理函數,第二是解析並執行瀏覽器請求。

註冊路由處理函數是利用全局變量來實現的,這裏利用到了js的閉包實現:

var router = function (req, res) {
  var self = this;//保存this,用來實現閉包後的訪問
  this._get = [];
  this._post = [];
    
  var handle = function (req, res) {
  ......
  }
......  
  return handle()
}

_get與_post是兩個數組變量,用於保存get請求方法和post請求方法的路由與處理函數鍵值對,如本文第一個程序片段中的index與對應的function

handle是返回給app的實例,也就是用來處理請求的地方。

有了存儲空間,那麼必須有對應的操作方法,以get爲例如下,post類似。

  handle.get = function (str, callback) {
    if (!str.startsWith("/")) {
      str = "/" + str;
    }
    if (!str.endsWith("/")) {
      str = str + "/";
    }
    self._get[str] = callback;
  }

路由的解析有handle函數內部來實現,

var handle = function (req, res) {
    rewriteRes(res);//爲什麼修改形參卻會修改了實參???
    var reqUrl = url.parse(req.url, true);
    var pathname = reqUrl.pathname;
    if (!pathname.startsWith("/")) {
      pathname = "/" + pathname;
    }
    if (!pathname.endsWith("/")) {
      pathname = pathname + "/";
    }
    console.log("請求路徑是:", pathname);

    var method = req.method.toLowerCase();
    console.log("請求方法是:", method);

    console.log("_post", self._post);
    if (self["_" + method][pathname]) {
      var query = req.query;
      if (method == "get") {
        req.query = query;
        console.log("請求參數是:", req.query);
        self["_" + method][pathname](req, res);
      } else if (method == "post") {
        var chunk = "";
        req.on("data", function (data) {
          chunk += data;
        })
        req.on("end", function () {
          // console.log("chunk is:", chunk)
          try {
            let queryData = {}
            // chunk is: username=asfas&password=12134
            let chunkArr = chunk.split("&");
            // console.log("chunkArr", chunkArr);
            chunkArr.forEach((item) => {
              let key = item.split("=")[0];
              let val = item.split("=")[1];
              queryData[key] = val;
            })
            // console.log("queryData", queryData);
            req.query = queryData;
            //如果要實現程序健壯,這裏需要怎麼處理。
            console.log("請求參數是:", req.query);
            self["_" + method][pathname](req, res);
          } catch{
            console.log("post傳參解析錯誤")
          }
        })
      }
    } else {
      console.log("訪問的pathname不存在:", pathname);
      res.send("404");
    }

首先拿到請求的pathname和請求方法,然後判斷對應請求方法數組中是否有以pathname爲鍵值的元素,如果有,那麼進行下一步查詢參數的解析,get和post的參數解析方法不同,get直接可以按到,post是通過流數據來拿到的。無論通過哪種方式,最終執行開始註冊的callback函數來執行處理並返回給瀏覽器數據。這就是簡易的express路由實現。

完整程序

express_route.js

var url = require("url");

//res的rewrite
var rewriteRes = function (res) {
  res.send = function (data) {
    res.writeHead(200, { "Content-Type": "text/html;charset='utf-8'" });
    res.end(data);
  }
}

var router = function (req, res) {
  var self = this;//保存this,用來實現閉包後的訪問
  this._get = [];
  this._post = [];


  var handle = function (req, res) {
    rewriteRes(res);//爲什麼修改形參卻會修改了實參???
    var reqUrl = url.parse(req.url, true);
    var pathname = reqUrl.pathname;
    if (!pathname.startsWith("/")) {
      pathname = "/" + pathname;
    }
    if (!pathname.endsWith("/")) {
      pathname = pathname + "/";
    }
    console.log("請求路徑是:", pathname);

    var method = req.method.toLowerCase();
    console.log("請求方法是:", method);

    console.log("_post", self._post);
    if (self["_" + method][pathname]) {
      var query = req.query;
      if (method == "get") {
        req.query = query;
        console.log("請求參數是:", req.query);
        self["_" + method][pathname](req, res);
      } else if (method == "post") {
        var chunk = "";
        req.on("data", function (data) {
          chunk += data;
        })
        req.on("end", function () {
          // console.log("chunk is:", chunk)
          try {
            let queryData = {}
            // chunk is: username=asfas&password=12134
            let chunkArr = chunk.split("&");
            // console.log("chunkArr", chunkArr);
            chunkArr.forEach((item) => {
              let key = item.split("=")[0];
              let val = item.split("=")[1];
              queryData[key] = val;
            })
            // console.log("queryData", queryData);
            req.query = queryData;
            //如果要實現程序健壯,這裏需要怎麼處理。
            console.log("請求參數是:", req.query);
            self["_" + method][pathname](req, res);
          } catch{
            console.log("post傳參解析錯誤")
          }
        })
      }
    } else {
      console.log("訪問的pathname不存在:", pathname);
      res.send("404");
    }

    return handle;

  }

  //路由處理函數的註冊函數,分爲get和post兩種
  handle.get = function (str, callback) {
    if (!str.startsWith("/")) {
      str = "/" + str;
    }

    if (!str.endsWith("/")) {
      str = str + "/";
    }

    self._get[str] = callback;

  }

  handle.post = function (str, callback) {
    if (!str.startsWith("/")) {
      str = "/" + str;
    }

    if (!str.endsWith("/")) {
      str = str + "/";
    }

    self._post[str] = callback;
  }


  return handle;

}
module.exports = router();

app.js

var http = require("http");
var app = require("./model/express_router")
var fs = require("fs")
var ejs = require("ejs")
var server = http.createServer(app);
server.listen(9999);
app.get("/", function (req, res) {
  console.log("訪問到了 /");
  res.send("訪問到了 /");
})
// 網站標題圖片
app.get("favicon.ico", function (req, res) {
  fs.readFile("./view/favicon.ico", function (err, data) {
    if (err) {
      console.log("讀取 favicon.ico 失敗,err:", err);
    } else {
      //這裏應該設置圖片類型
      res.send(data);
    }
  })
})

app.get("/login", function (req, res) {
  ejs.renderFile("./view/login.ejs", {}, function (err, data) {
    if (err) {
      console.log("read login.ejs err:", err);
    } else {
      res.send(data);
    }
  })
})
//接受post請求
app.post("/dologin", function (req, res) {
  console.log("req.query", req.query);
  res.send("<script>alert('登錄完成')</script>")
})
app.get("/register", function (req, res) {
  ejs.renderFile("./view/register.ejs", {}, function (err, data) {
    if (err) {
      console.log("read register.ejs err:", err);
    } else {
      res.send(data);
    }
  })
})

//渲染變量到ejs中
app.get("/result", function (req, res) {
  ejs.renderFile("./view/result.ejs", {
    operate: "渲染變量",
    result: "成功",
  }, function (err, data) {
    if (err) {
      console.log("read result.ejs err:", err);
    } else {
      res.send(data);
    }
  })
})

console.log("自定義的類express應用運行於 http://127.0.0.1:9999")
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章