欢迎转载,但请注明出处:http://blog.csdn.net/sysuzjz/article/details/43987289
一个应用由不同模块组成,现在我们就将这几个模块一一道来。
服务器模块
上一节中,我们用了一个使用Node的例子:
var http = require("http");
http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}).listen(8888);
这段代码用于开启一个服务器。http是一个内置的模块,第一行代码使得本地变量http赋值为http模块对象。这也是最核心的模块。
JavaScript有一个特定,就是一切皆对象,函数作为对象的一种,也可以作为函数的参数。我们把其中的匿名回调函数替换成实名函数。这样,我们就可以把上面那个例子的耦合度再度降低。
var http = require("http");
function onRequest(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(8080);
把这段代码放到server.js里,运行node server.js,结果和之前是一模一样的。
再进一步处理,我们把函数进一步封装,把服务器开启包装成一个函数。
var http = require("http");
function start() {
function onRequest(request, response) {
console.log("Request received.");
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(8888);
}
start();
有一点值得注意的是,大部分服务器在访问http://localhost:8888的时候,会顺便访问http://localhost:8888/favicon.ico。所以,回调函数可能会被执行两次。
全局模块
上述服务器模块功能有限,或者说,我们刻意对上述模块进行限制,使其只负责HTTP服务器方面,而不涉及具体业务处理。为了让各模块各自独立,降低耦合度,我们应该设定一个全局的模块,用来各模块之间通信协同。我们把它命名为index.js,内容如下:
var server = require("./server");
server.start();
第一句表示包含了本目录里的server.js里的模块。这样,就只需要通过运行index.js,就能达到我们之前的效果。但是,我们还得先对server.js作点修改来适应这种变化。
var http = require("http");
function start() {
function onRequest(request, response) {
console.log("Request received.");
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(8888);
}
exports.start = start;
我们可以看到,其实变化的就只有最后一句,exports是本文件,或者说,本模块的返回对象,给exports对象添加start方法。这是因为start函数是不能被其他模块直接引用的,但是模块返回对象可以通过require方法暴露在其他模块里。这样,就达到了在全局模块调用其他模块的目的。
我们运行node index.js,会发现,结果和上面的还是一模一样。
路由模块
一般情况下,我们的应用会有多种业务需求,如何通过url,来识别不同请求呢?这就是路由。
不像PHP,PHP不负责服务器部分,所以PHP可以一个文件负责一个业务,请求也是PHP的文件名,例如action="./index.php"。但Node不同,服务器也是由Node来搭建,而且集中在server.js中。因此,我们引入了路由机制。即所有同域下的请求,根据路径来转交给不同的处理函数,这种做法类似于路由器。
我们来介绍两个新的模块:url和querystring,它们负责url的解析。同http模块类似,它们也是内置的模块,所以我们不需要去安装它们。
我们以一个url为例,介绍下各部分和这两个模块的对应关系
接下来我们来编写路由模块。编写router.js文件:
function route(pathname) {
console.log("About to route a request for " + pathname);
}
exports.route = route;
将路由穿插进服务器模块中。这是因为url来自于createServer回调函数里的request对象。
var http = require("http");
var url = require("url");
function start() {
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname;
console.log("Request for " + pathname + " received.");
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(8888);
}
exports.start = start;
怎么将两者协同起来呢?这就要靠全局模块了。
我们先将server.js修改一下,
var http = require("http");
var url = require("url");
function start(route) {
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname;
route(pathname);
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(8888);
}
注意,start函数多出了一个参数,而这个参数在onRequest函数中得到调用,说明这是一个函数,并且参数为路径字符串。而这个函数,其实就是路由模块的返回对象。
我们修改下index.js:
var server = require("./server");
var router = require("./router");
server.start(router.route);
这样,我们又加入了路由模块。并且,服务器依旧只干服务器的活。当然,如果你觉得url的解析不应该放在服务器模块的话,你也可以把request对象当做参数传给route函数,然后在router模块中进行解析。
业务处理模块
我们虽然加入了路由模块,但实际上,它什么都没做。所以,我们还需要一个来做事情的模块,那就是业务处理模块。路由模块根据请求url来决定,交由哪个业务处理函数来执行。假设我们有start和upload两个业务。编写handler.js:
function start() {
console.log("Request handler 'start' was called.");
}
function upload() {
console.log("Request handler 'upload' was called.");
}
exports.start = start;
exports.upload = upload;
可能有些强迫症患者已经发现,如果业务较多的话,下面的exports.xx = xx会异常的多,这对于一个强迫症患者来说简直不能忍,也给维护带来了困难。我们包装一下:
function start() {
console.log("Request handler 'start' was called.");
}
function upload() {
console.log("Request handler 'upload' was called.");
}
var exportObj = {
start: start,
upload: upload
};
exports = exportObj;
瞬间高大上了。
那么,问题来了,怎么将业务处理模块并入呢?同样的,还是得靠全局模块。修改index.js
var server = require("./server");
var router = require("./router");
var handler = require("./handler");
handlers['/'] = handlers['start'];
server.start(router.route, handler);
似乎不难理解,唯一的问题是,start函数又多出了一个参数,这个参数并不是服务器模块所需要的,但却是路由模块所需要的,而路由模块是在服务器模块中调用的。所以,我们间接的将业务处理模块,通过服务器模块,传递给路由模块。修改server.js:
function start(route, handle) {
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname;
console.log("Request for " + pathname + " received.");
route(handle, pathname);
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(8888);
}
exports.start = start;
然后修改router.js:
function route(handle, pathname) {
console.log("About to route a request for " + pathname);
if (typeof handle[pathname] === 'function') {
handle[pathname]();
} else {
console.log("No request handler found for " + pathname);
}
}
exports.route = route;
最终形态
这样似乎就完美了。但还有个小问题,不同业务处理函数也许输出不一样,我们总不能在每个业务处理函数中都return要输出的东西,然后交给服务器模块去输出吧?毕竟,输出靠的是createServer回调函数的response对象。更好的办法是,将response对象传给业务处理模块。那么,server.js的最终形态就是:var http = require("http");
var url = require("url");
function start(route, handler) {
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname;
route(pathname, handler, response);
response.writeHead(200, {"Content-type": "text/plain"});
response.write("hello world");
response.end();
}
http.createServer(onRequest).listen(8080);
}
exports.start = start;
而路由模块也相应的做点更改(其实就是传个参数,酱油了一次),router.js最终形态如下:
function route(handle, pathname, response) {
console.log("About to route a request for " + pathname);
if (typeof handle[pathname] === 'function') {
handle[pathname](response);
} else {
console.log("No request handler found for " + pathname);
}
}
exports.route = route;
业务处理模块handler.js最终如下:
function start(response) {
response.writeHead(200, {"Content-type": "text/plain"});
response.write("Request handler 'start' was called.");
response.end();
}
function upload(response) {
response.writeHead(200, {"Content-type": "text/plain"});
response.write("Request handler 'upload' was called.");
response.end();
}
var exportObj = {
start: start,
upload: upload
};
exports = exportObj;
可能又会有强迫症患者发现了,部分代码是可以复用的,我们继续包装:
function output(response, message) {
response.writeHead(200, {"Content-type": "text/plain"});
response.write(message);
response.end();
}
function start(response) {
output(response, "Request handler 'start' was called.");
}
function upload(response) {
output(response, "Request handler 'upload' was called.");
}
var exportObj = {
start: start,
upload: upload
};
module.exports = exportObj;
这样就告一段落了。
以上只是一些简单的模块,并没有多少实际内容,具体内容还是得根据需要来进行填充。也许这种思路不是最佳的,但也并不是一文不值,至少,它体现了一种功能分离、低耦合的思想。如果大家有更好的建议,或者发现不足、错误之处,欢迎在评论里提出。