构建基于Node.js的web应用
(参考书籍《node入门》)
在写Node.js应用的时候我们不仅要知道
如何写代码,还要知道
如何组织这些代码。
下面我们来看一个简单的基于Node.js的web应用,以此来学习如何组织我们项目的代码,这个应用的主要功能是上传图片,并在浏览器中展示出来。
应用的结构
我们来看看整个
应用结构
:
project
+----node_modules
|----router.js
|----server.js
|----requestHandles.js
|----index.js
node_modules中存放的是项目相关的依赖。
router.js为路由,以便我们队不同的url进行不同的处理。
requestHandles.js是对不同url进行的具体的处理。
server.js为服务器模块,我们将在这里创建服务器。
index.js为整个项目的入口文件。
这个web应用,它的代码并不是简单的凑到一块,而是有一定的结构,是模块化的组织方式,不同的功能对应不同的模块(文件),这有利于代码的维护和重构,在大项目中也有利于团队的合作。
现在我们来具体到每个文件,一点一点的分析这个web应用。
1. 先来看一下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;
console.log("Requset for " + pathname + " received");
route(handle, pathname, response, request);
}
http.createServer(onRequest).listen(8888);
console.log("Server has started.");
}
exports.start = start;
在这里,我们运用node.js自带的
http模块的
createServer()方法创建一个服务器,并在8888端口进行监听,我们可以通过访问
http://localhost:8888/,在浏览器中访问该服务器。通过
url模块对浏览器发来的请求
request进行解析,提取出想要的信息。
另外我们可以看到,start函数接受一个
route函数作为参赛,这个
route便是路由,通过调用
route(handle, pathname, response, request)去处理相应的请求。
2.我们再来看看router.js文件:
function route (handle, pathname, response, request) {
console.log("About to route a request for" + pathname);
if(typeof handle[pathname] === 'function') {
handle[pathname](response, request);
} else {
console.log("No request handler found for " + pathname);
response.writeHead(404, {"Content-Type": "text/plain"});
response.write("404 Not found");
response.end();
}
}
exports.route = route;
在router.js文件中我们看到,
route函数中接收了handle, pathname, response, request四个参数,
通过不同的
pathname调用不同的
handle对象中的函数,并把
response和request传入handle对象中相应的函数。如果找不到相应的handle就返回一个404错误。我们继续顺藤摸瓜看看handle对象里有什么对应的函数。
3. requestHandlers.js
该文件中有start、upload、show这三个函数,分别对应:返回首页内容,上传文件图片,显示文件图片,这三个函数就是放在handle对象里的函数,
将它们单独提取出来放在一个文件中(requestHandlers.js)中是为了当项目变得越来越大的时候便与整理。
将它们单独提取出来放在一个文件中(requestHandlers.js)中是为了当项目变得越来越大的时候便与整理。
首先,我们来该文件看看需要引入的模块
var fs = require('fs'),
formidable = require("formidable");
我们要将文件读取到我们的服务器中,需要使用一个叫
fs的模块。故我们需要
require('fs'),该模块的作用是帮助我们读取文件。
formidable模块这是帮助我们分析上传的文件数据,即处理post数据。
函数start:
function start(response, request) {
console.log("Request handler 'start' was called.");
var body = '<html>'+
'<head>'+
'<meta http-equiv="Content-Type" '+
'content="text/html; charset=UTF-8" />'+
'</head>'+
'<body>'+
'<form action="/upload" enctype="multipart/form-data" '+
'method="post">'+
'<input type="file" name="upload">'+
'<input type="submit" value="Upload file" />'+
'</form>'+
'</body>'+
'</html>';
response.writeHead(200, {"Content-Type": "text/html"});
response.write(body);
response.end();
}
start函数用于给页面返回一个表单,可以用于上传图片。
upload函数:
function upload(response, request) {
console.log("Request handler 'upload' was called");
var form = new formidable.IncomingForm();
form.uploadDir = 'tmp';
console.log("about to parse");
form.parse(request, function(error, fields, files) {
console.log("parsing done");
fs.renameSync(files.upload.path, "/tmp/test.png");
response.writeHead(200, {"Content-Type": "text/html"});
response.write("received image:<br/>")
response.write("<img src='/show' />");
response.end();
})
}
在uoload中,我们用到了
formidable模块中的
IncomingForm()方法,并把该方法赋给
form变量,然后调用
form.parse(),回调函数中我们可以转换请求中的表单数据,拿到所有的字段域和文本信息。在回调函数中,我们调用函数
fs.renameSync(files.upload.path, "/tmp/test.png"),这里的意思的读取
files.upload.path路径指向的图片,也就是你选择上传的图片,并将它保存到tmp文件内命名为test.png。
form.uploadDir = ‘tmp’的作用是解决跨磁盘传输文件的问题,如果没有设置uploadDir的话,跨域传输文件可能会报错。
函数show:
function show(response, request) {
console.log("Request handler 'show' was called.")
fs.readFile("/tmp/test.png", "binary", function(error, file) {
if(error) {
response.writeHead(500, {"Content-Type": "text/plain"});
response.write(error + "\n");
response.end();
} else {
response.writeHead(200, {"Content-Type": "image/png"});
response.write(file, "binary");
response.end();
}
})
}
show函数顾名思义,就是用来显示图片的,在函数中我们看到,我们用到了引进来的fs模块中的readFile方法来读取指定路径的文件,如何读入错误,则进行错误处理。
整个requestHanlders.js文件是这样的:
var fs = require('fs'),
formidable = require("formidable");
function start(response, request) {
console.log("Request handler 'start' was called.");
var body = '<html>'+
'<head>'+
'<meta http-equiv="Content-Type" '+
'content="text/html; charset=UTF-8" />'+
'</head>'+
'<body>'+
'<form action="/upload" enctype="multipart/form-data" '+
'method="post">'+
'<input type="file" name="upload">'+
'<input type="submit" value="Upload file" />'+
'</form>'+
'</body>'+
'</html>';
response.writeHead(200, {"Content-Type": "text/html"});
response.write(body);
response.end();
}
function upload(response, request) {
console.log("Request handler 'upload' was called");
var form = new formidable.IncomingForm();
form.uploadDir = 'tmp';
console.log("about to parse");
form.parse(request, function(error, fields, files) {
console.log("parsing done");
fs.renameSync(files.upload.path, "/tmp/test.png");
response.writeHead(200, {"Content-Type": "text/html"});
response.write("received image:<br/>")
response.write("<img src='/show' />");
response.end();
})
}
function show(response, request) {
console.log("Request handler 'show' was called.")
fs.readFile("/tmp/test.png", "binary", function(error, file) {
if(error) {
response.writeHead(500, {"Content-Type": "text/plain"});
response.write(error + "\n");
response.end();
} else {
response.writeHead(200, {"Content-Type": "image/png"});
response.write(file, "binary");
response.end();
}
})
}
exports.start = start;
exports.upload = upload;
exports.show = show;
4.index.js
最后我们来看一下入口文件index.js
var server = require("./server");
var router = require("./router");
var requestHandlers = require("./requestHandlers");
var handle = {}
handle["/"] = requestHandlers.start;
handle["/start"] = requestHandlers.start;
handle["/upload"] = requestHandlers.upload;
handle["/show"] = requestHandlers.show;
server.start(router.route, handle);
可以看到我们为什么把
index.js叫做
入口文件,这里我们把其他三个文件server.js、router.js、requestHandlers.js都引了进来,然后通过一个
handle对象将requestHandlers中的相应方法与相应的路径对应起来。再通过
server.start(router.route, handle)将三者结合在一起。
在项目中,为了代码的可重用性,很多地方我们不采用硬编码的方式把它写死,而是通过函数传参等依赖注入的方式去构造我们的代码,比如这个handle对象,它把相应的路径映射到了对应的处理方法,然后通过参数传递的方式一路传到了route方法中。这样的话,当有新的路径和requestHandlers.js中有新的对应的处理方法时,我们只需要在handle对象中增加相应的路径跟方法就可以了,不需要对route方法进行改写。
而且为了以
不阻塞
的方式去实现我们的代码,我也用函数传递的方式,将
response
对象传递到了
requestHanlders.js中
,在里面的每个函数中调用
response.writeHead()、response.write()、response.end()
等方法,避免当其中一个函数(start()或upload()或show())消耗比较长的时间的时候会对后面的请求起到阻塞的效果。
这就就是一个简单的基于node.js的web应用的代码的组织形式,希望能帮助大家。