构建基于Node.js的web应用

构建基于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应用的代码的组织形式,希望能帮助大家。






  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值