一个完整的基于NodeJs的web应用

a.提供web页面,需要一个HTTP服务器

b.对于不同的请求,根据请求的URL,我们的服务器需要给予不同的响应,需要一个路由,用于把请求对应到请求处理程序

c.当请求被服务器接收并通过路由传递之后,需要可以对其进行处理,因此我们需要最终的请求处理程序

d.路由还应该能处理POST数据,并且把数据封装成为更友好的格式传递请求处理程序,因此需哟啊请求数据处理功能。

e.把内容显示出来,需要一些视图逻辑供请求处理程序使用,以便将内容发送给浏览器。

构建应用的模块

1.一个基础的HTTP服务器

创建文件server.js如下:

var http = require("http");//require为node.js自带的http模块
http.createServer(function(request,response){
    response.writeHead(200,{"content-Type":"text/plain"});
    response.write("Hello World");
    response.end();
    }).listen(8888);

执行:node server.js 访问localhost:8888端口即可访问创建的服务器。

分析:

第一行,require为node.js自带的http模块,并复制给http变量

接下来调用http模块提供的函数:createServer。这个函数会返回一个对象,这个对象有一个Listen的方法,这个方法参数可以指定HTTP服务器监听的端口。

var server = http.createServer();
server.listen(8888);

2.进行函数传递:

function say(word){ console.log(word); } function execute(someFunction,value){ someFunction(value); } execute(say,”hello”);

3.函数传递是如何让服务器工作的?

第一段代码等价于:

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(8888);

4.基于事件驱动的回调

nodeJs是事件驱动的。

请求是异步的,随时可能到达,但是我们服务器却在一个单一进程中。

创建了服务器,并且向创建它的方法传递一个函数,无论何时我们的服务器收到一个请求,这个函数就会被调用。

var http = require("http");
http.createServer(function(request,response){
    console.log("Request received");
    response.writeHead(200,{"content-Type":"text/plain"});
    response.write("Hello World");
    response.end();
    }).listen(8888);
console.log("Server has started");

这段代码会在运行node server.js时,立马输出Server has started

在我们通过浏览器想服务器发送请求,即输入http://localhost:8888/时,Request recived也会输出到命令行。

5.服务器是如何处理请求的

回调函数onRequest()的主体,有两个参数被传入:request和response

response.writeHead()行数发送一个HTTP状态200和HTTP的内容类型(content-type),使用response.write()函数在HTTP相应的主体中发送文本Hello world。最后调用response.end()完成响应。

6.服务端的模块放在哪里?

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); console.log(“Server has started”); } exports.start = start;

可以创建主文件index.js,并在其中启动HTTP了

服务器的代码仍在server.js中

var server = require("./server");
server.start();

请求这个文件并把它指向一个变量,其中已导出的函数就可以被使用了。

通过这种方式,我们可以把不同部分放入不同的文件中,并且通过生成模块的方式把它们连接到一起。

处理不用的HTTP请求在我们的代码中是一个不同的部分,叫做“路由选择”

7.如何来进行请求的“路由”

创建router.js如下:

function route(pathname) {
    console.log("About to route a request for "+pathname);
}
exports.route = route;

在主文件index.js中引入

var router = require("./router");
server.start(router.route);

8.因为驱动执行

9.路由给真正的请求处理程序

10.让请求处理程序作出响应

因此,我们需要让请求处理程序能够像onRequest函数那样可以和浏览器进行“对话”。

11.不好的实现方式

让请求处理程序通过onRequest函数直接返回,他们要展示给用户的信息。

12.阻塞和非阻塞

function start() {
    console.log("Request handler 'start' was called");
    function sleep(milliSeconds) {
        var startTime = new Date().getTime();
        while(new Date().getTime()<startTime + milliSeconds);
    }
    sleep(10000);
    return "hello Start";
}

但由于start()包含了阻塞操作,他阻塞了所有其他的处理工作,会使其他的处理程序也要等待10秒

Node.js可以在不新增额外线程的情况下,依然可以对任务进行并行处理 —— Node.js是单线程的。它通过事件轮询(event loop)来实现并行操作,对此,我们应该要充分利用这一点 —— 尽可能的避免阻塞操作,取而代之,多使用非阻塞操作。

要用非阻塞操作,就需要使用回调,通过将函数作为参数传递给其他需要花时间做处理的函数(比如:休眠10秒,查询数据库,进行大量的计算)

var exec = require("child_process").exec;
function start() {
    console.log("Request handler 'start' was called.");
    var content = "empty";
    exec("ls-lah",function(error,stdout,stderr){
        content = stdout;
    });
    return content;
}

上面代码引入了一个新的Nodejs模块,child_procee。因为他有一个非阻塞操作exec(); 他从nodejs来执行一个shell命令。上面例子用它来获取当前路径下所有的文件(ls-lah),然后,当/startURL请求的时候讲文件信息输出到浏览器中。

13.以非阻塞操作进行请求响应

用nodejs就有这样一种实现方案:函数传递。

应用已经可以通过应用各层之间传递值的方式(请求处理程序->请求路由->服务器)将请求处理程序返回的内容(请求处理程序最终显示给用户的内容)传递给HTTP服务器。

14.更有用的场景

服务器,请求有了以及请求程序程序都已经完成了。

(1)处理POST请求

当用户提交表单时,触发/upload请求处理程序处理POST请求的问题。

因为POST请求一般都会比较重,用户可能会输出大量的内容,用阻塞的方式处理大数据量的请求必然会导致用户操作的阻塞。

通过request对象上注册监听器(listener)来实现。这里的request对象是每次接收到HTTP请求,都会把该对象传递给onRequest回调函数。

request.addListener(“data”,function(chunk){ //called when a new chunk of data was received }); request.addListener(“end”,function(chunk){ //called when all chunks of data have been received });

function start(route,handle){
    function onRequest(request,response){
        var postData = "";
        var pathname = url.parse(request.url).pathname;
        console.log("Request for "+pathname+" received.");
        // 设置了接收数据的编码格式为UTF-8,
        request.setEncoding("utf-8");
        // 然后注册了”data“事件的监听器,用于收集每次接收到的新数据块,并将其赋值给postData变量
        request.addListener("data",function(postDataChunk){
            postData += postDataChunk;
            console.log("Received POST data chunk '"+postDataChunk+"'.");
        });
        // 将请求路由的调用移动到end事件处理程序中,以确保他只会当所有数据接收完毕后才触发,并且只触发一次
        // 同时将post数据传递给请求路由,因为这些数据,请求处理程序会用到。
        request.addListener("end",function(){
           route(handle,pathname,response,postData);
        });
       
    }
    http.createServer(onRequest).listen(8888);
    console.log("Server has started");
}
exports.start = start;

当前我们把请求的真个消息体传递给了请求路由和请求处理程序。

querystring模块来实现:

response.write("You are sent the text: "+querystring.parse(postData).text);

(2)处理文件上传

处理文件上传就是处理POST数据

使用该模块,首先需要安装改模块:

npm install formidable

var formidable = require("formidable");

该模块就是将通过HTTP POST请求提交的表单,在Node.js中可以被解析。我们要做的就是创建一个新的IncomingForm,它是对提交表单的抽象表示,之后,就可以用它解析request对象,获取表单中需要的数据字段。

var formidable = require("formidable");
var http = require("http");
var sys = require("sys");
 http.createServer(function(req,res){
    if(req.url =='/upload'&&req.toLowerCase()=='post'){
        var form = new formidable.IncomingForm();
        form.parse(req,function(err,fields,files){
            res.writeHead(200,{"content-Type":"text/plain"});
            res.write("received upload:\n\n");
            res.end(sys.inspect({fields:fields,files:files}));
        });
        return;
    }
    //show a file upload form
    res.writeHead(200,{"content-Type":"text/html"});
    res.end(
        '<form action="/upload" enctype="multipart/form-data" '+ 'method="post">'+
        '<input type="text" name="title"><br>'+
        '<input type="file" name="upload" multiple="multiple"><br>'+
        '<input type="submit" value="Upload">'+
        '</form>'
        );
    
}).listen(8888);

需要把文件读取到我们的服务器中,使用一个叫fs的模块。

我们需要的就是:

1.在/start表单中添加一个文件上传元素

2.将node-formidable整合到我们的upload请求处理程序中,用于将上传的图片保存到/tmp/test.png

3.将上传的图片内嵌到/uploadURL输出的HTML中

整个过程如下:

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("Request for "+ pathname +" received.");
    route(handle, pathname, response, request);//路由参数修改
  }
  http.createServer(onRequest).listen(8888);
  console.log("Server has started.");
}
exports.start = start;

router.js

function route(handle,pathname,response,request) {
    console.log("About to route a request for "+pathname);
    if(typeof handle[pathname] === "function"){
        return 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;

requestHandlers.js

var querystring = require("querystring"),
    fs = require("fs"),
    formidable = require("formidable");
function start(response){
  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" multiple="multiple">'+
    '<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();//调用formidable模块
  console.log("about to parse");
  //通过调用form.parse传递给回调函数的files对象的内容
  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){
  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;

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);

上面这些就完成了一个Node.js的web应用,技术点:服务端Javascript、函数式编程、阻塞与非阻塞、回调、事件、内部和外部模块。

Express:

Express是一个简介、灵活的node.js Web应用开发框架,它提供一系列强大的特性,帮助你创建各种web和移动设备应用。