构建基于Node.js的web应用

来源:互联网 发布:php 直播功能如何实现 编辑:程序博客网 时间:2024/06/04 19:03

构建基于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)中是为了当项目变得越来越大的时候便与整理。
首先,我们来该文件看看需要引入的模块
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应用的代码的组织形式,希望能帮助大家。






原创粉丝点击