使用http模块重构微型服务器

之前写过一个用net模块构建的微型服务器,👉链接

现在用http模块来重构一下。👉GitHub仓库

http模块的初步使用

配置文件server.conf,用以配置服务器信息👇

1
2
3
port=12306
page_path=page
static_file_type=.html|.js|.css|.jpg|.png|.gif|.ico|.json

其中的静态资源信息用以后面判断请求是静态资源还是动态资源。

静态资源主要就是一些写好固定的文件,比如写好的html文件、图片、css、js等,

动态资源就涉及到了可能会与业务逻辑处理有关,需要ajax处理或者涉及数据库,动态生成的数据。

这里我们暂且简化概念,把后缀名为.html|.js|.css|.jpg|.png|.gif|.ico|.json的路径请求视为静态数据,其余的视为动态。具体由不同的操作,下文会处理。


配置文件处理config.js,用以处理配置文件信息👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var fs = require('fs');

var conf = fs.readFileSync('./server.conf');
// console.log(typeof conf);//这哥们是个对象
var confArr = conf.toString().split('\n');//将conf字符串化后根据换行符分割成数组
// console.log(confArr);

var globalConfig = {};

for (const i in confArr) {
var itemArr = confArr[i].replace(/\s+/g, "").split('=');//去掉所有空格并用'='拆分
// console.log(itemArr);
globalConfig[itemArr[0]] = itemArr[1];
}

if (globalConfig.static_file_type) {
globalConfig.static_file_type = globalConfig.static_file_type.split("|");
} else {
throw new Error("配置文件异常,缺少:static_file_type");
}
// console.log(globalConfig);

module.exports = globalConfig;

处理完配置文件后导出,长这个样子👇


duntengServer.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
var http = require('http');
var url = require('url');
var fs = require('fs');
var globalConfig = require('./config')


http.createServer(function (request, response) {
console.log('新的连接已建立');
console.log(request.url);

var urlObj = url.parse(request.url);
var pathName = url.parse(request.url).pathname;
var params = url.parse(request.url).query;
var paramsObj = url.parse(request.url, true).query;
console.log(urlObj);
console.log(pathName);
console.log(params);
console.log(paramsObj);
}).listen(globalConfig.port);

console.log(globalConfig);

// 判断是静态数据请求还是动态数据请求
function isStaticsRequest(pathName) {
for (var i = 0; i < globalConfig.static_file_type.length; i++) {
var temp = globalConfig.static_file_type[i];
if (pathName.indexOf(temp) == pathName.length - temp.length) {
return true;
}
}
return false;
}

接下来尝试启动它,浏览器访问http://127.0.0.1:12306/index.html?a=1&b=3

终端输出如下👇


处理静态数据请求

从上一步可以得到浏览器的请求头信息,最主要的就是我们服务端知道了浏览器想要得到index.html的内容,并可以判断出是静态数据请求还是动态数据请求:


写个html文件/page/login.html👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
</script>
</head>
<body>
<h1>hello man!</h1>
<img src="./cheng.png" alt="" />

</body>
</html>


duntengServer.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
var http = require('http');
var url = require('url');
var fs = require('fs');
var globalConfig = require('./config')


http.createServer(function (request, response) {
console.log('新的连接已建立');
console.log(request.url);

var urlObj = url.parse(request.url);
var pathName = url.parse(request.url).pathname;
var params = url.parse(request.url).query;
var paramsObj = url.parse(request.url, true).query;
console.log(urlObj);
console.log(pathName);
console.log(params);
console.log(paramsObj);
console.log(isStaticsRequest(pathName));

if (isStaticsRequest(pathName)) {
// 处理静态数据请求
try {
var dataFile = fs.readFileSync(globalConfig.page_path + pathName);
response.writeHead(200);
response.write(dataFile)
response.end();
} catch (error) {
response.writeHead(404);
response.write("<html><body><h1>404 NotFound</h1></body></html>");
response.end();
}
} else {
// 处理动态数据请求
console.log('动态请求');
}



}).listen(globalConfig.port);


// 判断是静态数据请求还是动态数据请求
function isStaticsRequest(pathName) {
for (var i = 0; i < globalConfig.static_file_type.length; i++) {
var temp = globalConfig.static_file_type[i];
if (pathName.indexOf(temp) == pathName.length - temp.length) {
return true;
}
}
return false;
}

启动duntengServer.js,浏览器访问<http://127.0.0.1:12306/login.html

浏览器如下👇

在服务器终端打印出以下信息👇

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
 node duntengServer.js
新的连接已建立
/login.html
Url {
protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: null,
query: null,
pathname: '/login.html',
path: '/login.html',
href: '/login.html' }
/login.html
null
[Object: null prototype] {}
true
静态请求
/login.html
新的连接已建立
/cheng.png
Url {
protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: null,
query: null,
pathname: '/cheng.png',
path: '/cheng.png',
href: '/cheng.png' }
/cheng.png
null
[Object: null prototype] {}
true
静态请求
/cheng.png

可见,每请求一次静态数据就会创建对应的http链接,这里一共建立了两个http请求链接,分别是对login.htmlcheng.png


处理动态数据请求

到这一步为止,就完成了对客户端请求静态数据的响应处理,接下来搞一下动态的数据请求处理。

这里以一个ajax请求为例子。

page/login.js👇

1
2
3
4
5
6
7
8
9
10
11
12
13
window.onload = function () {
xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", "/getData", true);
xmlHttp.send(null);

xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
console.log(xmlHttp.responseText);
console.log(typeof xmlHttp.responseText);

}
}
}

这样login.js就触发了对http://127.0.0.1:12306/getData的请求,这个路径都不符合server.conf中对静态数据的限定,所以视为动态请求。

login.html中引入login.js👇

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="./login.js"></script>
</head>

<body>
<h1>hello man!</h1>
<img src="./cheng.png" alt="" />
</body>
</html>


duntengServer.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
var http = require('http');
var url = require('url');
var fs = require('fs');
var globalConfig = require('./config')


http.createServer(function (request, response) {
console.log('新的连接已建立');
console.log(request.url);

var urlObj = url.parse(request.url);
var pathName = url.parse(request.url).pathname;
var params = url.parse(request.url).query;
var paramsObj = url.parse(request.url, true).query;
console.log(urlObj);
console.log(pathName);
console.log(params);
console.log(paramsObj);
console.log(isStaticsRequest(pathName));

if (isStaticsRequest(pathName)) {
console.log("静态请求");
console.log(pathName);
// 处理静态数据请求
try {
var dataFile = fs.readFileSync(globalConfig.page_path + pathName);
response.writeHead(200);
response.write(dataFile)
response.end();
} catch (error) {
response.writeHead(404);
response.write("<html><body><h1>404 NotFound</h1></body></html>");
response.end();
}
} else {
// 处理动态数据请求
console.log('动态请求');
console.log(pathName);
}



}).listen(globalConfig.port);


// 判断是静态数据请求还是动态数据请求
function isStaticsRequest(pathName) {
for (var i = 0; i < globalConfig.static_file_type.length; i++) {
var temp = globalConfig.static_file_type[i];
if (pathName.indexOf(temp) == pathName.length - temp.length) {
return true;
}
}
return false;
}

启动之,浏览器访问http://127.0.0.1:12306/login.html,可以看到👇

可以看到对动态请求的监测和处理是成功了。

从duntengServer.js中抽离出业务处理代码

上文中客户端发起了数据请求,比如login.html-->login.js-->ajax我们直接在duntengServer.js中处理了针对它的业务处理(打印出了/getData的路径),但是这是不合适的,对于这种业务处理的代码应该交由/web/loginController.js来处理。所以应该在/web/loginController.js中写针对login这一功能的业务处理代码,然后在duntengServer.js中require这个/web/loginController.js。如下👇

📌/web/loginController.js

1
2
3
4
5
6
7
function getData(data) {
console.log("666~");
console.log(data);
// 或者是存储数据等其他什么操作,这里只以打印操作为例
}

module.exports = getData;

📌duntengServer.js


但是如果一个项目里面除了login,还有其他很多的业务操作,像上面这样子一个一个地往duntengServer.js里手动引入会很繁琐,所以要想办法自动引入👇


进一步优化实现自动识别路径和处理方法的映射

项目分层架构

📌创建一个/loader.js,它负责将/web下所有业务处理 js 文件中的路径与方法函数之间的映射存进一个Map中👇

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
var fs = require('fs');
var globalConfig = require("./config");

var pathMap = new Map();//用于管理全局的路径请求和对应方法(操作)的映射

var files = fs.readdirSync(globalConfig['web_path']); //读取出了web路径下的所有文件名
// console.log(files);

for (var i = 0; i < files.length; i++) {
var temp = require('./' + globalConfig['web_path'] + '/' + files[i]);
if (temp.path) {
for (var [key, value] of temp.path) {
// console.log("key为 " + key);
// console.log("value为 " + value);
if (pathMap.get(key) == null) {
pathMap.set(key, value);
} else {
throw new Error("重复设置了pathMap,key为" + key + "的路径和方法的映射已存在,请检查你的代码是否针对" + key + "路径设置了多个处理方法");
}
}
}
}
// console.log(pathMap);

module.exports = pathMap;

📌web/loginController.js

1
2
3
4
5
6
7
8
9
var path = new Map();

function getData(request, response) {
response.writeHead(200);
response.write("hey kong, can u hear me?");
response.end();
}
path.set("/getData", getData);
module.exports.path = path;

📌web/test.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

var path = new Map();

function testA(request, response) {
response.writeHead(200);
response.write("This is testA");
response.end();
}

function testB(request, response) {
response.writeHead(200);
response.write("This is testB");
response.end();
}

path.set("/testA", testA);
path.set("/testB", testB);
module.exports.path = path;

📌duntengServer.js

启动服务器duntengServer.js,浏览器访问http://127.0.0.1:12306/login.html👇


浏览器访问http://127.0.0.1:12306/testA和<http://127.0.0.1:12306/testB 👇



生成运行日志

📌 log.js

1
2
3
4
5
6
7
8
9
10
11
12
13
var fs = require("fs");
var globalConfig = require("./config");

var fileName = globalConfig.log_path + globalConfig.log_name;

function log(data) {
var time = new Date().toLocaleString();
console.log(data);
fs.appendFile(fileName, time + " " + data + "\n", { flag: "a" }, function () { });

}

module.exports = log;

duntengServer.js中引用log.js,在服务启动的时候调用log函数进行日志的更新写入。