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")
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章