基于React+NodeJs+MongoDB的简易留言板(一)

先放出这次小项目的最后效果:http://sliwey.duapp.com/index.html
源码地址:https://github.com/sliwey/ReactMsgBoard

这算是这段时间的学习总结,所以还是按照我的实际学习情况一步步来。我的学习时间线是NodeJs->MongoDB->React,于是我在标题后面加了个“(一)”,所以这篇也可以看做是“NodeJs篇”。

NodeJs已经不是什么新东西了,现在还不知道NodeJs的前端肯定不是好设计师,所以关于NodeJs的介绍和安装什么的我也就不赘述了,而深入的嘛,我也不知道,so…直接进入主题吧,不过进入主题前,得先明确下目标,本次的目标是用NodeJs实现一个动态服务器(或者叫应用服务器?)。

既然要实现的是一个动态服务器,那么我们先从静态服务器开始,关于NodeJs静态服务器的搭建可以看朴灵大大在11年的文章Node.js静态文件服务器实战,只要看到MIME类型支持就可以了,对于本次项目,这些内容已经足够,后面的内容感兴趣的可以继续研究。

按照朴大大的代码,运行后,输入url,你会发现命令行工具中会丢出句path.exists is now called 'fs.exists'.,解决方法很明显,就是把server方法中的path.exists改成fs.exists。所以,现在app.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
//app.js
var http = require('http'),
fs = require("fs"),
url = require("url"),
path = require("path"),
mime = require("./mime").types;

http.createServer(function (request, response) {
var pathname = url.parse(request.url).pathname;
var realPath = "public" + pathname;
var ext = path.extname(realPath);

ext = ext ? ext.slice(1) : 'unknown';
var contentType = mime[ext] || "text/plain";

fs.exists(realPath, function (exists) {
if (!exists) {
response.writeHead(404, {
'Content-Type': 'text/plain'
});
response.write(pathname + " was not found on this server.");
response.end();
} else {
fs.readFile(realPath, "binary", function (err, file) {
if (err) {
response.writeHead(500, {
'Content-Type': 'text/plain'
});
response.end(err);
} else {
response.writeHead(200, {
'Content-Type': contentType
});
response.write(file, "binary");
response.end();
}
});
}
});
}).listen(8888);

console.log("Server running");

再次运行,OK,现在就能访问在public文件夹下的文件了,先在public文件夹下新建个index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>MsgBoard</title>
</head>
<body>
<form>
<input type="text" />
<input type="submit" />
</form>
</body>
</html>

保存后,在浏览器中输入http://localhost:8888/index.html,搞定!

接下来就是要让我们的服务器“动”起来,怎么“动”呢?就是能处理请求,访问数据库什么的了,本篇先完成处理请求的功能,至于怎么跟数据库“摩擦”,到下篇再提。

我们通常处理的HTTP请求都是GET或者POST方式的,所以就只简单实现对这两种请求的处理。

既然最终要实现的是留言板,那就就要有提交留言和显示留言的功能,那么显而易见,提交时用POST请求,拉取信息时用GET请求。那怎么处理请求呢?不要着急,在这之前,我们先重构下现在的代码,以方便之后的开发。

1
2
3
4
5
6
7
8
9
10
11
//app.js
var server = require("./server");
var router = require("./router");
var requestHandlers = require("./requestHandlers");

var handle = {};
handle["/"] = requestHandlers.start;
handle["/save"] = requestHandlers.save;
handle["/list"] = requestHandlers.list;

server.start(router.route, handle);

app.js是入口文件,在这里可以分发路由,server模块用来完成服务器的基本配置,router模块用来处理是静态文件的访问还是功能性的请求,功能性请求的具体处理方法由requestHandlers模块完成实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//server.js
var http = require("http");
var url = require("url");

function start(route, handle) {
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname;

route(handle, pathname, request, response);
}

http.createServer(onRequest).listen(8888);
console.log("Server has started.");
}

exports.start = start;

server.js就是简单的服务器配置,以及对route方法的调用。

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
//router.js
var fs = require("fs");
var path = require("path");
var mime = require("./mime").types;

function route(handle, pathname, request, response) {
console.log("About to route a request for " + pathname);
if (typeof handle[pathname] === 'function') {
handle[pathname](request, response);
} else {
var realPath = "public" + pathname;
var ext = path.extname(realPath);

ext = ext ? ext.slice(1) : 'unknown';
var contentType = mime[ext] || "text/plain";

fs.exists(realPath, function (exists) {
if (!exists) {
response.writeHead(404, {
'Content-Type': 'text/plain'
});
response.write(pathname + " was not found on this server.");
response.end();
} else {
fs.readFile(realPath, "binary", function (err, file) {
if (err) {
response.writeHead(500, {
'Content-Type': 'text/plain'
});
response.end(err);
} else {
response.writeHead(200, {
'Content-Type': contentType
});
response.write(file, "binary");
response.end();
}
});
}
});
}
}

exports.route = route;

route方法通过传入的handlepathname来判断当前请求是文件访问还是功能性请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//requestHandlers.js
function start(request, response) {
console.log("strat method");
response.writeHead(200, {"Content-Type": "text/html"});
response.write("<div>Hello World</div>");
response.end();
}

function save(request, response) {
//svae comment
}

function list(request, response) {
//list comments
}

exports.start = start;
exports.save = save;
exports.list = list;

requestHandlers.js中就是对各个功能性请求的处理。

现在,再次执行node app命令,在浏览器中输入http://localhost:8888/,就能看到requestHandlers.js中的start方法所返回的Hello World了。

OK,我们的重构到这就算是完成了,不过这也并不是什么最佳方案,但至少逻辑上看起来清晰了很多,完成留言板的功能是足够了。

接下来回答之前提出的问题上来,怎么处理请求。先从GET请求开始,众所周知,GET请求的参数是url中跟在?后面的那一串key=value,类似于:http://www.xx.com?param1=1&parma2=2,那么只要处理param1=1&parma2=2这串字符就好了。直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//requestHandlers.js
var url = require("url"),
util = require("util");

//...

function list(request, response) {
var params = url.parse(request.url, true).query;

response.writeHead(200, {"Content-Type": "application/json"});
response.write(util.format('%j', params));
response.end();
}

//...

记得先require所需要的url模块和util模块,重启下,输入http://localhost:8888/list?page=1

好了,接着我们处理POST请求,POST请求的参数都在请求体中,而不是直接显示在url上的,那怎么接收呢?NodeJs是事件驱动的,所以提供了data事件和end事件分别用来处理数据接收中和数据接收完成的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//requestHandlers.js
var querystring = require("querystring");

//...

function save(request, response) {
var str = '';

request.on("data", function(chunk) {
str += decodeURIComponent(chunk);
});

request.on("end", function() {
var param = querystring.parse(str);

response.writeHead(200, {"Content-Type": "text/html"});
response.write(util.format('%j', param));
response.end();
});
}

//...

先把index.html中的代码稍作修改:

1
2
3
4
<form action="/save" method="post">
<input type="text" name="msg" />
<input type="submit" />
</form>

重启下,访问http://localhost:8888/index.html,在输入框中随便输入点什么:

点击“提交”

搞定,本篇文章到这基本上就已经结束了,我们用NodeJs实现了一个简易的动态服务器,迈出了这次留言板项目的第一步。

第一次写这么长的文章,还是蛮累的,真是好佩服那些博客产出量很高的大大。继续努力!

本文为原创文章,如有不足之处欢迎批评指正。
欢迎转载,转载请注明源地址:qianliwei.com/2014/12/04/react-msgboard-1/