对比原生Node封装的Express路由 和 express框架路由
文章目录
- 前言
- 一、routes.js
- 1.引入模块
- 2.changesRes() - send()
- 3.getFileMime() - type()
- 4.initStatic
- 5.server()
- app() - 注册中间件
- app.get() - get()
- app.post() - post()
- app.static()
- 二、app.js
- 1.引入模块
- 2.简化 createServer()
- 3.拟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()方法传参,routes模块的app.get()方法接收到参数后将传入的回调函数作为参数cb注册到对象G._get中;res.send("首页信息");
})
依据用户跳转到的URL, 调用不同的方法向app.get()或app.post()传回调函数, 并且在G._post 或 G._get 里注册这些回调函数, 它们就相当于是express的路由中间件了.
JS的解析是从上至下, 而一次请求只会触发一次createServer(), 而res.send()或res.join()这种方法如果执行完毕就自动返回, 这一轮响应到这就结束了, 后面的路由根本没有机会接受匹配, so, 不要把"/"的路由写在最上面.
与Vue的路由不太像, Vue-router(我是说路由表那边)不会出现这种结果, Vue路由的结构更加像是标准版的express路由:
用express模块的Router()注册路由对象(router是个函数, 也可以作为中间件), 在Vue中这个"中间件"应该可以看作Vue的路由表routes.
const router = express.Router();
然后在这个中间件上安插各条路由, 也就像在routes数组中配置各条路由.
//一般会把路由中间件抽离出来, 这里为了节省篇幅没有进行;
router.get('/', (req, res, next) => {res.send('hello');
});//use会出现正则匹配错误的情况出现, router并不会.
router.get('/index', (req, res, next) => {res.send('index pages');
});
rouer对象就像是脊椎, 而这些中间件就像是肋骨.
小路由卡上去以后, 将router暴露.
module.exports = router;
就像vue中暴露路由表至app.js以注册到全局:
app.use('/', router);
三、总结
这篇拖了好久, 它几乎占用了这几天拿来写博客的所有时间, 我的最初方案是直接记录下拟express路由的制作方案就结束的, 但因为觉得没有意义马上否定了这个方案, 如果不进行任何对比, 不明白它是如何模拟express路由, 与express有何相似, 又有何区别的话, 这种记录毫无意义, 我想自己以后也不会回来看的.
另外就是因为方法封的比较多, 整篇文章看起来有点乱七八糟的, 那些很长的方法拆开又不行(点名批评app), 但太长大家又不愿意看, 里面再加一堆注释这看起来就更眼花缭乱了, 我在想如何去解决这个问题(虽然最后也没解决好).
然后就打算拖一拖等到express学的差不多再回来补上封装的各个方法与express的对比(事实是每天都要回来改一改这篇文章), 这期间我也完成了几篇小博文, 但是觉得都有点瑕疵就没敢发出去, 我觉得哪怕写一些浅显的知识, 但至少不能有错误.
然后…然后就咕咕咕了…en…
最后, 如果这篇文章帮到了你, 我很高兴.
对比原生Node封装的Express路由 和 express框架路由相关推荐
- php mvc 路由,PHP MVC框架路由学习笔记
文章主要介绍了PHP MVC框架路由学习笔记的相关资料,需要的朋友可以参考下. 提到PHP开发web,自然离不开开发框架,开发框架为我们提供了灵活的开发方式,MVC层分离,业务解耦等... 第一篇先来 ...
- Node 学习 | Day03 express (初识Express、Express 路由、Express 中间件、使用 Express 写接口)
Express 初识Express 1.1 Express 简介 1.1.1 什么是 express 1.1.2 进一步理解 Express 1.1.3 Express可以做什么 1.2 Expres ...
- 原生node创建路由的分层
原生node创建路由的分层 为了方便维护 可以将路由内容跟创建路由以及服务端基本结构分来 可以分为四层 服务端的基本基本结构 这个结构就能通过上面暴露的内容 拼成完整的,这里完全是createServ ...
- Node.js—Express、Express 路由 、Express 中间件、使用 Express 写接口
目标: 能够使用 express.static() 快速托管静态资源 能够使用 express 路由精简项目结构 能够使用常见的 express 中间件 能够使用 express 创建API接口 能够 ...
- Node.js 系列:构建原生 Node.js 应用
原生 Node.js 应用 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境 Node.js 使用了一个事件驱动.非阻塞式 I/O 的模型,使其轻量又高效 Nod ...
- Node.js中的egg入门、egg路由、egg控制器、egg服务、egg中间件
目录 1 egg入⻔ 1.1 初始化 1.2 目录结构 1.3 内置对象 Application Context Request Response Controller Service Helper ...
- 不可抗力/(ㄒoㄒ)/~~ 开始学习node全栈<四>express Web框架
初始Express 基础概念 Express 是基于Node.js平台 快速.开放.极简的Web开发框架 通俗的理解:express 的作用和Node.js 内置的http模块类似,是专门用来创建We ...
- 基于 Node.js 平台的web开发框架-----express
express官网:---->传送门 express express框架有许多功能,比如路由配置,中间件,对于想配置服务器的前端来说,非常便捷 自从node发展之后,基于nodejs的开发框架 ...
- Express新建工程以及新建路由规则、匹配路由规则、控制权转移
场景 npm提供了大量的第三方模块,其中不乏许多Web框架,我们没有必要重复发明轮子, 因而选择使用Express作为开发框架,因为它是目前最稳定.使用最广泛,而且Node.js官 方推荐的唯一一个W ...
最新文章
- 「软件项目管理」一文详解软件项目成本计划
- 推荐一些经过实践检验的学习方法
- Java处理微博数据集中的超链接
- java list 数据分离_Java(Android)数据结构汇总(一)-- List(下)
- c语言答辩中期报告,安徽工程大学毕业设计(论文)中期检查总结
- oracle11.2.03,升级Oracle11.2.0.3后遭遇ORA-00600[kfioTranslateIO03][17090]
- Javascript:学习笔记
- 院士在西湖大学分享科研经历:读博过程中也曾想放弃,因为没有任何进展
- 《计算机网络》谢希仁第七版知识点总结
- stm32的语音识别_基于STM32的嵌入式语音识别模块设计
- android studio深色模式,Xamarin 中的深色模式
- 快速迭代内部学习心得
- 执行npm install报错:npm ERR! code EINTEGRITY,npm ERR! 最彻底,最实用的方法就是更新node版本
- www是什么,http是什么,到底什么区别?
- 计算机黑屏启动超慢,电脑开机慢黑屏时间长怎么解决
- tensorflow实现非线性拟合
- Unity 镜像sprite
- 百度搜索结果显示“我喜欢”按钮
- 冰河联合猫大人又出版一本分布式事务领域的开山之作,这是要再次起飞了吗?
- 标签体系应用及设计思路