对比原生Node封装的Express路由 和 express框架路由
Posted 白瑕
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对比原生Node封装的Express路由 和 express框架路由相关的知识,希望对你有一定的参考价值。
文章目录
前言
我将拟express方法和正规的express方法做了很多对比(类似"这个方法是为了实现express的哪个功能")
全篇主要在两个文件里完成, 一个是"父目录/app.js", 另一个是"父目录/module/route.js";
app.js部分因为有个很长的方法会有些繁琐, 那是为了完成路由中间件的注册和阻复注册, 不过我加了很多注释, 我觉得你应该能看懂…
一、routes.js
以下是源码, 我会拆开说的:
const fs = require('fs');
const path = require('path');
let changesRes = function (res) {
res.send = (data) => {
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end(data);
}
}
let getFileMime = function (extname) {
let data = fs.readFileSync('./data/mime.json');
let mimeObj = JSON.parse(data.toString());
return mimeObj[extname];
}
let initStatic = function (req, res, staticPath) {
const { url } = req;
const { host } = req.headers;
const myURL = new URL(url, `http://${host}`);
let pathname = myURL.pathname;
let extname = path.extname(pathname);
if (extname) {
try {
let data = fs.readFileSync('./' + staticPath + pathname);
if (data) {
let mime = getFileMime(extname);
res.writeHead(200, { 'Content-Type': '' + mime + ';charset="utf-8"' });
res.end(data);
}
} catch (error) {
console.log(error)
}
}
}
let server = () => {
let G = {
_get: {},
_post: {},
staticPath: "static",
};
let app = function (req, res) {
changesRes(res);
initStatic(req, res, G.staticPath);
const { url } = req;
const { host } = req.headers;
const myURL = new URL(url, `http://${host}`);
let pathname = myURL.pathname;
let method = req.method.toLowerCase();
let extname = path.extname(pathname);
if (!extname) {
if (G['_' + method][pathname]) {
if (method == "get") {
G['_' + method][pathname](req, res);
} else {
let postData = '';
req.on('data', (chunk) => {
postData += chunk;
})
req.on('end', () => {
req.body = postData;
G['_' + method][pathname](req, res);
})
}
} else {
res.writeHead(404, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end("页面早已离你而去了...");
}
}
}
app.get = function (str, cb) {
G._get[str] = cb;
}
app.post = function (str, cb) {
G._post[str] = cb;
}
app.static = function (staticPath) {
G.staticPath = staticPath;
}
return app;
}
module.exports = server();
1.引入模块
因为原本的static()需要fs文件模块来读取文件.
const fs = require('fs');
const path = require('path');
2.changesRes() - send()
为了实现express的res.send()方法.
把send()放到changesRes()里, 通过在app()中调用changesRes()的手段将send()释放进app.js;
这层changesRes()外壳存在的意义是将内部的send()完好的释放到app()中,并从app()中摄取res.
因为app.js直接调用了send(), 所以send()可以接收到app.js传入的data(也就是renderFile()的执行结果, 即渲染后的页面数据), 而app.js也调用了app()和其内部的changesRes()为send()提供了res;
最后由send()中的end()将data放到页面上.
let changesRes = function (res) {
//在app()内调用changesRes()相当于直接将send()写到app()中;
res.send = (data) => {
res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end(data);
}
}
changesRes()方法也简化了app.js中的路由队列, 现在路由表里只要写一句"res.send(data)"就好(然而偷懒直接传了一个字符串);
3.getFileMime() - type()
算是半个res.type()吧, 这个方法调用后返回这个文件对应的文件类型即Content-Type属性到app()中, 由initStatic()方法进行接收并设置到writeHead里.
或许该说是getFileMime()和initStatic()共同组成了拟res.type();
它接收extname作为参数(别担心, extname会由app()调用传入), 这个参数是从URL里拿到的文件路径所指向文件的扩展名, 因为我们在封装静态web, 针对那些带有文件扩展名的URL进行处理, 至于没扩展名的…
扔给路由吧! (啪)
//根据后缀名获取文件类型;
let getFileMime = function (extname) {
let data = fs.readFileSync('./data/mime.json');
let mimeObj = JSON.parse(data.toString()); //拿到data转换为字符串再转换为对象;
return mimeObj[extname];
//extname是".xxx"的形式, 如果用mimeObj.extname会翻车;
}
拿到后缀名之后readFileSync()利用同步阻塞从外部文件mime.json中读取相应的Content-Type, 读取结果赋值给data.
但是读取到的是16进制的数字码, 先用toString()将其转化为字符串, 再JSON.parse()把它们转换为原本在文件里时候的对象形态, 赋值给mimeObj,这样用起来就舒服多了.
额, 记得return出去…
4.initStatic
一个为了读取文件而设立的方法.
该方法读取URL中的文件路径, 提取文件内容并传给end();
end()内传入执行完毕后要呈现的内容,如果指定了 data 的值,就意味着在执行完 res.end() 之后,会接着执行如下语句:
response.write(data , [对应的字符编码encoding]);
来看看完整方法:
//静态web服务的方法;
let initStatic = function (req, res, staticPath) {
const { url } = req;
const { host } = req.headers;
const myURL = new URL(url, `http://${host}`);//拼凑绝对路径, 并解析;
let pathname = myURL.pathname; //拿到解析结果中的pathname属性即文件路径;
let extname = path.extname(pathname); //利用path模块提供的方法拿到文件路径指向的文件的后缀名;
if (extname) { //如果文件扩展名存在, 判定交由route.js的静态web服务处理; //否则交付至app.js中的路由进行处理;
try {
let data = fs.readFileSync('./' + staticPath + pathname); //将同步读取的文件内容存入变量data;
if (data) {
let mime = getFileMime(extname); //传入文件扩展名,mime被赋值文件类型;
res.writeHead(200, { 'Content-Type': '' + mime + ';charset="utf-8"' });
res.end(data);
}
} catch (error) {
console.log(error); //尝试执行出错, 抓取错误信息输出;
}
}
}
5.server()
外壳负责提供G对象内部的数据和方法;
app()负责依据请求方法调用G中的方法(既路由激活后的处理办法);
app.get()负责将各路由受get请求的处理方法注册入G;
app.post()负责将各路由受post请求的处理方法注册入G;
app.static()负责将静态路径存入G供initStatic(ststicPath)读取路由需要的文件;
let server = () => {
let G = {
_get: {}, //在G里声明空对象_get;
_post: {}, //在G里声明空对象_post;\\
staticPath: "static",
};
//app();
//app.get();
//app.post();
//app.stsatic();
return app;
}
app() - 注册中间件
依据本次请求的方法来决定是调用app.get()注册到G里的回调函数还是调用app.post()注册到G里的回调函数.
这步其实可以看作是express中对路由中间件进行的抽离注册, 然后在合适的时候挂载到Router对象上;
//把app放在server里防止全局污染;
let app = function (req, res) {
changesRes(res);
//相当于书写了如下:
/* res.send = (data) => {
res.writeHead(200, {'Content-Type':'text/html;charset="utf-8"'});
res.end(data);
app.js中可以直接调用send()方法了;
*/
}
initStatic(req, res, G.staticPath); //根据请求的URL读取响应文件.
const { url } = req;
const { host } = req.headers;
const myURL = new URL(url, `http://${host}`);
let pathname = myURL.pathname; //拿取pathname文件路径(相对路径);
let method = req.method.toLowerCase(); //获取本次请求的方法(GET/POST),并转换为小写(get/post)赋值给method;
let extname = path.extname(pathname); //用path模块的自带方法extname()来获取到相对路径所指向文件的扩展名;
if (!extname) { //判定扩展名是否不存在, 存在就留给路由执行;
if (G['_' + method][pathname]) { //G中有无归属于"_post"或"_get"对象还[符合路径]的方法;
if (method == "get") { //判定本次请求方式是否为get(req.method获取请求方式)
G['_' + method][pathname](req, res); //如果是,调用G._get里与路径(比如"/login")同名的方法(路径是从app传入的参数)
} else {
//因为post的请求以流的形式进行, 服务器也是分段接收;
//所以要监听两个阶段点判断是不是传输完成;
let postData = '';
//声明并赋值postData为空字符串预备存储data;
//POST提交数据以流的形式提交,服务器在接收POST数据的时候是分块接收的
//所以用监听阶段点的方式判断是否传输完成;
req.on('data', (chunk) => {
postData += chunk; //用变量chunk接收拿到的数据, 然后填入postData中;
})
req.on('end', () => { //监听end事件, 将postData赋值给res.body中
req.body = postData;
G['_' + method][pathname](req, res);//调用app传入并注册的该页的post型回调函数;
})
}
} else {
//如果没有找到为这个页面的路由注册的任何回调函数, 直接向客户端返回404;
res.writeHead(404, { 'Content-Type': 'text/html;charset="utf-8"' });
res.end("页面早已离你而去了..."); //或者直接G['/404'](req, res);
}
}
}
app.get() - get()
express里可以直接在注册路由的时候规定这条路由只有甚麽方法才能触发:
router.get('/api/list', list);
这里就是为了拟这个功能:
将app.js传入的回调函数注册到G.get, 作为该条路由触发时的处理办法.
GET与POST的提交途径必须分别交由app.get()和app.post(), 防止G中出现覆盖注册;
app.get = function (str, cb) { //接收app传来的参数, 在G._get中为传来的回调函数cb(既"callback的简写")进行注册;
G._get[str] = cb;
}
app.post() - post()
express里可以直接在注册路由的时候规定这条路由只有甚麽方法才能触发:
router.post('/api/list', list);
这里就是为了拟这个功能:
将app.js传入的回调函数注册到G.post, 作为该条路由触发时的处理办法.
GET与POST的提交途径必须分别交由app.get()和app.post(), 防止G中出现覆盖注册;
app.post = function (str, cb) {
//接收app传来的参数, 在G._post中为传来的回调函数cb(既"callback的简写")进行注册;
G._post[str] = cb;
}
app.static()
将app中传入的静态路径存放到G中, 供initStatic()使用.
app.static = function (staticPath) {
//app.static()会在app.js中首先调用, 获取staticPath存入G中,生成G.staticPath, 供initStatic()使用;
G.staticPath = staticPath;//将参数staticPath赋值到G.staticPath中.
}
二、app.js
利用route.js中封装好的拟express方法完成拟express路由的主体结构.
以下是源码, 我会拆开说的:
const http = require('http');
const app = require('./module/route');
const ejs = require('ejs');
let app = function(req,res){
}
http.createServer(app).listen(3000);
app.static("static");
app.get("/login", function (req, res) {
ejs.renderFile('./views/form.ejs', {}, (err, data) => {
res.send(data);
});
})
app.get("/news", function (req, res) {
res.send("展示");
})
app.get("/register", function (req, res) {
res.send("注册页");
})
app.get("/", function (req, res) {
res.send("首页");
})
app.post("/doLogin", function (req, res) {
res.send(req.body);
})
1.引入模块
就是引入依赖, 没什么好说的…
const http = require('http');
//引入route模块,里面有个server方法要用到;
const app = require('./module/route');
//引入ejs模块, 待会要使用renderFile();
const ejs = require('ejs');
2.简化 createServer()
用app()作为createServer的回调函数, 触发请求直接执行app().
//let app = (req,res) => {}
//http.createServer((req, res) => {});
/* 观察可知createServer方法中的function结构与app方法的外框完全相同;
因此app()的回调函数可以放入createServer()内,这样用户一旦在客户端发动请求, 马上就会触发app(); */
http.createServer(app).listen(3000);
3.拟express路由的挂载
在express框架中, 当有多个子路由需要操作的时候, 将子路由挂载到父级路由router然后直接挂载router会是一个更好的选择, 只要几句就好了:
const router = express.Router();
router.get('路径', 路由中间件);
app.use('/', router);
但如果用app.get()一个个来, 在暴露和引入使用时都不会太方便.很遗憾我只能使用这种"一个个来"的方式在app.js里做路由, 因为router对象是express提供的, 而这是拟express:
app.static("static");
//向route模块末的static方法传参静态目录以修改G中的默认静态web目录;
//从而完成在静态web模式下对网页文件的读取;
app.get("/login", function (req, res) {
//app.get()接收到参数后将传入的回调函数cb注册到route.js对象G._get中;
ejs.renderFile('./views/form.ejs', {}, (err, data) => {
//login页面的路由被触发, 渲染路径form.ejs;
res.send(data);
//res.send()将从目标文件读取到的内容data传到客户端;
});
})
app.get("/", function (req, res) {
//在此处向引入的route模块中的app.get()方法传以上是关于对比原生Node封装的Express路由 和 express框架路由的主要内容,如果未能解决你的问题,请参考以下文章