0%

封装类express服务

之前我们完成了以下功能,并做了抽离

  • 响应各种类型的文件
  • 路由业务的扩展
  • 404处理

但是缺点还是很多的,比如

  • 没有对不同的请求类型进行分类处理
  • 业务逻辑不够清晰
  • 代码冗余

我们可以基于之前的功能,封装一个类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响应信息
    • 下文讲述的封装,将此API压入app内

封装版本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();
//类express服务处理
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("没有找到该资源!");
}
};
//API
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); //异步执行,下面的请求已经被压入到封装方法的G中

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; //基本服务不能正常执行,置为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) { //若基本服务不能正常执行,再执行express服务部分
……
FLAG = true; //最后要置为初始值,让下个请求依旧正常逻辑执行
}
};
……
}
module.exports = expressServe();

封装版本3

问题

上述封装的express基本服务版本2,虽然能让资源正常的加载,但还不够完美。

问题概述:

​ 如果我想要在执行defaultServe读取文件的同时,又要在外部使用express服务部分进行一些拦截,要怎么实现呢?

​ 这个功能是非常有必要实现的,因为我们往往要完成的不只是展示,通常还要做一些操作。

解决

​ 这里我将思路重新整理了一下,之前都是先执行defaultServe,再执行express服务部分,但既然我们有可能要使用路由拦截,就不如将执行顺序调换一下,当然不只是简单的调换,还要做一些处理,保证逻辑能够正常执行。

  1. 由于要先执行express服务部分,所以在执行对应路由操作之前,先判断有没有这个callback
  2. express服务部分中最外层的else取消掉。
  3. defaultServe默认服务的catch部分,要对错误进行处理,因为是放在express服务后执行,所以不再像原来一样,将错误交给express服务,这也就是为什么要把 express服务部分的else取消掉 的原因
  4. 由于调换了执行顺序,此时也就不需要版本二的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) {
//defaultServe最后执行,要对错误进行处理
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); //默认服务
};
//API
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 ,在校大学生,立志成为资深前端工程师,欢迎大家一起交流、学习。后续更新更多文章,请持续关注哦~

原创文章,文笔有限,才疏学浅,文中若有不正之处,速速告知。