之前我们完成了以下功能,并做了抽离
但是缺点还是很多的,比如
- 没有对不同的请求类型进行分类处理
- 业务逻辑不够清晰
- 代码冗余
我们可以基于之前的功能,封装一个类express的服务,这样管理起来就比较方便,并且按不同的请求类型进行了分类。
express
既然要封装类express服务,就要先了解express的基本使用方法。
路由处理(在真正使用express时,下面的app为express实例):
app.get("path",(req,res)=>{……})
app.post("path",(req,res)=>{……})
- 其他请求处理在此省略
扩展API:
req.body
获取post请求传参
res.send(data)
向Browser响应信息
封装版本1
外部使用app.get/post
时,会传入对应的处理函数,我们会根据 请求类型、pathName
压入到内置Global
变量的对应属性中,在接收请求时,再根据 请求类型、pathName
找到并执行对应的处理函数即可。
当请求类型为POST
时,涉及到参数的接收,以及API的配置。
向app添加静态方法get/post
,供外部使用。
我们将封装的功能抽离到外部./modules/expressServe.js
以下为./modules/expressServe.js
代码👇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| const _http = require("http"); const _url = require("url"); const _path = require("path"); const _fs = require("fs");
const staticPath = "public"; const defaultMimePath = `./modules/mime.json`;
function getFileType(pathName, mimePath = defaultMimePath) { let mime = JSON.parse(_fs.readFileSync(mimePath)); let extName = _path.extname(pathName); return mime[extName]; }
function defaultServe(req, res) { let reqUrl = req.url == "/" ? "/index.html" : req.url; let pathName = _url.parse(reqUrl).pathname; let fileType = getFileType(pathName); try { if (reqUrl != "/favicon.ico") { res.writeHead(200, { "Content-Type": `${fileType};charset="utf-8"` }); let data = _fs.readFileSync(staticPath + pathName); res.end(data); } } catch (err) {} }
function expressServe() { const G = { _get: {}, _post: {}, staticPath }; let app = function (req, res) { defaultServe(req, res); let pathName = _url.parse(req.url).pathname; let reqMethod = req.method.toLowerCase(); if (G[`_${reqMethod}`][pathName]) { if ((reqMethod = "get")) { G[`_get`][pathName](req, res); } if ((reqMethod = "post")) { let postData = ""; req.on("data", (chunk) => { postData += chunk; }); req.on("end", () => { req.body = postData; G[`_post`][pathName](req, res); }); } } else { res.writeHead(200, { "Content-Type": `text/html;charset="utf-8"` }); res.write(`<head><meta charset="UTF-8" /></head>`); res.end("没有找到该资源!"); } }; app.get = (pathName, callback) => { G["_get"][pathName] = callback; }; app.post = (pathName, callback) => { G["_post"][pathName] = callback; }; app.send = (req, res, content) => { res.writeHead(200, { "Content-Type": `text/html;charset="utf-8"` }); res.write(`<head><meta charset="UTF-8"/></head>`); res.end(content); }; return app; } module.exports = expressServe();
|
入口文件使用:👇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| const _http = require("http"); const _url = require("url"); const _path = require("path"); const _fs = require("fs");
const staticPath = "public"; const _ejs = require("ejs");
const app = require("./modules/createDefaultServer"); const port = 8086; _http.createServer(app).listen(port);
app.get("/login", (req, res) => { let Params = { content: "Ashuntefannao", list: ["login", "testEjs", "Ashun"], }; _ejs.renderFile("./views/login.ejs", Params, (err, data) => { res.writeHead(200, { "Content-Type": `text/html;charset='utf-8'`, }); res.end(data); }); });
app.post("/main.html", (req, res) => { console.log("/main.html send POST Request"); });
console.log(`http://localhost:${port}`);
|
封装版本2
问题
上述封装的express基本服务版本1
,其实有一些问题,细心的同学可能会留意到。
问题概述:
版本1
会导致一些文件不能读取成功,具体原因是什么?我们走一遍对应的逻辑即可
- 首先执行
defaultServe
创建最基本的服务,读取文件信息,如果文件读取不成功并不会立即抛出错误,而是选择交由外部处理,这样才能进行路由业务的扩张
- 然后执行
express服务
的逻辑
表面上好像没什么问题,其实不然,问题产生的关键就在于:发送的任何请求都会将express服务
走一遍
网站肯定要展示信息给用户,展示过程即 defaultServe
读取文件的过程,但 defaultServe
执行完毕后又会直接执行express服务
,而这些读取文件的基本服务我们都是交给defaultServe
处理的,在入口文件
中并没有使用app.get/post
处理,也就不会被压入Global变量
的对应属性中。就会执行下述代码,产生报错信息。
但这些报错信息并不会导致页面显示没有找到该资源!
,而是某些资源不能正常展示,这些报错会反映在服务端,原因是:
- 经过了
defaultServe
读取文件,要使用res.end
响应,而在这里又直接使用了res.write
,而使用res.end
以后就代表该次响应结束了。
1 2 3 4 5 6 7
| if (G[`_${reqMethod}`][pathName]) { …… } else { res.writeHead(200, { "Content-Type": `text/html;charset="utf-8"` }); res.write(`<head><meta charset="UTF-8" /></head>`); res.end("没有找到该资源!"); }
|
有的同学就会提问了:我们在外部使用app.get/post
配置这些路由不就好了吗?
我们不可能对这些基本的服务依次配置,原因有以下两点:
- 实际开发请求是非常非常多的,而且还涉及到动态路由。
- 如果依次配置,封装也就没有意义了
解决
我们需要判断defaultServe
的状态,如果其正常读取文件,则不执行express服务
部分。
反之,defaultServe
不能正常读取,就轮到了扩展路由业务部分,再执行express服务
。
我们定义一个开关变量即可,对应代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| …… let FLAG = true; ……
function defaultServe(req, res) { …… try { …… } catch (err) { FLAG = false; } }
function expressServe() { const G = { _get: {}, _post: {}, staticPath }; let app = function (req, res) { defaultServe(req, res); let pathName = _url.parse(req.url).pathname; let reqMethod = req.method.toLowerCase(); if (!FLAG) { …… FLAG = true; } }; …… } module.exports = expressServe();
|
封装版本3
问题
上述封装的express基本服务版本2
,虽然能让资源正常的加载,但还不够完美。
问题概述:
如果我想要在执行defaultServe
读取文件的同时,又要在外部使用express服务
部分进行一些拦截,要怎么实现呢?
这个功能是非常有必要实现的,因为我们往往要完成的不只是展示,通常还要做一些操作。
解决
这里我将思路重新整理了一下,之前都是先执行defaultServe
,再执行express服务
部分,但既然我们有可能要使用路由拦截,就不如将执行顺序调换一下,当然不只是简单的调换,还要做一些处理,保证逻辑能够正常执行。
- 由于要先执行
express服务
部分,所以在执行对应路由操作之前,先判断有没有这个callback
- 把
express服务
部分中最外层的else
取消掉。
defaultServe
默认服务的catch
部分,要对错误进行处理,因为是放在express服务
后执行,所以不再像原来一样,将错误交给express服务
,这也就是为什么要把 express服务
部分的else
取消掉 的原因
- 由于调换了执行顺序,此时也就不需要版本二的
FLAG
开关了。
整体代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| const _http = require("http"); const _url = require("url"); const _path = require("path"); const _fs = require("fs");
const staticPath = "public"; const defaultMimePath = `./modules/mime.json`;
function getFileType(pathName, mimePath = defaultMimePath) { let mime = JSON.parse(_fs.readFileSync(mimePath)); let extName = _path.extname(pathName); return mime[extName]; }
function defaultServe(req, res) { let reqUrl = req.url == "/" ? "/index.html" : req.url; let pathName = _url.parse(reqUrl).pathname; let fileType = getFileType(pathName); try { if (reqUrl != "/favicon.ico") { res.writeHead(200, { "Content-Type": `${fileType};charset="utf-8"` }); let data = _fs.readFileSync(staticPath + pathName); res.end(data); } } catch (err) { res.writeHead(200, { "Content-Type": `text/html;charset="utf-8"` }); res.write(`<head><meta charset="UTF-8" /></head>`); res.end("没有找到该资源!"); } }
function expressServe() { const G = { _get: {}, _post: {}, staticPath }; let app = function (req, res) { let pathName = _url.parse(req.url).pathname; let reqMethod = req.method.toLowerCase();
if (G[`_${reqMethod}`][pathName]) { if ((reqMethod = "get")) { G[`_get`][pathName] && G[`_get`][pathName](req, res); } if ((reqMethod = "post")) { let postData = ""; req.on("data", (chunk) => { postData += chunk; }); req.on("end", () => { req.body = postData; G[`_post`][pathName] && G[`_post`][pathName](req, res); }); } } defaultServe(req, res); }; app.get = (pathName, callback) => { G["_get"][pathName] = callback; }; app.post = (pathName, callback) => { G["_post"][pathName] = callback; }; app.send = (req, res, content) => { res.writeHead(200, { "Content-Type": `text/html;charset="utf-8"` }); res.write(`<head><meta charset="UTF-8"/></head>`); res.end(content); }; return app; } module.exports = expressServe();
|
入口文件使用:👇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const _http = require("http"); const _url = require("url"); const _path = require("path"); const _fs = require("fs"); const { extname } = require("path"); const port = 5505; const staticPath = "public";
const app = require("./modules/expressServe");
_http.createServer(app).listen(port);
app.post("/main.html", (req, res) => { console.log(`/main.html路由映射的服务`); }); app.get("/ashun", (req, res) => { app.send(req, res, "Ashuntefannao"); });
console.log(`http://127.0.0.1:${port}`);
|
最后
本文到此结束,希望对你有所帮助,我是 Ashun ,在校大学生,立志成为资深前端工程师,欢迎大家一起交流、学习。后续更新更多文章,请持续关注哦~
原创文章,文笔有限,才疏学浅,文中若有不正之处,速速告知。