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")